2025-2-26-fixed

This commit is contained in:
2025-02-26 09:16:07 +08:00
parent bf50b6c865
commit 4968d276dc
456 changed files with 27801 additions and 1 deletions

View File

@ -0,0 +1,22 @@
/* global Fluid */
Fluid.boot = {};
Fluid.boot.registerEvents = function() {
Fluid.events.billboard();
Fluid.events.registerNavbarEvent();
Fluid.events.registerParallaxEvent();
Fluid.events.registerScrollDownArrowEvent();
Fluid.events.registerScrollTopArrowEvent();
Fluid.events.registerImageLoadedEvent();
};
Fluid.boot.refresh = function() {
Fluid.plugins.fancyBox();
Fluid.plugins.codeWidget();
Fluid.events.refresh();
};
document.addEventListener('DOMContentLoaded', function() {
Fluid.boot.registerEvents();
});

View File

@ -0,0 +1,286 @@
/* global Fluid */
/**
* Modified from https://blog.skk.moe/post/hello-darkmode-my-old-friend/
*/
(function(window, document) {
var rootElement = document.documentElement;
var colorSchemaStorageKey = 'Fluid_Color_Scheme';
var colorSchemaMediaQueryKey = '--color-mode';
var userColorSchemaAttributeName = 'data-user-color-scheme';
var defaultColorSchemaAttributeName = 'data-default-color-scheme';
var colorToggleButtonSelector = '#color-toggle-btn';
var colorToggleIconSelector = '#color-toggle-icon';
var iframeSelector = 'iframe';
function setLS(k, v) {
try {
localStorage.setItem(k, v);
} catch (e) {}
}
function removeLS(k) {
try {
localStorage.removeItem(k);
} catch (e) {}
}
function getLS(k) {
try {
return localStorage.getItem(k);
} catch (e) {
return null;
}
}
function getSchemaFromHTML() {
var res = rootElement.getAttribute(defaultColorSchemaAttributeName);
if (typeof res === 'string') {
return res.replace(/["'\s]/g, '');
}
return null;
}
function getSchemaFromCSSMediaQuery() {
var res = getComputedStyle(rootElement).getPropertyValue(
colorSchemaMediaQueryKey
);
if (typeof res === 'string') {
return res.replace(/["'\s]/g, '');
}
return null;
}
function resetSchemaAttributeAndLS() {
rootElement.setAttribute(userColorSchemaAttributeName, getDefaultColorSchema());
removeLS(colorSchemaStorageKey);
}
var validColorSchemaKeys = {
dark : true,
light: true
};
function getDefaultColorSchema() {
// 取默认字段的值
var schema = getSchemaFromHTML();
// 如果明确指定了 schema 则返回
if (validColorSchemaKeys[schema]) {
return schema;
}
// 默认优先按 prefers-color-scheme
schema = getSchemaFromCSSMediaQuery();
if (validColorSchemaKeys[schema]) {
return schema;
}
// 否则按本地时间是否大于 18 点或凌晨 0 ~ 6 点
var hours = new Date().getHours();
if (hours >= 18 || (hours >= 0 && hours <= 6)) {
return 'dark';
}
return 'light';
}
function applyCustomColorSchemaSettings(schema) {
// 接受从「开关」处传来的模式,或者从 localStorage 读取,否则按默认设置值
var current = schema || getLS(colorSchemaStorageKey) || getDefaultColorSchema();
if (current === getDefaultColorSchema()) {
// 当用户切换的显示模式和默认模式相同时,则恢复为自动模式
resetSchemaAttributeAndLS();
} else if (validColorSchemaKeys[current]) {
rootElement.setAttribute(
userColorSchemaAttributeName,
current
);
} else {
// 特殊情况重置
resetSchemaAttributeAndLS();
return;
}
// 根据当前模式设置图标
setButtonIcon(current);
// 设置代码高亮
setHighlightCSS(current);
// 设置其他应用
setApplications(current);
}
var invertColorSchemaObj = {
dark : 'light',
light: 'dark'
};
function getIconClass(scheme) {
return 'icon-' + scheme;
}
function toggleCustomColorSchema() {
var currentSetting = getLS(colorSchemaStorageKey);
if (validColorSchemaKeys[currentSetting]) {
// 从 localStorage 中读取模式,并取相反的模式
currentSetting = invertColorSchemaObj[currentSetting];
} else if (currentSetting === null) {
// 当 localStorage 中没有相关值,或者 localStorage 抛了 Error
// 先按照按钮的状态进行切换
var iconElement = document.querySelector(colorToggleIconSelector);
if (iconElement) {
currentSetting = iconElement.getAttribute('data');
}
if (!iconElement || !validColorSchemaKeys[currentSetting]) {
// 当 localStorage 中没有相关值,或者 localStorage 抛了 Error则读取默认值并切换到相反的模式
currentSetting = invertColorSchemaObj[getSchemaFromCSSMediaQuery()];
}
} else {
return;
}
// 将相反的模式写入 localStorage
setLS(colorSchemaStorageKey, currentSetting);
return currentSetting;
}
function setButtonIcon(schema) {
if (validColorSchemaKeys[schema]) {
// 切换图标
var icon = getIconClass('dark');
if (schema) {
icon = getIconClass(schema);
}
var iconElement = document.querySelector(colorToggleIconSelector);
if (iconElement) {
iconElement.setAttribute(
'class',
'iconfont ' + icon
);
iconElement.setAttribute(
'data',
invertColorSchemaObj[schema]
);
} else {
// 如果图标不存在则说明图标还没加载出来,等到页面全部加载再尝试切换
Fluid.utils.waitElementLoaded(colorToggleIconSelector, function() {
var iconElement = document.querySelector(colorToggleIconSelector);
if (iconElement) {
iconElement.setAttribute(
'class',
'iconfont ' + icon
);
iconElement.setAttribute(
'data',
invertColorSchemaObj[schema]
);
}
});
}
if (document.documentElement.getAttribute('data-user-color-scheme')) {
var color = getComputedStyle(document.documentElement).getPropertyValue('--navbar-bg-color').trim()
document.querySelector('meta[name="theme-color"]').setAttribute('content', color)
}
}
}
function setHighlightCSS(schema) {
// 启用对应的代码高亮的样式
var lightCss = document.getElementById('highlight-css');
var darkCss = document.getElementById('highlight-css-dark');
if (schema === 'dark') {
if (darkCss) {
darkCss.removeAttribute('disabled');
}
if (lightCss) {
lightCss.setAttribute('disabled', '');
}
} else {
if (lightCss) {
lightCss.removeAttribute('disabled');
}
if (darkCss) {
darkCss.setAttribute('disabled', '');
}
}
setTimeout(function() {
// 设置代码块组件样式
document.querySelectorAll('.markdown-body pre').forEach((pre) => {
var cls = Fluid.utils.getBackgroundLightness(pre) >= 0 ? 'code-widget-light' : 'code-widget-dark';
var widget = pre.querySelector('.code-widget-light, .code-widget-dark');
if (widget) {
widget.classList.remove('code-widget-light', 'code-widget-dark');
widget.classList.add(cls);
}
});
}, 200);
}
function setApplications(schema) {
// 设置 remark42 评论主题
if (window.REMARK42) {
window.REMARK42.changeTheme(schema);
}
// 设置 cusdis 评论主题
if (window.CUSDIS) {
window.CUSDIS.setTheme(schema);
}
// 设置 utterances 评论主题
var utterances = document.querySelector('.utterances-frame');
if (utterances) {
var utterancesTheme = schema === 'dark' ? window.UtterancesThemeDark : window.UtterancesThemeLight;
const message = {
type : 'set-theme',
theme: utterancesTheme
};
utterances.contentWindow.postMessage(message, 'https://utteranc.es');
}
// 设置 giscus 评论主题
var giscus = document.querySelector('iframe.giscus-frame');
if (giscus) {
var giscusTheme = schema === 'dark' ? window.GiscusThemeDark : window.GiscusThemeLight;
const message = {
setConfig: {
theme: giscusTheme,
}
};
// giscus.style.cssText += 'color-scheme: normal;';
giscus.contentWindow.postMessage({ 'giscus': message }, 'https://giscus.app');
}
}
// 当页面加载时,将显示模式设置为 localStorage 中自定义的值(如果有的话)
applyCustomColorSchemaSettings();
Fluid.utils.waitElementLoaded(colorToggleIconSelector, function() {
applyCustomColorSchemaSettings();
var button = document.querySelector(colorToggleButtonSelector);
if (button) {
// 当用户点击切换按钮时,获得新的显示模式、写入 localStorage、并在页面上生效
button.addEventListener('click', function() {
applyCustomColorSchemaSettings(toggleCustomColorSchema());
});
var icon = document.querySelector(colorToggleIconSelector);
if (icon) {
// 光标悬停在按钮上时,切换图标
button.addEventListener('mouseenter', function() {
var current = icon.getAttribute('data');
icon.classList.replace(getIconClass(invertColorSchemaObj[current]), getIconClass(current));
});
button.addEventListener('mouseleave', function() {
var current = icon.getAttribute('data');
icon.classList.replace(getIconClass(current), getIconClass(invertColorSchemaObj[current]));
});
}
}
});
Fluid.utils.waitElementLoaded(iframeSelector, function() {
applyCustomColorSchemaSettings();
});
})(window, document);

View File

@ -0,0 +1,184 @@
/* global Fluid */
HTMLElement.prototype.wrap = function(wrapper) {
this.parentNode.insertBefore(wrapper, this);
this.parentNode.removeChild(this);
wrapper.appendChild(this);
};
Fluid.events = {
registerNavbarEvent: function() {
var navbar = jQuery('#navbar');
if (navbar.length === 0) {
return;
}
var submenu = jQuery('#navbar .dropdown-menu');
if (navbar.offset().top > 0) {
navbar.removeClass('navbar-dark');
submenu.removeClass('navbar-dark');
}
Fluid.utils.listenScroll(function() {
navbar[navbar.offset().top > 50 ? 'addClass' : 'removeClass']('top-nav-collapse');
submenu[navbar.offset().top > 50 ? 'addClass' : 'removeClass']('dropdown-collapse');
if (navbar.offset().top > 0) {
navbar.removeClass('navbar-dark');
submenu.removeClass('navbar-dark');
} else {
navbar.addClass('navbar-dark');
submenu.removeClass('navbar-dark');
}
});
jQuery('#navbar-toggler-btn').on('click', function() {
jQuery('.animated-icon').toggleClass('open');
jQuery('#navbar').toggleClass('navbar-col-show');
});
},
registerParallaxEvent: function() {
var ph = jQuery('#banner[parallax="true"]');
if (ph.length === 0) {
return;
}
var board = jQuery('#board');
if (board.length === 0) {
return;
}
var parallax = function() {
var pxv = jQuery(window).scrollTop() / 5;
var offset = parseInt(board.css('margin-top'), 10);
var max = 96 + offset;
if (pxv > max) {
pxv = max;
}
ph.css({
transform: 'translate3d(0,' + pxv + 'px,0)'
});
var sideCol = jQuery('.side-col');
if (sideCol) {
sideCol.css({
'padding-top': pxv + 'px'
});
}
};
Fluid.utils.listenScroll(parallax);
},
registerScrollDownArrowEvent: function() {
var scrollbar = jQuery('.scroll-down-bar');
if (scrollbar.length === 0) {
return;
}
scrollbar.on('click', function() {
Fluid.utils.scrollToElement('#board', -jQuery('#navbar').height());
});
},
registerScrollTopArrowEvent: function() {
var topArrow = jQuery('#scroll-top-button');
if (topArrow.length === 0) {
return;
}
var board = jQuery('#board');
if (board.length === 0) {
return;
}
var posDisplay = false;
var scrollDisplay = false;
// Position
var setTopArrowPos = function() {
var boardRight = board[0].getClientRects()[0].right;
var bodyWidth = document.body.offsetWidth;
var right = bodyWidth - boardRight;
posDisplay = right >= 50;
topArrow.css({
'bottom': posDisplay && scrollDisplay ? '20px' : '-60px',
'right' : right - 64 + 'px'
});
};
setTopArrowPos();
jQuery(window).resize(setTopArrowPos);
// Display
var headerHeight = board.offset().top;
Fluid.utils.listenScroll(function() {
var scrollHeight = document.body.scrollTop + document.documentElement.scrollTop;
scrollDisplay = scrollHeight >= headerHeight;
topArrow.css({
'bottom': posDisplay && scrollDisplay ? '20px' : '-60px'
});
});
// Click
topArrow.on('click', function() {
jQuery('body,html').animate({
scrollTop: 0,
easing : 'swing'
});
});
},
registerImageLoadedEvent: function() {
if (!('NProgress' in window)) { return; }
var bg = document.getElementById('banner');
if (bg) {
var src = bg.style.backgroundImage;
var url = src.match(/\((.*?)\)/)[1].replace(/(['"])/g, '');
var img = new Image();
img.onload = function() {
window.NProgress && window.NProgress.status !== null && window.NProgress.inc(0.2);
};
img.src = url;
if (img.complete) { img.onload(); }
}
var notLazyImages = jQuery('main img:not([lazyload])');
var total = notLazyImages.length;
for (const img of notLazyImages) {
const old = img.onload;
img.onload = function() {
old && old();
window.NProgress && window.NProgress.status !== null && window.NProgress.inc(0.5 / total);
};
if (img.complete) { img.onload(); }
}
},
registerRefreshCallback: function(callback) {
if (!Array.isArray(Fluid.events._refreshCallbacks)) {
Fluid.events._refreshCallbacks = [];
}
Fluid.events._refreshCallbacks.push(callback);
},
refresh: function() {
if (Array.isArray(Fluid.events._refreshCallbacks)) {
for (var callback of Fluid.events._refreshCallbacks) {
if (callback instanceof Function) {
callback();
}
}
}
},
billboard: function() {
if (!('console' in window)) {
return;
}
// eslint-disable-next-line no-console
console.log(`
-------------------------------------------------
| |
| ________ __ _ __ |
| |_ __ |[ | (_) | ] |
| | |_ \\_| | | __ _ __ .--.| | |
| | _| | |[ | | | [ |/ /'\`\\' | |
| _| |_ | | | \\_/ |, | || \\__/ | |
| |_____| [___]'.__.'_/[___]'.__.;__] |
| |
| Powered by Hexo x Fluid |
| https://github.com/fluid-dev/hexo-theme-fluid |
| |
-------------------------------------------------
`);
}
};

View File

@ -0,0 +1,10 @@
/* global Fluid, CONFIG */
(function(window, document) {
for (const each of document.querySelectorAll('img[lazyload]')) {
Fluid.utils.waitElementVisible(each, function() {
each.removeAttribute('srcset');
each.removeAttribute('lazyload');
}, CONFIG.lazyload.offset_factor);
}
})(window, document);

View File

@ -0,0 +1,192 @@
/* global CONFIG */
// eslint-disable-next-line no-console
(function(window, document) {
// 查询存储的记录
function getRecord(Counter, target) {
return new Promise(function(resolve, reject) {
Counter('get', '/classes/Counter?where=' + encodeURIComponent(JSON.stringify({ target })))
.then(resp => resp.json())
.then(({ results, code, error }) => {
if (code === 401) {
throw error;
}
if (results && results.length > 0) {
var record = results[0];
resolve(record);
} else {
Counter('post', '/classes/Counter', { target, time: 0 })
.then(resp => resp.json())
.then((record, error) => {
if (error) {
throw error;
}
resolve(record);
}).catch(error => {
console.error('Failed to create: ', error);
reject(error);
});
}
}).catch((error) => {
console.error('LeanCloud Counter Error: ', error);
reject(error);
});
});
}
// 发起自增请求
function increment(Counter, incrArr) {
return new Promise(function(resolve, reject) {
Counter('post', '/batch', {
'requests': incrArr
}).then((res) => {
res = res.json();
if (res.error) {
throw res.error;
}
resolve(res);
}).catch((error) => {
console.error('Failed to save visitor count: ', error);
reject(error);
});
});
}
// 构建自增请求体
function buildIncrement(objectId) {
return {
'method': 'PUT',
'path' : `/1.1/classes/Counter/${objectId}`,
'body' : {
'time': {
'__op' : 'Increment',
'amount': 1
}
}
};
}
// 校验是否为有效的 Host
function validHost() {
if (CONFIG.web_analytics.leancloud.ignore_local) {
var hostname = window.location.hostname;
if (hostname === 'localhost' || hostname === '127.0.0.1') {
return false;
}
}
return true;
}
// 校验是否为有效的 UV
function validUV() {
var key = 'LeanCloud_UV_Flag';
var flag = localStorage.getItem(key);
if (flag) {
// 距离标记小于 24 小时则不计为 UV
if (new Date().getTime() - parseInt(flag, 10) <= 86400000) {
return false;
}
}
localStorage.setItem(key, new Date().getTime().toString());
return true;
}
function addCount(Counter) {
var enableIncr = CONFIG.web_analytics.enable && !Fluid.ctx.dnt && validHost();
var getterArr = [];
var incrArr = [];
// 请求 PV 并自增
var pvCtn = document.querySelector('#leancloud-site-pv-container');
if (pvCtn) {
var pvGetter = getRecord(Counter, 'site-pv').then((record) => {
enableIncr && incrArr.push(buildIncrement(record.objectId));
var ele = document.querySelector('#leancloud-site-pv');
if (ele) {
ele.innerText = (record.time || 0) + (enableIncr ? 1 : 0);
pvCtn.style.display = 'inline';
}
});
getterArr.push(pvGetter);
}
// 请求 UV 并自增
var uvCtn = document.querySelector('#leancloud-site-uv-container');
if (uvCtn) {
var uvGetter = getRecord(Counter, 'site-uv').then((record) => {
var incrUV = validUV() && enableIncr;
incrUV && incrArr.push(buildIncrement(record.objectId));
var ele = document.querySelector('#leancloud-site-uv');
if (ele) {
ele.innerText = (record.time || 0) + (incrUV ? 1 : 0);
uvCtn.style.display = 'inline';
}
});
getterArr.push(uvGetter);
}
// 如果有页面浏览数节点,则请求浏览数并自增
var viewCtn = document.querySelector('#leancloud-page-views-container');
if (viewCtn) {
var path = eval(CONFIG.web_analytics.leancloud.path || 'window.location.pathname');
var target = decodeURI(path.replace(/\/*(index.html)?$/, '/'));
var viewGetter = getRecord(Counter, target).then((record) => {
enableIncr && incrArr.push(buildIncrement(record.objectId));
var ele = document.querySelector('#leancloud-page-views');
if (ele) {
ele.innerText = (record.time || 0) + (enableIncr ? 1 : 0);
viewCtn.style.display = 'inline';
}
});
getterArr.push(viewGetter);
}
// 如果启动计数自增,批量发起自增请求
if (enableIncr) {
Promise.all(getterArr).then(() => {
incrArr.length > 0 && increment(Counter, incrArr);
});
}
}
var appId = CONFIG.web_analytics.leancloud.app_id;
var appKey = CONFIG.web_analytics.leancloud.app_key;
var serverUrl = CONFIG.web_analytics.leancloud.server_url;
if (!appId) {
throw new Error('LeanCloud appId is empty');
}
if (!appKey) {
throw new Error('LeanCloud appKey is empty');
}
function fetchData(api_server) {
var Counter = (method, url, data) => {
return fetch(`${api_server}/1.1${url}`, {
method,
headers: {
'X-LC-Id' : appId,
'X-LC-Key' : appKey,
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
};
addCount(Counter);
}
var apiServer = serverUrl || `https://${appId.slice(0, 8).toLowerCase()}.api.lncldglobal.com`;
if (apiServer) {
fetchData(apiServer);
} else {
fetch('https://app-router.leancloud.cn/2/route?appId=' + appId)
.then(resp => resp.json())
.then((data) => {
if (data.api_server) {
fetchData('https://' + data.api_server);
}
});
}
})(window, document);

View File

@ -0,0 +1,159 @@
/* global CONFIG */
(function() {
// Modified from [hexo-generator-search](https://github.com/wzpan/hexo-generator-search)
function localSearchFunc(path, searchSelector, resultSelector) {
'use strict';
// 0x00. environment initialization
var $input = jQuery(searchSelector);
var $result = jQuery(resultSelector);
if ($input.length === 0) {
// eslint-disable-next-line no-console
throw Error('No element selected by the searchSelector');
}
if ($result.length === 0) {
// eslint-disable-next-line no-console
throw Error('No element selected by the resultSelector');
}
if ($result.attr('class').indexOf('list-group-item') === -1) {
$result.html('<div class="m-auto text-center"><div class="spinner-border" role="status"><span class="sr-only">Loading...</span></div><br/>Loading...</div>');
}
jQuery.ajax({
// 0x01. load xml file
url : path,
dataType: 'xml',
success : function(xmlResponse) {
// 0x02. parse xml file
var dataList = jQuery('entry', xmlResponse).map(function() {
return {
title : jQuery('title', this).text(),
content: jQuery('content', this).text(),
url : jQuery('url', this).text()
};
}).get();
if ($result.html().indexOf('list-group-item') === -1) {
$result.html('');
}
$input.on('input', function() {
// 0x03. parse query to keywords list
var content = $input.val();
var resultHTML = '';
var keywords = content.trim().toLowerCase().split(/[\s-]+/);
$result.html('');
if (content.trim().length <= 0) {
return $input.removeClass('invalid').removeClass('valid');
}
// 0x04. perform local searching
dataList.forEach(function(data) {
var isMatch = true;
if (!data.title || data.title.trim() === '') {
data.title = 'Untitled';
}
var orig_data_title = data.title.trim();
var data_title = orig_data_title.toLowerCase();
var orig_data_content = data.content.trim().replace(/<[^>]+>/g, '');
var data_content = orig_data_content.toLowerCase();
var data_url = data.url;
var index_title = -1;
var index_content = -1;
var first_occur = -1;
// Skip matching when content is included in search and content is empty
if (CONFIG.include_content_in_search && data_content === '') {
isMatch = false;
} else {
keywords.forEach(function (keyword, i) {
index_title = data_title.indexOf(keyword);
index_content = data_content.indexOf(keyword);
if (index_title < 0 && index_content < 0) {
isMatch = false;
} else {
if (index_content < 0) {
index_content = 0;
}
if (i === 0) {
first_occur = index_content;
}
}
});
}
// 0x05. show search results
if (isMatch) {
resultHTML += '<a href=\'' + data_url + '\' class=\'list-group-item list-group-item-action font-weight-bolder search-list-title\'>' + orig_data_title + '</a>';
var content = orig_data_content;
if (first_occur >= 0) {
// cut out 100 characters
var start = first_occur - 20;
var end = first_occur + 80;
if (start < 0) {
start = 0;
}
if (start === 0) {
end = 100;
}
if (end > content.length) {
end = content.length;
}
var match_content = content.substring(start, end);
// highlight all keywords
keywords.forEach(function(keyword) {
var regS = new RegExp(keyword, 'gi');
match_content = match_content.replace(regS, '<span class="search-word">' + keyword + '</span>');
});
resultHTML += '<p class=\'search-list-content\'>' + match_content + '...</p>';
}
}
});
if (resultHTML.indexOf('list-group-item') === -1) {
return $input.addClass('invalid').removeClass('valid');
}
$input.addClass('valid').removeClass('invalid');
$result.html(resultHTML);
});
}
});
}
function localSearchReset(searchSelector, resultSelector) {
'use strict';
var $input = jQuery(searchSelector);
var $result = jQuery(resultSelector);
if ($input.length === 0) {
// eslint-disable-next-line no-console
throw Error('No element selected by the searchSelector');
}
if ($result.length === 0) {
// eslint-disable-next-line no-console
throw Error('No element selected by the resultSelector');
}
$input.val('').removeClass('invalid').removeClass('valid');
$result.html('');
}
var modal = jQuery('#modalSearch');
var searchSelector = '#local-search-input';
var resultSelector = '#local-search-result';
modal.on('show.bs.modal', function() {
var path = CONFIG.search_path || '/local-search.xml';
localSearchFunc(path, searchSelector, resultSelector);
});
modal.on('shown.bs.modal', function() {
jQuery('#local-search-input').focus();
});
modal.on('hidden.bs.modal', function() {
localSearchReset(searchSelector, resultSelector);
});
})();

View File

@ -0,0 +1,164 @@
/* global Fluid, CONFIG */
HTMLElement.prototype.wrap = function(wrapper) {
this.parentNode.insertBefore(wrapper, this);
this.parentNode.removeChild(this);
wrapper.appendChild(this);
};
Fluid.plugins = {
typing: function(text) {
if (!('Typed' in window)) { return; }
var typed = new window.Typed('#subtitle', {
strings: [
' ',
text
],
cursorChar: CONFIG.typing.cursorChar,
typeSpeed : CONFIG.typing.typeSpeed,
loop : CONFIG.typing.loop
});
typed.stop();
var subtitle = document.getElementById('subtitle');
if (subtitle) {
subtitle.innerText = '';
}
jQuery(document).ready(function() {
typed.start();
});
},
fancyBox: function(selector) {
if (!CONFIG.image_zoom.enable || !('fancybox' in jQuery)) { return; }
jQuery(selector || '.markdown-body :not(a) > img, .markdown-body > img').each(function() {
var $image = jQuery(this);
var imageUrl = $image.attr('data-src') || $image.attr('src') || '';
if (CONFIG.image_zoom.img_url_replace) {
var rep = CONFIG.image_zoom.img_url_replace;
var r1 = rep[0] || '';
var r2 = rep[1] || '';
if (r1) {
if (/^re:/.test(r1)) {
r1 = r1.replace(/^re:/, '');
var reg = new RegExp(r1, 'gi');
imageUrl = imageUrl.replace(reg, r2);
} else {
imageUrl = imageUrl.replace(r1, r2);
}
}
}
var $imageWrap = $image.wrap(`
<a class="fancybox fancybox.image" href="${imageUrl}"
itemscope itemtype="http://schema.org/ImageObject" itemprop="url"></a>`
).parent('a');
if ($imageWrap.length !== 0) {
if ($image.is('.group-image-container img')) {
$imageWrap.attr('data-fancybox', 'group').attr('rel', 'group');
} else {
$imageWrap.attr('data-fancybox', 'default').attr('rel', 'default');
}
var imageTitle = $image.attr('title') || $image.attr('alt');
if (imageTitle) {
$imageWrap.attr('title', imageTitle).attr('data-caption', imageTitle);
}
}
});
jQuery.fancybox.defaults.hash = false;
jQuery('.fancybox').fancybox({
loop : true,
helpers: {
overlay: {
locked: false
}
}
});
},
imageCaption: function(selector) {
if (!CONFIG.image_caption.enable) { return; }
jQuery(selector || `.markdown-body > p > img, .markdown-body > figure > img,
.markdown-body > p > a.fancybox, .markdown-body > figure > a.fancybox`).each(function() {
var $target = jQuery(this);
var $figcaption = $target.next('figcaption');
if ($figcaption.length !== 0) {
$figcaption.addClass('image-caption');
} else {
var imageTitle = $target.attr('title') || $target.attr('alt');
if (imageTitle) {
$target.after(`<figcaption aria-hidden="true" class="image-caption">${imageTitle}</figcaption>`);
}
}
});
},
codeWidget() {
var enableLang = CONFIG.code_language.enable && CONFIG.code_language.default;
var enableCopy = CONFIG.copy_btn && 'ClipboardJS' in window;
if (!enableLang && !enableCopy) {
return;
}
function getBgClass(ele) {
return Fluid.utils.getBackgroundLightness(ele) >= 0 ? 'code-widget-light' : 'code-widget-dark';
}
var copyTmpl = '';
copyTmpl += '<div class="code-widget">';
copyTmpl += 'LANG';
copyTmpl += '</div>';
jQuery('.markdown-body pre').each(function() {
var $pre = jQuery(this);
if ($pre.find('code.mermaid').length > 0) {
return;
}
if ($pre.find('span.line').length > 0) {
return;
}
var lang = '';
if (enableLang) {
lang = CONFIG.code_language.default;
if ($pre[0].children.length > 0 && $pre[0].children[0].classList.length >= 2 && $pre.children().hasClass('hljs')) {
lang = $pre[0].children[0].classList[1];
} else if ($pre[0].getAttribute('data-language')) {
lang = $pre[0].getAttribute('data-language');
} else if ($pre.parent().hasClass('sourceCode') && $pre[0].children.length > 0 && $pre[0].children[0].classList.length >= 2) {
lang = $pre[0].children[0].classList[1];
$pre.parent().addClass('code-wrapper');
} else if ($pre.parent().hasClass('markdown-body') && $pre[0].classList.length === 0) {
$pre.wrap('<div class="code-wrapper"></div>');
}
lang = lang.toUpperCase().replace('NONE', CONFIG.code_language.default);
}
$pre.append(copyTmpl.replace('LANG', lang).replace('code-widget">',
getBgClass($pre[0]) + (enableCopy ? ' code-widget copy-btn" data-clipboard-snippet><i class="iconfont icon-copy"></i>' : ' code-widget">')));
if (enableCopy) {
var clipboard = new ClipboardJS('.copy-btn', {
target: function(trigger) {
var nodes = trigger.parentNode.childNodes;
for (var i = 0; i < nodes.length; i++) {
if (nodes[i].tagName === 'CODE') {
return nodes[i];
}
}
}
});
clipboard.on('success', function(e) {
e.clearSelection();
e.trigger.innerHTML = e.trigger.innerHTML.replace('icon-copy', 'icon-success');
setTimeout(function() {
e.trigger.innerHTML = e.trigger.innerHTML.replace('icon-success', 'icon-copy');
}, 2000);
});
}
});
}
};

View File

@ -0,0 +1,99 @@
// 从配置文件中获取 umami 的配置
const website_id = CONFIG.web_analytics.umami.website_id;
// 拼接请求地址
const request_url = `${CONFIG.web_analytics.umami.api_server}/websites/${website_id}/stats`;
const start_time = new Date(CONFIG.web_analytics.umami.start_time).getTime();
const end_time = new Date().getTime();
const token = CONFIG.web_analytics.umami.token;
// 检查配置是否为空
if (!website_id) {
throw new Error("Umami website_id is empty");
}
if (!request_url) {
throw new Error("Umami request_url is empty");
}
if (!start_time) {
throw new Error("Umami start_time is empty");
}
if (!token) {
throw new Error("Umami token is empty");
}
// 构造请求参数
const params = new URLSearchParams({
startAt: start_time,
endAt: end_time,
});
// 构造请求头
const request_header = {
method: "GET",
headers: {
"Content-Type": "application/json",
"x-umami-api-key": "oZKCH3msvqt10VlXKwoJvHclmaS4bVx0",
},
};
// 获取站点统计数据
async function siteStats() {
try {
const response = await fetch(`${request_url}?${params}`, request_header);
const data = await response.json();
const uniqueVisitors = data.uniques.value; // 获取独立访客数
const pageViews = data.pageviews.value; // 获取页面浏览量
let pvCtn = document.querySelector("#umami-site-pv-container");
if (pvCtn) {
let ele = document.querySelector("#umami-site-pv");
if (ele) {
ele.textContent = pageViews; // 设置页面浏览量
pvCtn.style.display = "inline"; // 将元素显示出来
}
}
let uvCtn = document.querySelector("#umami-site-uv-container");
if (uvCtn) {
let ele = document.querySelector("#umami-site-uv");
if (ele) {
ele.textContent = uniqueVisitors;
uvCtn.style.display = "inline";
}
}
} catch (error) {
console.error(error);
return "-1";
}
}
// 获取页面浏览量
async function pageStats(path) {
try {
const response = await fetch(`${request_url}?${params}&url=${path}`, request_header);
const data = await response.json();
const pageViews = data.pageviews.value;
let viewCtn = document.querySelector("#umami-page-views-container");
if (viewCtn) {
let ele = document.querySelector("#umami-page-views");
if (ele) {
ele.textContent = pageViews;
viewCtn.style.display = "inline";
}
}
} catch (error) {
console.error(error);
return "-1";
}
}
siteStats();
// 获取页面容器
let viewCtn = document.querySelector("#umami-page-views-container");
// 如果页面容器存在,则获取页面浏览量
if (viewCtn) {
let path = window.location.pathname;
let target = decodeURI(path.replace(/\/*(index.html)?$/, "/"));
pageStats(target);
}

View File

@ -0,0 +1,245 @@
/* global Fluid, CONFIG */
window.requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame;
Fluid.utils = {
listenScroll: function(callback) {
var dbc = new Debouncer(callback);
window.addEventListener('scroll', dbc, false);
dbc.handleEvent();
return dbc;
},
unlistenScroll: function(callback) {
window.removeEventListener('scroll', callback);
},
listenDOMLoaded(callback) {
if (document.readyState !== 'loading') {
callback();
} else {
document.addEventListener('DOMContentLoaded', function () {
callback();
});
}
},
scrollToElement: function(target, offset) {
var of = jQuery(target).offset();
if (of) {
jQuery('html,body').animate({
scrollTop: of.top + (offset || 0),
easing : 'swing'
});
}
},
elementVisible: function(element, offsetFactor) {
offsetFactor = offsetFactor && offsetFactor >= 0 ? offsetFactor : 0;
var rect = element.getBoundingClientRect();
const viewportHeight = window.innerHeight || document.documentElement.clientHeight;
return (
(rect.top >= 0 && rect.top <= viewportHeight * (1 + offsetFactor) + rect.height / 2) ||
(rect.bottom >= 0 && rect.bottom <= viewportHeight * (1 + offsetFactor) + rect.height / 2)
);
},
waitElementVisible: function(selectorOrElement, callback, offsetFactor) {
var runningOnBrowser = typeof window !== 'undefined';
var isBot = (runningOnBrowser && !('onscroll' in window))
|| (typeof navigator !== 'undefined' && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent));
if (!runningOnBrowser || isBot) {
return;
}
offsetFactor = offsetFactor && offsetFactor >= 0 ? offsetFactor : 0;
function waitInViewport(element) {
Fluid.utils.listenDOMLoaded(function() {
if (Fluid.utils.elementVisible(element, offsetFactor)) {
callback();
return;
}
if ('IntersectionObserver' in window) {
var io = new IntersectionObserver(function(entries, ob) {
if (entries[0].isIntersecting) {
callback();
ob.disconnect();
}
}, {
threshold : [0],
rootMargin: (window.innerHeight || document.documentElement.clientHeight) * offsetFactor + 'px'
});
io.observe(element);
} else {
var wrapper = Fluid.utils.listenScroll(function() {
if (Fluid.utils.elementVisible(element, offsetFactor)) {
Fluid.utils.unlistenScroll(wrapper);
callback();
}
});
}
});
}
if (typeof selectorOrElement === 'string') {
this.waitElementLoaded(selectorOrElement, function(element) {
waitInViewport(element);
});
} else {
waitInViewport(selectorOrElement);
}
},
waitElementLoaded: function(selector, callback) {
var runningOnBrowser = typeof window !== 'undefined';
var isBot = (runningOnBrowser && !('onscroll' in window))
|| (typeof navigator !== 'undefined' && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent));
if (!runningOnBrowser || isBot) {
return;
}
if ('MutationObserver' in window) {
var mo = new MutationObserver(function(records, ob) {
var ele = document.querySelector(selector);
if (ele) {
callback(ele);
ob.disconnect();
}
});
mo.observe(document, { childList: true, subtree: true });
} else {
Fluid.utils.listenDOMLoaded(function() {
var waitLoop = function() {
var ele = document.querySelector(selector);
if (ele) {
callback(ele);
} else {
setTimeout(waitLoop, 100);
}
};
waitLoop();
});
}
},
createScript: function(url, onload) {
var s = document.createElement('script');
s.setAttribute('src', url);
s.setAttribute('type', 'text/javascript');
s.setAttribute('charset', 'UTF-8');
s.async = false;
if (typeof onload === 'function') {
if (window.attachEvent) {
s.onreadystatechange = function() {
var e = s.readyState;
if (e === 'loaded' || e === 'complete') {
s.onreadystatechange = null;
onload();
}
};
} else {
s.onload = onload;
}
}
var ss = document.getElementsByTagName('script');
var e = ss.length > 0 ? ss[ss.length - 1] : document.head || document.documentElement;
e.parentNode.insertBefore(s, e.nextSibling);
},
createCssLink: function(url) {
var l = document.createElement('link');
l.setAttribute('rel', 'stylesheet');
l.setAttribute('type', 'text/css');
l.setAttribute('href', url);
var e = document.getElementsByTagName('link')[0]
|| document.getElementsByTagName('head')[0]
|| document.head || document.documentElement;
e.parentNode.insertBefore(l, e);
},
loadComments: function(selector, loadFunc) {
var ele = document.querySelector('#comments[lazyload]');
if (ele) {
var callback = function() {
loadFunc();
ele.removeAttribute('lazyload');
};
Fluid.utils.waitElementVisible(selector, callback, CONFIG.lazyload.offset_factor);
} else {
loadFunc();
}
},
getBackgroundLightness(selectorOrElement) {
var ele = selectorOrElement;
if (typeof selectorOrElement === 'string') {
ele = document.querySelector(selectorOrElement);
}
var view = ele.ownerDocument.defaultView;
if (!view) {
view = window;
}
var rgbArr = view.getComputedStyle(ele).backgroundColor.replace(/rgba*\(/, '').replace(')', '').split(/,\s*/);
if (rgbArr.length < 3) {
return 0;
}
var colorCast = (0.213 * rgbArr[0]) + (0.715 * rgbArr[1]) + (0.072 * rgbArr[2]);
return colorCast === 0 || colorCast > 255 / 2 ? 1 : -1;
},
retry(handler, interval, times) {
if (times <= 0) {
return;
}
var next = function() {
if (--times >= 0 && !handler()) {
setTimeout(next, interval);
}
};
setTimeout(next, interval);
}
};
/**
* Handles debouncing of events via requestAnimationFrame
* @see http://www.html5rocks.com/en/tutorials/speed/animations/
* @param {Function} callback The callback to handle whichever event
*/
function Debouncer(callback) {
this.callback = callback;
this.ticking = false;
}
Debouncer.prototype = {
constructor: Debouncer,
/**
* dispatches the event to the supplied callback
* @private
*/
update: function() {
this.callback && this.callback();
this.ticking = false;
},
/**
* ensures events don't get stacked
* @private
*/
requestTick: function() {
if (!this.ticking) {
requestAnimationFrame(this.rafCallback || (this.rafCallback = this.update.bind(this)));
this.ticking = true;
}
},
/**
* Attach this as the event listeners
*/
handleEvent: function() {
this.requestTick();
}
};