mirror of
https://github.com/tangkunyin/hexo-theme-jsimple.git
synced 2026-01-25 14:56:28 +00:00
600 lines
27 KiB
JavaScript
600 lines
27 KiB
JavaScript
/**
|
||
* Zepto扩展
|
||
*/
|
||
$.fn.scrollTo = function(options) {
|
||
const defaults = {
|
||
toT : 0, //滚动目标位置
|
||
durTime : 500, //过渡动画时间
|
||
delay : 30, //定时器时间
|
||
callback:null //回调函数
|
||
};
|
||
let opts = $.extend(defaults,options),
|
||
timer = null,
|
||
_this = this,
|
||
curTop = _this.scrollTop(),//滚动条当前的位置
|
||
subTop = opts.toT - curTop, //滚动条目标位置和当前位置的差值
|
||
index = 0,
|
||
dur = Math.round(opts.durTime / opts.delay),
|
||
smoothScroll = function(t){
|
||
index++;
|
||
let per = Math.round(subTop/dur);
|
||
if(index >= dur) {
|
||
_this.scrollTop(t);
|
||
window.clearInterval(timer);
|
||
if(opts.callback && typeof opts.callback == 'function'){
|
||
opts.callback();
|
||
}
|
||
return;
|
||
} else {
|
||
_this.scrollTop(curTop + index*per);
|
||
}
|
||
};
|
||
timer = window.setInterval(function(){
|
||
smoothScroll(opts.toT);
|
||
}, opts.delay);
|
||
return _this;
|
||
};
|
||
/**
|
||
* Local Search
|
||
* @author https://github.com/theme-next/hexo-theme-next
|
||
*/
|
||
const LocalSearch = {
|
||
searchPath: '',
|
||
trigger: 'auto',
|
||
topN: '1',
|
||
unescape: false,
|
||
isfetched: false,
|
||
isXml: true,
|
||
initParams (data) {
|
||
LocalSearch.searchPath = data.dbPath;
|
||
LocalSearch.trigger = data.trigger;
|
||
LocalSearch.topN = data.topN;
|
||
LocalSearch.unescape = data.unescape === 'false' ? false : true;
|
||
if (LocalSearch.searchPath.length === 0) {
|
||
LocalSearch.searchPath = "/search.xml";
|
||
} else if (/json$/i.test(LocalSearch.searchPath)) {
|
||
LocalSearch.isXml = false;
|
||
}
|
||
},
|
||
onPopupClose () {
|
||
$('.popup').hide();
|
||
$('#local-search-input').val('');
|
||
$('.search-result-list').remove();
|
||
$('#no-result').remove();
|
||
$(".local-search-pop-overlay").remove();
|
||
$('body').css('overflow', '');
|
||
},
|
||
proceedsearch () {
|
||
$("body").append('<div class="search-popup-overlay local-search-pop-overlay"></div>').css('overflow', 'hidden');
|
||
$('.search-popup-overlay').click(LocalSearch.onPopupClose);
|
||
$('.popup').toggle();
|
||
const $localSearchInput = $('#local-search-input');
|
||
$localSearchInput.attr("autocapitalize", "none");
|
||
$localSearchInput.attr("autocorrect", "off");
|
||
$localSearchInput.focus();
|
||
},
|
||
// search function;
|
||
searchFunc (search_id, content_id) {
|
||
// if loading exists then return.
|
||
if (!$('.search-popup-overlay').is(':visible')) {
|
||
// start loading animation
|
||
$("body").append('<div class="search-popup-overlay local-search-pop-overlay">' +
|
||
'<div id="search-loading-icon">' +
|
||
'<i class="fa fa-spinner fa-pulse fa-5x fa-fw"></i>' +
|
||
'</div>' +
|
||
'</div>').css('overflow', 'hidden');
|
||
$("#search-loading-icon").css('margin', '20% auto 0 auto').css('text-align', 'center');
|
||
} else {
|
||
alert("Fetching data...don't worry")
|
||
}
|
||
const input = document.getElementById(search_id);
|
||
const resultContent = document.getElementById(content_id);
|
||
if (!input || !resultContent) {
|
||
console.error('Elements not exists with searchId: '+search_id+', resultContentId: '+content_id);
|
||
return;
|
||
}
|
||
if (LocalSearch.unescape) {
|
||
// ref: https://github.com/ForbesLindesay/unescape-html
|
||
var unescapeHtml = function(html) {
|
||
return String(html)
|
||
.replace(/"/g, '"')
|
||
.replace(/'/g, '\'')
|
||
.replace(/:/g, ':')
|
||
// replace all the other &#x; chars
|
||
.replace(/&#(\d+);/g, function (m, p) { return String.fromCharCode(p); })
|
||
.replace(/</g, '<')
|
||
.replace(/>/g, '>')
|
||
.replace(/&/g, '&');
|
||
};
|
||
}
|
||
$.ajax({
|
||
url: LocalSearch.searchPath,
|
||
timeout: 5000,
|
||
dataType: LocalSearch.isXml ? "xml" : "json",
|
||
async: true,
|
||
success (res) {
|
||
// get the contents from search data
|
||
LocalSearch.isfetched = true;
|
||
$('.popup').detach().appendTo('body');
|
||
const datas = LocalSearch.isXml ? $("entry", res).map(function () {
|
||
return {
|
||
title: $("title", this).text(),
|
||
content: $("content", this).text(),
|
||
url: $("url", this).text()
|
||
};
|
||
}).get() : res;
|
||
const inputEventFunction = function () {
|
||
const searchText = input.value.trim().toLowerCase();
|
||
const keywords = searchText.split(/[\s\-]+/);
|
||
if (keywords.length > 1) {
|
||
keywords.push(searchText);
|
||
}
|
||
const resultItems = [];
|
||
if (searchText.length > 0 && Array.isArray(datas)) {
|
||
for (const data of datas) {
|
||
if (!data || !data.content) continue;
|
||
let isMatch = false;
|
||
let hitCount = 0;
|
||
let searchTextCount = 0;
|
||
const title = data.title.trim();
|
||
const titleInLowerCase = title.toLowerCase();
|
||
let content = data.content.trim().replace(/<[^>]+>/g, "");
|
||
if (LocalSearch.unescape && content) {
|
||
content = unescapeHtml(content);
|
||
}
|
||
const contentInLowerCase = content.toLowerCase();
|
||
const articleUrl = decodeURIComponent(data.url).replace(/\/{2,}/g, '/');
|
||
let indexOfTitle = [];
|
||
let indexOfContent = [];
|
||
// only match articles with not empty titles
|
||
if (title != '') {
|
||
keywords.forEach(function (keyword) {
|
||
function getIndexByWord(word, text, caseSensitive) {
|
||
const wordLen = word.length;
|
||
if (wordLen === 0) {
|
||
return [];
|
||
}
|
||
let startPosition = 0, position = [], index = [];
|
||
if (!caseSensitive) {
|
||
text = text.toLowerCase();
|
||
word = word.toLowerCase();
|
||
}
|
||
while ((position = text.indexOf(word, startPosition)) > -1) {
|
||
index.push({position: position, word: word});
|
||
startPosition = position + wordLen;
|
||
}
|
||
return index;
|
||
}
|
||
|
||
indexOfTitle = indexOfTitle.concat(getIndexByWord(keyword, titleInLowerCase, false));
|
||
indexOfContent = indexOfContent.concat(getIndexByWord(keyword, contentInLowerCase, false));
|
||
});
|
||
if (indexOfTitle.length > 0 || indexOfContent.length > 0) {
|
||
isMatch = true;
|
||
hitCount = indexOfTitle.length + indexOfContent.length;
|
||
}
|
||
}
|
||
// show search results
|
||
if (isMatch) {
|
||
// sort index by position of keyword
|
||
[indexOfTitle, indexOfContent].forEach(function (index) {
|
||
index.sort(function (itemLeft, itemRight) {
|
||
if (itemRight.position !== itemLeft.position) {
|
||
return itemRight.position - itemLeft.position;
|
||
} else {
|
||
return itemLeft.word.length - itemRight.word.length;
|
||
}
|
||
});
|
||
});
|
||
|
||
// merge hits into slices
|
||
function mergeIntoSlice(text, start, end, index) {
|
||
let item = index[index.length - 1];
|
||
let position = item.position;
|
||
let word = item.word;
|
||
let hits = [];
|
||
let searchTextCountInSlice = 0;
|
||
while (position + word.length <= end && index.length != 0) {
|
||
if (word === searchText) {
|
||
searchTextCountInSlice++;
|
||
}
|
||
hits.push({position: position, length: word.length});
|
||
const wordEnd = position + word.length;
|
||
|
||
// move to next position of hit
|
||
|
||
index.pop();
|
||
while (index.length != 0) {
|
||
item = index[index.length - 1];
|
||
position = item.position;
|
||
word = item.word;
|
||
if (wordEnd > position) {
|
||
index.pop();
|
||
} else {
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
searchTextCount += searchTextCountInSlice;
|
||
return {
|
||
hits: hits,
|
||
start: start,
|
||
end: end,
|
||
searchTextCount: searchTextCountInSlice
|
||
};
|
||
}
|
||
|
||
const slicesOfTitle = [];
|
||
if (indexOfTitle.length != 0) {
|
||
slicesOfTitle.push(mergeIntoSlice(title, 0, title.length, indexOfTitle));
|
||
}
|
||
|
||
let slicesOfContent = [];
|
||
while (indexOfContent.length != 0) {
|
||
const item = indexOfContent[indexOfContent.length - 1];
|
||
const position = item.position;
|
||
const word = item.word;
|
||
// cut out 100 characters
|
||
let start = position - 20;
|
||
let end = position + 80;
|
||
if (start < 0) {
|
||
start = 0;
|
||
}
|
||
if (end < position + word.length) {
|
||
end = position + word.length;
|
||
}
|
||
if (end > content.length) {
|
||
end = content.length;
|
||
}
|
||
slicesOfContent.push(mergeIntoSlice(content, start, end, indexOfContent));
|
||
}
|
||
// sort slices in content by search text's count and hits' count
|
||
slicesOfContent.sort(function (sliceLeft, sliceRight) {
|
||
if (sliceLeft.searchTextCount !== sliceRight.searchTextCount) {
|
||
return sliceRight.searchTextCount - sliceLeft.searchTextCount;
|
||
} else if (sliceLeft.hits.length !== sliceRight.hits.length) {
|
||
return sliceRight.hits.length - sliceLeft.hits.length;
|
||
} else {
|
||
return sliceLeft.start - sliceRight.start;
|
||
}
|
||
});
|
||
|
||
// select top N slices in content
|
||
const upperBound = parseInt(LocalSearch.topN);
|
||
if (upperBound >= 0) {
|
||
slicesOfContent = slicesOfContent.slice(0, upperBound);
|
||
}
|
||
// highlight title and content
|
||
|
||
function highlightKeyword(text, slice) {
|
||
let result = '';
|
||
let prevEnd = slice.start;
|
||
slice.hits.forEach(function (hit) {
|
||
result += text.substring(prevEnd, hit.position);
|
||
const end = hit.position + hit.length;
|
||
result += '<b class="search-keyword">' + text.substring(hit.position, end) + '</b>';
|
||
prevEnd = end;
|
||
});
|
||
result += text.substring(prevEnd, slice.end);
|
||
return result;
|
||
}
|
||
|
||
let resultItem = '';
|
||
|
||
if (slicesOfTitle.length != 0) {
|
||
resultItem += "<li><a href='" + articleUrl + "' class='search-result-title'>" + highlightKeyword(title, slicesOfTitle[0]) + "</a>";
|
||
} else {
|
||
resultItem += "<li><a href='" + articleUrl + "' class='search-result-title'>" + title + "</a>";
|
||
}
|
||
|
||
slicesOfContent.forEach(function (slice) {
|
||
resultItem += "<a href='" + articleUrl + "'>" +
|
||
"<p class=\"search-result\">" + highlightKeyword(content, slice) +
|
||
"...</p>" + "</a>";
|
||
});
|
||
|
||
resultItem += "</li>";
|
||
resultItems.push({
|
||
item: resultItem,
|
||
searchTextCount: searchTextCount,
|
||
hitCount: hitCount,
|
||
id: resultItems.length
|
||
});
|
||
}
|
||
}
|
||
}
|
||
if (keywords.length === 1 && keywords[0] === "") {
|
||
resultContent.innerHTML = `<div id="no-result">
|
||
<i class="fa fa-search fa-5x"></i>
|
||
<p>按Esc退出搜索...</p>
|
||
</div>`
|
||
} else if (resultItems.length === 0) {
|
||
resultContent.innerHTML = `<div id="no-result">
|
||
<i class="fa fa-frown-o fa-5x"></i>
|
||
<p>暂未查到有关内容,催更点<a href="${SimpleCore.chatLink}" target="blank">这里</a></p>
|
||
</div>`
|
||
} else {
|
||
resultItems.sort(function (resultLeft, resultRight) {
|
||
if (resultLeft.searchTextCount !== resultRight.searchTextCount) {
|
||
return resultRight.searchTextCount - resultLeft.searchTextCount;
|
||
} else if (resultLeft.hitCount !== resultRight.hitCount) {
|
||
return resultRight.hitCount - resultLeft.hitCount;
|
||
} else {
|
||
return resultRight.id - resultLeft.id;
|
||
}
|
||
});
|
||
let searchResultList = '<ul class=\"search-result-list\">';
|
||
resultItems.forEach(function (result) {
|
||
searchResultList += result.item;
|
||
});
|
||
searchResultList += "</ul>";
|
||
resultContent.innerHTML = searchResultList;
|
||
}
|
||
};
|
||
|
||
if ('auto' === LocalSearch.trigger) {
|
||
input.addEventListener('input', inputEventFunction);
|
||
} else {
|
||
$('.search-icon').click(inputEventFunction);
|
||
input.addEventListener('keypress', function (event) {
|
||
if (event.keyCode === 13) {
|
||
inputEventFunction();
|
||
}
|
||
});
|
||
}
|
||
// remove loading animation
|
||
$(".local-search-pop-overlay").remove();
|
||
$('body').css('overflow', '');
|
||
LocalSearch.proceedsearch();
|
||
},
|
||
error (xhr,status,error) {
|
||
alert(status+', Load error when get '+ LocalSearch.searchPath);
|
||
console.error(error);
|
||
window.location.reload(true);
|
||
}
|
||
});
|
||
},
|
||
doSearch (e) {
|
||
e && e.stopPropagation();
|
||
if (!LocalSearch.isfetched) {
|
||
LocalSearch.searchFunc('local-search-input', 'local-search-result');
|
||
} else {
|
||
LocalSearch.proceedsearch();
|
||
}
|
||
}
|
||
};
|
||
/**
|
||
* JSimple Theme CoreLib
|
||
* @author tangkunyin 2017/1/25.
|
||
*/
|
||
const SimpleCore = {
|
||
creationTime: new Date(),
|
||
highlightTheme: null,
|
||
isPost: 'false',
|
||
prevTop: 0,
|
||
headerShow: true,
|
||
localSearch: {},
|
||
readMode: 'day',
|
||
chatLink: '/',
|
||
initParams (params) {
|
||
SimpleCore.creationTime = params.creationTime;
|
||
SimpleCore.highlightTheme = params.highlightTheme;
|
||
SimpleCore.isPost = params.isPost;
|
||
SimpleCore.localSearch = params.localSearch;
|
||
SimpleCore.readMode = params.readMode;
|
||
SimpleCore.chatLink = params.chatLink;
|
||
},
|
||
init (params) {
|
||
SimpleCore.initParams(params);
|
||
LocalSearch.initParams(SimpleCore.localSearch);
|
||
$(window).resize(function () {
|
||
SimpleCore.syncSize();
|
||
});
|
||
$(window).scroll(function (e) {
|
||
SimpleCore.scrollCallback();
|
||
});
|
||
$(document).on('click', '.btn-read-mode', function (e) {
|
||
e.preventDefault();
|
||
SimpleCore.switchReadMode();
|
||
});
|
||
$(document).on('click', '.btn-search', function (e) {
|
||
e.preventDefault();
|
||
SimpleCore.switchSearch();
|
||
});
|
||
$(document).on('click', '#local-search-input-tip', function (e) {
|
||
LocalSearch.doSearch(e);
|
||
});
|
||
$(document).on('click', '.popup-btn-close', function (e) {
|
||
e.preventDefault();
|
||
LocalSearch.onPopupClose();
|
||
});
|
||
$(document).on('click', '.popup', function (e) {
|
||
e.stopPropagation();
|
||
});
|
||
$(document).on('click', '.btn-gotop', function (e) {
|
||
e.preventDefault();
|
||
SimpleCore.goTop();
|
||
});
|
||
SimpleCore.changeReadModel();
|
||
SimpleCore.setPageCurrent();
|
||
SimpleCore.setBuildingTime();
|
||
SimpleCore.syncSize();
|
||
SimpleCore.printGreeting();
|
||
SimpleCore.registerHotKeyEvent();
|
||
SimpleCore.setDefaultReadingMode();
|
||
},
|
||
goTop () {
|
||
$("html, body").scrollTo({toT: 0});
|
||
},
|
||
setPageCurrent () {
|
||
if (SimpleCore.isPost === 'true') {
|
||
$('#cover').hide();
|
||
$('body').addClass('single');
|
||
} else {
|
||
$('#cover').show();
|
||
$('body').removeClass('single');
|
||
}
|
||
$.each($('.nav-menu a'), function (k, v) {
|
||
if (v.href === window.location.href || v.href === window.location.href.replace(/\/$/, '')) {
|
||
$(v).addClass('current');
|
||
} else {
|
||
$(v).removeClass('current');
|
||
}
|
||
});
|
||
},
|
||
scrollCallback () {
|
||
const top = document.documentElement.scrollTop
|
||
|| document.body.scrollTop
|
||
|| 0;
|
||
if (top > 100) {
|
||
$('.fixed-btn').show();
|
||
} else {
|
||
$('.fixed-btn').hide();
|
||
}
|
||
if ($('body').hasClass('single')) {
|
||
SimpleCore.headerShow = (top < 100 || (SimpleCore.prevTop - top) > 0) ? true : false;
|
||
SimpleCore.headerToggle();
|
||
}
|
||
SimpleCore.prevTop = top;
|
||
},
|
||
headerToggle () {
|
||
if (SimpleCore.headerShow) {
|
||
$('.page-title').css("top", 0);
|
||
$('.nav-user').css("top", 0);
|
||
if ($(window).width() < 480) {
|
||
$('#nav').css("top", 0);
|
||
}
|
||
} else {
|
||
$('.page-title').css("top", -45);
|
||
$('.nav-user').css("top", -45);
|
||
if ($(window).width() < 480) {
|
||
$('#nav').css("top", -45);
|
||
}
|
||
}
|
||
},
|
||
syncSize () { //同步窗口大小
|
||
const pageTitle = $('.page-title');
|
||
const size = $(window).width();
|
||
if (size > 768 && SimpleCore.isPost !== 'true') {
|
||
pageTitle.width($('#body > .main').width());
|
||
} else {
|
||
pageTitle.removeAttr('style');
|
||
}
|
||
if (size < 768) {
|
||
$('.site-name').click(function (e) {
|
||
e.preventDefault();
|
||
});
|
||
}
|
||
},
|
||
switchSearch () {
|
||
const srh = $('#search');
|
||
if (srh.hasClass('active')) {
|
||
srh.removeClass('active');
|
||
} else {
|
||
srh.addClass('active');
|
||
}
|
||
},
|
||
switchReadMode (mode) {
|
||
let next_mode = $('body').hasClass('night-mode') ? 'day' : 'night';
|
||
if (typeof mode === 'string' && mode.length > 0) {
|
||
next_mode = mode;
|
||
}
|
||
SimpleCore.setLocalData('read-mode', next_mode);
|
||
SimpleCore.changeReadModel();
|
||
},
|
||
changeReadModel () {
|
||
const btn = $('.btn-read-mode');
|
||
if (SimpleCore.getLocalData('read-mode') == 'night') {
|
||
$('body').addClass('night-mode');
|
||
btn.find('i').attr('class', 'fa fa-moon-o');
|
||
$(".cover-img").attr('src', location.origin + '/images/cover-night.webp');
|
||
} else {
|
||
$('body').removeClass('night-mode');
|
||
btn.find('i').attr('class', 'fa fa-sun-o');
|
||
$(".cover-img").attr('src', location.origin + '/images/cover-day.webp');
|
||
}
|
||
// 设置高亮主题
|
||
let highlCss = SimpleCore.highlightTheme || 'default';
|
||
if (highlCss !== 'default') {
|
||
highlCss += SimpleCore.getLocalData('read-mode') == 'night' ? '-dark' : '-light';
|
||
}
|
||
$('#highl_css').load(`/css/${highlCss}.min.css`);
|
||
},
|
||
alert (title,msg) {
|
||
const notice = $('#notice');
|
||
if (notice.length > 0) {
|
||
return false;
|
||
}
|
||
|
||
$('<div id="notice"></div>').appendTo($('body'));
|
||
const item = 'notice-' + (new Date().getTime());
|
||
const html = '<div id="' + item + '" class="notice-item">'
|
||
+ '<span class="notice-item-close"><i class="fa fa-close"></i></span>'
|
||
+ '<p><h3 style="text-align: center;margin:0 0 10px 0">'+title+'</h3>' + msg + '</p></div>';
|
||
$(html).appendTo($('#notice')).on('click', '.notice-item-close', function () {
|
||
$('#notice').remove();
|
||
return false;
|
||
});
|
||
|
||
//居中显示,于x秒后自动关闭
|
||
$('#notice').css('margin-right', -$('#notice').width() / 2);
|
||
setTimeout(function () {
|
||
$('#notice').remove();
|
||
}, 10000);
|
||
},
|
||
setLocalData (key, value) {
|
||
if (window.localStorage) {
|
||
window.localStorage.setItem(key, value);
|
||
}
|
||
},
|
||
getLocalData (key) {
|
||
if (window.localStorage) {
|
||
return window.localStorage.getItem(key);
|
||
}
|
||
},
|
||
setBuildingTime () {
|
||
const urodz = new Date(SimpleCore.creationTime); //建站时间
|
||
const now = new Date();
|
||
const ile = now.getTime() - urodz.getTime();
|
||
const buildingDays = Math.floor(ile / (1000 * 60 * 60 * 24));
|
||
$('#cpYear').html(now.getFullYear());
|
||
$('#siteBuildingTime').html(buildingDays);
|
||
return buildingDays;
|
||
},
|
||
printGreeting () {
|
||
const asciiTxt = " _ _ _ _ _ _ _ _ \n" +
|
||
"( ) ( ) (_ ) (_ ) ( ) _ ( ) (_ ) ( )\n" +
|
||
"| |_| | __ | | | | _ | | ( ) | | _ _ __ | | _| |\n" +
|
||
"| _ | /'__`\\ | | | | /'_`\\ | | | | | | /'_`\\ ( '__)| | /'_` |\n" +
|
||
"| | | |( ___/ | | | | ( (_) ) | (_/ \\_) |( (_) )| | | | ( (_| |\n" +
|
||
"(_) (_)`\\____)(___)(___)`\\___/' `\\___x___/'`\\___/'(_) (___)`\\__,_)\n" +
|
||
"\n已稳定运行" + this.setBuildingTime() + "天,拦截了无数次逗比攻击!🎉🎉🎉\n ";
|
||
console.log("%c\n"+asciiTxt, "color: #527fe2; font-family:KaiTi;font-size: 16px");
|
||
},
|
||
registerHotKeyEvent(e) {
|
||
$(document).on('keyup', function(event) {
|
||
const shouldDismissSearchPopup = event.key === 'Escape'
|
||
&& $('.search-popup').css('display') === 'block';
|
||
if (shouldDismissSearchPopup) {
|
||
$('.search-popup').hide();
|
||
$('.search-popup-overlay').remove();
|
||
$('body').css('overflow', '');
|
||
}
|
||
const shouldShowSearchPopup = event.key === 'Shift'
|
||
&& $('.search-popup').css('display') === 'none';
|
||
if (shouldShowSearchPopup) {
|
||
LocalSearch.doSearch(e);
|
||
}
|
||
});
|
||
},
|
||
setDefaultReadingMode() {
|
||
if (!SimpleCore.getLocalData('read-mode')
|
||
&& ('day' === SimpleCore.readMode || 'night' === SimpleCore.readMode)) {
|
||
SimpleCore.switchReadMode(SimpleCore.readMode);
|
||
}
|
||
}
|
||
};
|
||
|
||
$(function () {
|
||
window.jsi_config ? SimpleCore.init(window.jsi_config) : console.error('JSimple get wrong config: ', window.jsi_config)
|
||
}); |