2025-2-26-fixed
This commit is contained in:
16
themes/fluid/scripts/events/index.js
Normal file
16
themes/fluid/scripts/events/index.js
Normal file
@ -0,0 +1,16 @@
|
||||
/* global hexo */
|
||||
|
||||
'use strict';
|
||||
|
||||
hexo.on('generateBefore', () => {
|
||||
require('./lib/merge-configs')(hexo);
|
||||
require('./lib/compatible-configs')(hexo);
|
||||
require('./lib/injects')(hexo);
|
||||
require('./lib/highlight')(hexo);
|
||||
require('./lib/lazyload')(hexo);
|
||||
require('./lib/footnote')(hexo);
|
||||
});
|
||||
|
||||
hexo.on('generateAfter', () => {
|
||||
require('./lib/hello')(hexo);
|
||||
});
|
||||
71
themes/fluid/scripts/events/lib/compatible-configs.js
Normal file
71
themes/fluid/scripts/events/lib/compatible-configs.js
Normal file
@ -0,0 +1,71 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (hexo) => {
|
||||
const isZh = hexo.theme.i18n.languages[0].search(/zh-CN/i) !== -1;
|
||||
|
||||
// Breaking change at v1.8.3 2020/09/03
|
||||
if (hexo.theme.config.highlight && !hexo.theme.config.code) {
|
||||
if (isZh) {
|
||||
hexo.log.warn('[Fluid] 检测到弃用的配置项: "highlight" 已被修改为 "code:highlight",请根据新版本更新');
|
||||
} else {
|
||||
hexo.log.warn('[Fluid] Deprecated config detected: "highlight" has been modified to "code:highlight", please update according to the release.');
|
||||
}
|
||||
hexo.theme.config.code = {
|
||||
copy_btn : hexo.theme.config.highlight.copy_btn,
|
||||
highlight: {
|
||||
enable : hexo.theme.config.highlight.enable,
|
||||
lib : 'highlightjs',
|
||||
highlightjs: {
|
||||
style: hexo.theme.config.highlight.style
|
||||
},
|
||||
prismjs: {
|
||||
style : 'default',
|
||||
preprocess: true
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Some configs that require hexo >= 5.0
|
||||
if (parseInt(hexo.version[0], 10) < 5) {
|
||||
if (isZh) {
|
||||
hexo.log.warn('[Fluid] 检测到 Hexo 版本低于 5.0.0,部分功能可能会受影响');
|
||||
} else {
|
||||
hexo.log.warn('[Fluid] Hexo version < 5.0.0 detected, some features may not be available.');
|
||||
}
|
||||
if (hexo.theme.config.code.highlight.lib === 'prismjs' && hexo.theme.config.code.highlight.prismjs.preprocess) {
|
||||
hexo.theme.config.code.highlight.prismjs.preprocess = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Breaking change at v1.8.7 2020/12/02
|
||||
if (hexo.theme.config.index.post_default_img) {
|
||||
if (isZh) {
|
||||
hexo.log.warn('[Fluid] 检测到弃用的配置项: "index:post_default_img" 已被修改为 "post:default_index_img",请根据新版本更新');
|
||||
} else {
|
||||
hexo.log.warn('[Fluid] Deprecated config detected: "index:post_default_img" has been modified to "post:default_index_img", please update according to the release.');
|
||||
}
|
||||
hexo.theme.config.post.default_index_img = hexo.theme.config.index.post_default_img;
|
||||
}
|
||||
|
||||
// Breaking change at v1.8.7 2020/12/10
|
||||
if (hexo.theme.config.banner_parallax) {
|
||||
if (isZh) {
|
||||
hexo.log.warn('[Fluid] 检测到弃用的配置项: "banner_parallax" 已被修改为 "banner:parallax",请根据新版本更新');
|
||||
} else {
|
||||
hexo.log.warn('[Fluid] Deprecated config detected: "banner_parallax" has been modified to "banner:parallax", please update according to the release.');
|
||||
}
|
||||
if (!hexo.theme.config.banner) {
|
||||
hexo.theme.config.banner = {};
|
||||
}
|
||||
hexo.theme.config.banner.parallax = hexo.theme.config.banner_parallax;
|
||||
}
|
||||
|
||||
// Breaking change at v1.8.11 2021/05/14
|
||||
if (hexo.theme.config.valine.appid) {
|
||||
hexo.theme.config.valine.appId = hexo.theme.config.valine.appid;
|
||||
}
|
||||
if (hexo.theme.config.valine.appkey) {
|
||||
hexo.theme.config.valine.appKey = hexo.theme.config.valine.appkey;
|
||||
}
|
||||
};
|
||||
117
themes/fluid/scripts/events/lib/footnote.js
Normal file
117
themes/fluid/scripts/events/lib/footnote.js
Normal file
@ -0,0 +1,117 @@
|
||||
'use strict';
|
||||
|
||||
const { stripHTML } = require('hexo-util');
|
||||
|
||||
// Register footnotes filter
|
||||
module.exports = (hexo) => {
|
||||
const config = hexo.theme.config;
|
||||
if (config.post.footnote.enable) {
|
||||
hexo.extend.filter.register('before_post_render', (page) => {
|
||||
if (page.footnote !== false) {
|
||||
page.content = renderFootnotes(page.content, page.footnote);
|
||||
}
|
||||
return page;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Modified from https://github.com/kchen0x/hexo-reference
|
||||
*
|
||||
* Render markdown footnotes
|
||||
* @param {String} text
|
||||
* @param {String} header
|
||||
* @returns {String} text
|
||||
*/
|
||||
function renderFootnotes(text, header) {
|
||||
const reFootnoteContent = /\[\^(\d+)]: ?([\S\s]+?)(?=\[\^(?:\d+)]|\n\n|$)/g;
|
||||
const reInlineFootnote = /\[\^(\d+)]\((.+?)\)/g;
|
||||
const reFootnoteIndex = /\[\^(\d+)]/g;
|
||||
const reCodeBlock = /<pre>[\s\S]*?<\/pre>/g;
|
||||
|
||||
let footnotes = [];
|
||||
let html = '';
|
||||
let codeBlocks = [];
|
||||
|
||||
// extract code block
|
||||
text = text.replace(reCodeBlock, function(match) {
|
||||
codeBlocks.push(match);
|
||||
return 'CODE_BLOCK_PLACEHOLDER';
|
||||
});
|
||||
|
||||
// threat all inline footnotes
|
||||
text = text.replace(reInlineFootnote, function(match, index, content) {
|
||||
footnotes.push({
|
||||
index : index,
|
||||
content: content ? content.trim() : ''
|
||||
});
|
||||
// remove content of inline footnote
|
||||
return '[^' + index + ']';
|
||||
});
|
||||
|
||||
// threat all footnote contents
|
||||
text = text.replace(reFootnoteContent, function(match, index, content) {
|
||||
footnotes.push({
|
||||
index : index,
|
||||
content: content ? content.trim() : ''
|
||||
});
|
||||
// remove footnote content
|
||||
return '';
|
||||
});
|
||||
|
||||
// create map for looking footnotes array
|
||||
function createLookMap(field) {
|
||||
let map = {};
|
||||
for (let i = 0; i < footnotes.length; i++) {
|
||||
const item = footnotes[i];
|
||||
const key = item[field];
|
||||
map[key] = item;
|
||||
}
|
||||
return map;
|
||||
}
|
||||
const indexMap = createLookMap('index');
|
||||
|
||||
// render (HTML) footnotes reference
|
||||
text = text.replace(reFootnoteIndex,
|
||||
function(match, index) {
|
||||
if (!indexMap[index]) {
|
||||
return match;
|
||||
}
|
||||
const tooltip = indexMap[index].content;
|
||||
return '<sup id="fnref:' + index + '" class="footnote-ref">'
|
||||
+ '<a href="#fn:' + index + '" rel="footnote">'
|
||||
+ '<span class="hint--top hint--rounded" aria-label="'
|
||||
+ stripHTML(tooltip)
|
||||
+ '">[' + index + ']</span></a></sup>';
|
||||
});
|
||||
|
||||
// sort footnotes by their index
|
||||
footnotes.sort(function(a, b) {
|
||||
return a.index - b.index;
|
||||
});
|
||||
|
||||
// render footnotes (HTML)
|
||||
footnotes.forEach(function(item) {
|
||||
html += '<li><span id="fn:' + item.index + '" class="footnote-text">';
|
||||
html += '<span>';
|
||||
const fn = hexo.render.renderSync({ text: item.content, engine: 'markdown' });
|
||||
html += fn.replace(/(<p>)|(<\/p>)/g, '').replace(/<br>/g, '');
|
||||
html += '<a href="#fnref:' + item.index + '" rev="footnote" class="footnote-backref"> ↩</a></span></span></li>';
|
||||
});
|
||||
|
||||
// add footnotes at the end of the content
|
||||
if (footnotes.length) {
|
||||
text += '<section class="footnotes">';
|
||||
text += header || config.post.footnote.header || '';
|
||||
text += '<div class="footnote-list">';
|
||||
text += '<ol>' + html + '</ol>';
|
||||
text += '</div></section>';
|
||||
}
|
||||
|
||||
// restore code block
|
||||
text = text.replace(/CODE_BLOCK_PLACEHOLDER/g, function() {
|
||||
return codeBlocks.shift();
|
||||
});
|
||||
|
||||
return text;
|
||||
}
|
||||
};
|
||||
43
themes/fluid/scripts/events/lib/hello.js
Normal file
43
themes/fluid/scripts/events/lib/hello.js
Normal file
@ -0,0 +1,43 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (hexo) => {
|
||||
if (hexo.theme.has_hello) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (hexo.theme.i18n.languages[0].search(/zh-CN/i) !== -1) {
|
||||
hexo.log.info(`
|
||||
------------------------------------------------
|
||||
| |
|
||||
| ________ __ _ __ |
|
||||
| |_ __ |[ | (_) | ] |
|
||||
| | |_ \\_| | | __ _ __ .--.| | |
|
||||
| | _| | |[ | | | [ |/ /'\`\\' | |
|
||||
| _| |_ | | | \\_/ |, | || \\__/ | |
|
||||
| |_____| [___]'.__.'_/[___]'.__.;__] |
|
||||
| |
|
||||
| 感谢使用 Fluid 主题 |
|
||||
| 文档: https://hexo.fluid-dev.com/docs/ |
|
||||
| |
|
||||
------------------------------------------------
|
||||
`);
|
||||
} else {
|
||||
hexo.log.info(`
|
||||
------------------------------------------------
|
||||
| |
|
||||
| ________ __ _ __ |
|
||||
| |_ __ |[ | (_) | ] |
|
||||
| | |_ \\_| | | __ _ __ .--.| | |
|
||||
| | _| | |[ | | | [ |/ /'\`\\' | |
|
||||
| _| |_ | | | \\_/ |, | || \\__/ | |
|
||||
| |_____| [___]'.__.'_/[___]'.__.;__] |
|
||||
| |
|
||||
| Thank you for using Fluid theme |
|
||||
| Docs: https://hexo.fluid-dev.com/docs/en/ |
|
||||
| |
|
||||
------------------------------------------------
|
||||
`);
|
||||
}
|
||||
|
||||
hexo.theme.has_hello = true;
|
||||
};
|
||||
156
themes/fluid/scripts/events/lib/highlight.js
Normal file
156
themes/fluid/scripts/events/lib/highlight.js
Normal file
@ -0,0 +1,156 @@
|
||||
'use strict';
|
||||
|
||||
let css;
|
||||
try {
|
||||
css = require('css');
|
||||
} catch (error) {
|
||||
if (error.code === 'MODULE_NOT_FOUND') {
|
||||
css = require('@adobe/css-tools');
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const fs = require('fs');
|
||||
const objUtil = require('../../utils/object');
|
||||
const resolveModule = require('../../utils/resolve');
|
||||
|
||||
module.exports = (hexo) => {
|
||||
|
||||
function resolveHighlight(name) {
|
||||
if (!name) {
|
||||
name = 'github-gist';
|
||||
}
|
||||
const cssName = name.toLowerCase().replace(/([^0-9])\s+?([^0-9])/g, '$1-$2').replace(/\s/g, '');
|
||||
let file = resolveModule('highlight.js', `styles/${cssName}.css`);
|
||||
if (cssName === 'github-gist' && !fs.existsSync(file)) {
|
||||
file = resolveModule('highlight.js', 'styles/github.css');
|
||||
}
|
||||
let backgroundColor;
|
||||
if (fs.existsSync(file)) {
|
||||
const content = fs.readFileSync(file, 'utf8');
|
||||
css.parse(content).stylesheet.rules
|
||||
.filter(rule => rule.type === 'rule' && rule.selectors.some(selector => selector.endsWith('.hljs')))
|
||||
.flatMap(rule => rule.declarations)
|
||||
.forEach(declaration => {
|
||||
if (declaration.property === 'background' || declaration.property === 'background-color') {
|
||||
backgroundColor = declaration.value;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
hexo.log.error(`[Fluid] highlightjs style '${name}' not found`);
|
||||
return {};
|
||||
}
|
||||
if (backgroundColor === 'white' || backgroundColor === '#ffffff') {
|
||||
backgroundColor = '#fff';
|
||||
}
|
||||
return { file, backgroundColor };
|
||||
}
|
||||
|
||||
function resolvePrism(name) {
|
||||
if (!name) {
|
||||
name = 'default';
|
||||
}
|
||||
let cssName = name.toLowerCase().replace(/[\s-]/g, '');
|
||||
if (cssName === 'prism' || cssName === 'default') {
|
||||
cssName = '';
|
||||
} else if (cssName === 'tomorrownight') {
|
||||
cssName = 'tomorrow';
|
||||
}
|
||||
let file = resolveModule('prismjs', `themes/${cssName ? 'prism-' + cssName : 'prism'}.css`);
|
||||
if (!fs.existsSync(file)) {
|
||||
file = resolveModule('prism-themes', `themes/${cssName}.css`);
|
||||
}
|
||||
if (!fs.existsSync(file)) {
|
||||
hexo.log.error(`[Fluid] prismjs style '${name}' not found`);
|
||||
return {};
|
||||
}
|
||||
return { file };
|
||||
}
|
||||
|
||||
const config = hexo.theme.config;
|
||||
if (!config.code || !config.code.highlight.enable) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (config.code.highlight.lib === 'highlightjs') {
|
||||
// Force set hexo config
|
||||
hexo.config.prismjs = objUtil.merge({}, hexo.config.prismjs, {
|
||||
enable: false
|
||||
});
|
||||
hexo.config.highlight = objUtil.merge({}, hexo.config.highlight, {
|
||||
enable : true,
|
||||
hljs : true,
|
||||
wrap : false,
|
||||
auto_detect: true,
|
||||
line_number: config.code.highlight.line_number || false
|
||||
});
|
||||
hexo.config.syntax_highlighter = 'highlight.js'; // hexo v7.0.0+ config
|
||||
hexo.theme.config.code.highlight.highlightjs = objUtil.merge({}, hexo.theme.config.code.highlight.highlightjs, {
|
||||
light: resolveHighlight(hexo.theme.config.code.highlight.highlightjs.style),
|
||||
dark : hexo.theme.config.dark_mode.enable && resolveHighlight(hexo.theme.config.code.highlight.highlightjs.style_dark)
|
||||
});
|
||||
|
||||
hexo.extend.filter.register('after_post_render', (page) => {
|
||||
if (hexo.config.highlight.line_number) {
|
||||
page.content = page.content.replace(/<figure[^>]+?highlight.+?<td[^>]+?code[^>]+?>.*?(<pre.+?<\/pre>).+?<\/figure>/gims, (str, p1) => {
|
||||
if (/<code[^>]+?mermaid[^>]+?>/ims.test(p1)) {
|
||||
return p1.replace(/(class="[^>]*?)hljs([^>]*?")/gims, '$1$2').replace(/<br>/gims, '\n');
|
||||
}
|
||||
return str;
|
||||
});
|
||||
} else {
|
||||
page.content = page.content.replace(/(?<!<div class="code-wrapper">)<pre.+?<\/pre>/gims, (str) => {
|
||||
if (/<code[^>]+?mermaid[^>]+?>/ims.test(str)) {
|
||||
return str.replace(/(class=".*?)hljs(.*?")/gims, '$1$2');
|
||||
}
|
||||
return `<div class="code-wrapper">${str}</div>`;
|
||||
});
|
||||
}
|
||||
|
||||
// 适配缩进型代码块
|
||||
page.content = page.content.replace(/<pre><code>/gims, (str) => {
|
||||
return '<pre><code class="hljs">';
|
||||
});
|
||||
|
||||
return page;
|
||||
});
|
||||
} else if (config.code.highlight.lib === 'prismjs') {
|
||||
// Force set hexo config
|
||||
hexo.config.highlight = objUtil.merge({}, hexo.config.highlight, {
|
||||
enable: false
|
||||
});
|
||||
hexo.config.prismjs = objUtil.merge({}, hexo.config.prismjs, {
|
||||
enable : true,
|
||||
preprocess : config.code.highlight.prismjs.preprocess || false,
|
||||
line_number: config.code.highlight.line_number || false
|
||||
});
|
||||
hexo.config.syntax_highlighter = 'prismjs'; // hexo v7.0.0+ config
|
||||
hexo.theme.config.code.highlight.prismjs = objUtil.merge({}, hexo.theme.config.code.highlight.prismjs, {
|
||||
light: resolvePrism(hexo.theme.config.code.highlight.prismjs.style),
|
||||
dark : hexo.theme.config.dark_mode.enable && resolvePrism(hexo.theme.config.code.highlight.prismjs.style_dark)
|
||||
});
|
||||
|
||||
hexo.extend.filter.register('after_post_render', (page) => {
|
||||
page.content = page.content.replace(/(?<!<div class="code-wrapper">)<pre.+?<\/pre>/gims, (str) => {
|
||||
if (/<code[^>]+?mermaid[^>]+?>/ims.test(str)) {
|
||||
if (hexo.config.highlight.line_number) {
|
||||
str = str.replace(/<span[^>]+?line-numbers-rows[^>]+?>.+?<\/span><\/code>/, '</code>');
|
||||
}
|
||||
str = str.replace(/<pre[^>]*?>/gims, '<pre>')
|
||||
.replace(/(class=".*?)language-mermaid(.*?")/gims, '$1mermaid$2')
|
||||
.replace(/<span[^>]+?>(.+?)<\/span>/gims, '$1');
|
||||
return str;
|
||||
}
|
||||
return `<figure><div class="code-wrapper">${str}</div></figure>`;
|
||||
});
|
||||
|
||||
// 适配缩进型代码块
|
||||
page.content = page.content.replace(/<pre><code>/gims, (str) => {
|
||||
return '<pre class="language-none"><code class="language-none">';
|
||||
});
|
||||
|
||||
return page;
|
||||
});
|
||||
}
|
||||
};
|
||||
118
themes/fluid/scripts/events/lib/injects.js
Normal file
118
themes/fluid/scripts/events/lib/injects.js
Normal file
@ -0,0 +1,118 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Modified from https://github.com/next-theme/hexo-theme-next/blob/master/scripts/events/lib/injects.js
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const defaultExtname = '.ejs';
|
||||
|
||||
// Defining stylus types
|
||||
class StylusInject {
|
||||
|
||||
constructor(base_dir) {
|
||||
this.base_dir = base_dir;
|
||||
this.files = [];
|
||||
}
|
||||
|
||||
push(file) {
|
||||
// Get absolute path base on hexo dir
|
||||
this.files.push(path.resolve(this.base_dir, file));
|
||||
}
|
||||
}
|
||||
|
||||
// Defining view types
|
||||
class ViewInject {
|
||||
|
||||
constructor(base_dir) {
|
||||
this.base_dir = base_dir;
|
||||
this.raws = [];
|
||||
}
|
||||
|
||||
raw(name, raw, ...args) {
|
||||
// Set default extname
|
||||
if (path.extname(name) === '') {
|
||||
name += defaultExtname;
|
||||
}
|
||||
this.raws.push({ name, raw, args });
|
||||
}
|
||||
|
||||
file(name, file, ...args) {
|
||||
// Set default extname from file's extname
|
||||
if (path.extname(name) === '') {
|
||||
name += path.extname(file);
|
||||
}
|
||||
// Get absolute path base on hexo dir
|
||||
this.raw(name, fs.readFileSync(path.resolve(this.base_dir, file), 'utf8'), ...args);
|
||||
}
|
||||
}
|
||||
|
||||
const points = {
|
||||
views: [
|
||||
'head',
|
||||
'header',
|
||||
'bodyBegin',
|
||||
'bodyEnd',
|
||||
'footer',
|
||||
'postMetaTop',
|
||||
'postMetaBottom',
|
||||
'postMarkdownBegin',
|
||||
'postMarkdownEnd',
|
||||
'postLeft',
|
||||
'postRight',
|
||||
'postCopyright',
|
||||
'postComments',
|
||||
'pageComments',
|
||||
'linksComments'
|
||||
],
|
||||
styles: [
|
||||
'variable',
|
||||
'mixin',
|
||||
'style'
|
||||
]
|
||||
};
|
||||
|
||||
// Init injects
|
||||
function initInject(base_dir) {
|
||||
const injects = {};
|
||||
points.styles.forEach(item => {
|
||||
injects[item] = new StylusInject(base_dir);
|
||||
});
|
||||
points.views.forEach(item => {
|
||||
injects[item] = new ViewInject(base_dir);
|
||||
});
|
||||
return injects;
|
||||
}
|
||||
|
||||
module.exports = (hexo) => {
|
||||
// Exec theme_inject filter
|
||||
const injects = initInject(hexo.base_dir);
|
||||
hexo.execFilterSync('theme_inject', injects);
|
||||
hexo.theme.config.injects = {};
|
||||
|
||||
// Inject stylus
|
||||
points.styles.forEach(type => {
|
||||
hexo.theme.config.injects[type] = injects[type].files;
|
||||
});
|
||||
|
||||
// Inject views
|
||||
points.views.forEach(type => {
|
||||
const configs = Object.create(null);
|
||||
hexo.theme.config.injects[type] = [];
|
||||
// Add or override view.
|
||||
injects[type].raws.forEach((injectObj, index) => {
|
||||
const name = `inject/${type}/${injectObj.name}`;
|
||||
hexo.theme.setView(name, injectObj.raw);
|
||||
configs[name] = {
|
||||
layout : name,
|
||||
locals : injectObj.args[0],
|
||||
options: injectObj.args[1],
|
||||
order : injectObj.args[2] || index
|
||||
};
|
||||
});
|
||||
// Views sort.
|
||||
hexo.theme.config.injects[type] = Object.values(configs)
|
||||
.sort((x, y) => x.order - y.order);
|
||||
});
|
||||
};
|
||||
50
themes/fluid/scripts/events/lib/lazyload.js
Normal file
50
themes/fluid/scripts/events/lib/lazyload.js
Normal file
@ -0,0 +1,50 @@
|
||||
'use strict';
|
||||
|
||||
const urlJoin = require('../../utils/url-join');
|
||||
|
||||
module.exports = (hexo) => {
|
||||
const config = hexo.theme.config;
|
||||
const loadingImage = urlJoin(hexo.config.root, config.lazyload.loading_img
|
||||
|| urlJoin(config.static_prefix.internal_img, 'loading.gif'));
|
||||
if (!config.lazyload || !config.lazyload.enable || !loadingImage) {
|
||||
return;
|
||||
}
|
||||
if (config.lazyload.onlypost) {
|
||||
hexo.extend.filter.register('after_post_render', (page) => {
|
||||
if (page.layout !== 'post' && !page.lazyload) {
|
||||
return;
|
||||
}
|
||||
if (page.lazyload !== false) {
|
||||
page.content = lazyImages(page.content, loadingImage);
|
||||
page.content = lazyComments(page.content);
|
||||
}
|
||||
return page;
|
||||
});
|
||||
} else {
|
||||
hexo.extend.filter.register('after_render:html', (html, data) => {
|
||||
if (!data.page || data.page.lazyload !== false) {
|
||||
html = lazyImages(html, loadingImage);
|
||||
html = lazyComments(html);
|
||||
return html;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const lazyImages = (htmlContent, loadingImage) => {
|
||||
return htmlContent.replace(/<img[^>]+?src=(".*?")[^>]*?>/gims, (str, p1) => {
|
||||
if (/lazyload/i.test(str)) {
|
||||
return str;
|
||||
}
|
||||
return str.replace(p1, `${p1} srcset="${loadingImage}" lazyload`);
|
||||
});
|
||||
};
|
||||
|
||||
const lazyComments = (htmlContent) => {
|
||||
return htmlContent.replace(/<[^>]+?id="comments"[^>]*?>/gims, (str) => {
|
||||
if (/lazyload/i.test(str)) {
|
||||
return str;
|
||||
}
|
||||
return str.replace('id="comments"', 'id="comments" lazyload');
|
||||
});
|
||||
};
|
||||
93
themes/fluid/scripts/events/lib/merge-configs.js
Normal file
93
themes/fluid/scripts/events/lib/merge-configs.js
Normal file
@ -0,0 +1,93 @@
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const objUtil = require('../../utils/object');
|
||||
const { isNotEmptyObject } = require('../../utils/object');
|
||||
|
||||
module.exports = (hexo) => {
|
||||
const isZh = hexo.theme.i18n.languages[0].search(/zh-CN/i) !== -1;
|
||||
|
||||
let dataConfig = {};
|
||||
let dataStaticConfig = {};
|
||||
|
||||
if (hexo.locals.get instanceof Function) {
|
||||
const data = hexo.locals.get('data');
|
||||
if (data && isNotEmptyObject(data.fluid_config)) {
|
||||
dataConfig = data.fluid_config;
|
||||
} else if (!configFromRoot(hexo)) {
|
||||
if (isZh) {
|
||||
hexo.log.warn('[Fluid] 推荐你使用覆盖配置功能: https://hexo.fluid-dev.com/docs/guide/#%E8%A6%86%E7%9B%96%E9%85%8D%E7%BD%AE');
|
||||
} else {
|
||||
hexo.log.warn('[Fluid] It is recommended that you use override configuration: https://hexo.fluid-dev.com/docs/en/guide/#override-configuration');
|
||||
}
|
||||
}
|
||||
if (data && isNotEmptyObject(data.fluid_static_prefix)) {
|
||||
dataStaticConfig = data.fluid_static_prefix;
|
||||
}
|
||||
|
||||
const { language } = hexo.config;
|
||||
const { i18n } = hexo.theme;
|
||||
const langConfigMap = {};
|
||||
for (const key in data) {
|
||||
if (Object.prototype.hasOwnProperty.call(data, key)) {
|
||||
if (/^languages\/.+$/.test(key)) {
|
||||
langConfigMap[key.replace('languages/', '')] = data[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isNotEmptyObject(langConfigMap)) {
|
||||
const mergeLang = (lang) => {
|
||||
if (langConfigMap[lang]) {
|
||||
i18n.set(lang, objUtil.merge({}, i18n.get([lang]), langConfigMap[lang]));
|
||||
}
|
||||
};
|
||||
if (Array.isArray(language)) {
|
||||
for (const lang of language) {
|
||||
mergeLang(lang);
|
||||
}
|
||||
} else {
|
||||
mergeLang(language);
|
||||
}
|
||||
if (isZh) {
|
||||
hexo.log.debug('[Fluid] 读取 source/_data/languages/*.yml 文件覆盖语言配置');
|
||||
} else {
|
||||
hexo.log.debug('[Fluid] Merge language config from source/_data/languages/*.yml');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isNotEmptyObject(hexo.config.theme_config)) {
|
||||
hexo.theme.config = objUtil.merge({}, hexo.theme.config, hexo.config.theme_config);
|
||||
if (isZh) {
|
||||
hexo.log.debug('[Fluid] 读取 _config.yml 中 theme_config 配置项覆盖主题配置');
|
||||
} else {
|
||||
hexo.log.debug('[Fluid] Merge theme config from theme_config in _config.yml');
|
||||
}
|
||||
}
|
||||
|
||||
if (isNotEmptyObject(dataStaticConfig)) {
|
||||
hexo.theme.config.static_prefix = objUtil.merge({}, hexo.theme.config.static_prefix, dataStaticConfig);
|
||||
if (isZh) {
|
||||
hexo.log.debug('[Fluid] 读取 source/_data/fluid_static_prefix.yml 文件覆盖主题配置');
|
||||
} else {
|
||||
hexo.log.debug('[Fluid] Merge theme config from source/_data/fluid_static_prefix.yml');
|
||||
}
|
||||
}
|
||||
|
||||
if (isNotEmptyObject(dataConfig)) {
|
||||
hexo.theme.config = objUtil.merge({}, hexo.theme.config, dataConfig);
|
||||
if (isZh) {
|
||||
hexo.log.debug('[Fluid] 读取 source/_data/fluid_config.yml 文件覆盖主题配置');
|
||||
} else {
|
||||
hexo.log.debug('[Fluid] Merge theme config from source/_data/fluid_config.yml');
|
||||
}
|
||||
}
|
||||
|
||||
hexo.log.debug('[Fluid] Output theme config:\n', JSON.stringify(hexo.theme.config, undefined, 2));
|
||||
};
|
||||
|
||||
const configFromRoot = (hexo) => {
|
||||
const configPath = path.join(hexo.base_dir, '_config.fluid.yml');
|
||||
return fs.existsSync(configPath);
|
||||
};
|
||||
27
themes/fluid/scripts/filters/default-injects.js
Normal file
27
themes/fluid/scripts/filters/default-injects.js
Normal file
@ -0,0 +1,27 @@
|
||||
/* global hexo */
|
||||
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
|
||||
hexo.extend.filter.register('theme_inject', function(injects) {
|
||||
injects.header.file('default', path.join(hexo.theme_dir, 'layout/_partials/header.ejs'));
|
||||
injects.footer.file('default', path.join(hexo.theme_dir, 'layout/_partials/footer.ejs'));
|
||||
|
||||
injects.postLeft.file('default', path.join(hexo.theme_dir, 'layout/_partials/post/sidebar-left.ejs'));
|
||||
injects.postRight.file('default', path.join(hexo.theme_dir, 'layout/_partials/post/sidebar-right.ejs'));
|
||||
injects.postMetaTop.file('default', path.join(hexo.theme_dir, 'layout/_partials/post/meta-top.ejs'));
|
||||
injects.postMetaBottom.file('default', path.join(hexo.theme_dir, 'layout/_partials/post/meta-bottom.ejs'));
|
||||
if (hexo.theme.config.post.copyright.enable) {
|
||||
injects.postCopyright.file('default', path.join(hexo.theme_dir, 'layout/_partials/post/copyright.ejs'));
|
||||
}
|
||||
if (hexo.theme.config.post.comments.enable) {
|
||||
injects.postComments.file('default', path.join(hexo.theme_dir, 'layout/_partials/comments.ejs'));
|
||||
}
|
||||
|
||||
injects.pageComments.file('default', path.join(hexo.theme_dir, 'layout/_partials/comments.ejs'));
|
||||
|
||||
if (hexo.theme.config.links.comments.enable) {
|
||||
injects.linksComments.file('default', path.join(hexo.theme_dir, 'layout/_partials/comments.ejs'));
|
||||
}
|
||||
}, -99);
|
||||
20
themes/fluid/scripts/filters/locals.js
Normal file
20
themes/fluid/scripts/filters/locals.js
Normal file
@ -0,0 +1,20 @@
|
||||
/* global hexo */
|
||||
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
|
||||
hexo.extend.filter.register('template_locals', locals => {
|
||||
const { env, config } = hexo;
|
||||
const { __ } = locals;
|
||||
const { i18n } = hexo.theme;
|
||||
locals.hexo_version = env.version;
|
||||
locals.fluid_version = require(path.normalize(path.join(hexo.theme_dir, 'package.json'))).version;
|
||||
locals.title = __('title') !== 'title' ? __('title') : config.title;
|
||||
locals.subtitle = __('subtitle') !== 'subtitle' ? __('subtitle') : config.subtitle;
|
||||
locals.author = __('author') !== 'author' ? __('author') : config.author;
|
||||
locals.description = __('description') !== 'description' ? __('description') : config.description;
|
||||
locals.languages = [...i18n.languages];
|
||||
locals.languages.splice(locals.languages.indexOf('default'), 1);
|
||||
locals.page.lang = locals.page.lang || locals.page.language;
|
||||
});
|
||||
55
themes/fluid/scripts/filters/post-filter.js
Normal file
55
themes/fluid/scripts/filters/post-filter.js
Normal file
@ -0,0 +1,55 @@
|
||||
/* global hexo */
|
||||
|
||||
'use strict';
|
||||
|
||||
// 生成前过滤文章
|
||||
hexo.extend.filter.register('before_generate', function() {
|
||||
this._bindLocals();
|
||||
|
||||
const allPages = this.locals.get('pages');
|
||||
allPages.data.map((page) => {
|
||||
if (page.comment !== true) {
|
||||
page.comments = typeof page.comment === 'string' && page.comment !== '';
|
||||
} else {
|
||||
page.comments = true;
|
||||
}
|
||||
return page;
|
||||
});
|
||||
this.locals.set('pages', allPages);
|
||||
|
||||
const allPosts = this.locals.get('posts');
|
||||
allPosts.data.map((post) => {
|
||||
if (post.comment === false) {
|
||||
post.comments = false;
|
||||
}
|
||||
return post;
|
||||
});
|
||||
const hidePosts = allPosts.filter(post => post.hide);
|
||||
const normalPosts = allPosts.filter(post => !post.hide);
|
||||
const indexPost = allPosts.filter(post => !post.hide && !post.archive)
|
||||
|
||||
this.locals.set('all_posts', allPosts);
|
||||
this.locals.set('hide_posts', hidePosts);
|
||||
this.locals.set('posts', normalPosts);
|
||||
this.locals.set('index_posts', indexPost);
|
||||
});
|
||||
|
||||
const original_post_generator = hexo.extend.generator.get('post');
|
||||
|
||||
hexo.extend.generator.register('post', function(locals) {
|
||||
// 发送时需要把过滤的页面也加入
|
||||
return original_post_generator.bind(this)({
|
||||
posts: new locals.posts.constructor(
|
||||
locals.posts.data.concat(locals.hide_posts.data)
|
||||
)
|
||||
});
|
||||
});
|
||||
|
||||
// 渲染文章后的过滤
|
||||
hexo.extend.filter.register('after_post_render', (page) => {
|
||||
// 移除 hexo-renderer-pandoc 生成的 <colgroup>
|
||||
page.content = page.content.replace(/<colgroup>.+?<\/colgroup>/gims, '');
|
||||
// 移除 hexo-renderer-pandoc 生成的 <span class="footnote-text">...<br>...</span>
|
||||
page.content = page.content.replace(/(class="footnote-text".+?)<br.+?>(.+?rev="footnote")/gims, '$1$2');
|
||||
return page;
|
||||
});
|
||||
22
themes/fluid/scripts/generators/index-generator.js
Normal file
22
themes/fluid/scripts/generators/index-generator.js
Normal file
@ -0,0 +1,22 @@
|
||||
'use strict';
|
||||
|
||||
const pagination = require('hexo-pagination');
|
||||
|
||||
module.exports = function(locals) {
|
||||
const config = this.config;
|
||||
const posts = locals.index_posts.sort(config.index_generator.order_by);
|
||||
|
||||
posts.data.sort((a, b) => (b.sticky || 0) - (a.sticky || 0));
|
||||
|
||||
const paginationDir = config.pagination_dir || 'page';
|
||||
const path = config.index_generator.path || '';
|
||||
|
||||
return pagination(path, posts, {
|
||||
perPage: config.index_generator.per_page,
|
||||
layout: 'index',
|
||||
format: paginationDir + '/%d/',
|
||||
data: {
|
||||
__index: true
|
||||
}
|
||||
});
|
||||
};
|
||||
68
themes/fluid/scripts/generators/local-search.js
Normal file
68
themes/fluid/scripts/generators/local-search.js
Normal file
@ -0,0 +1,68 @@
|
||||
/* global hexo */
|
||||
|
||||
'use strict';
|
||||
|
||||
hexo.extend.generator.register('_hexo_generator_search', function(locals) {
|
||||
const config = this.theme.config;
|
||||
if (!config.search.enable) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nunjucks = require('nunjucks');
|
||||
const env = new nunjucks.Environment();
|
||||
const pathFn = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
env.addFilter('uriencode', function(str) {
|
||||
return encodeURI(str);
|
||||
});
|
||||
|
||||
env.addFilter('noControlChars', function(str) {
|
||||
// eslint-disable-next-line no-control-regex
|
||||
return str && str.replace(/[\x00-\x1F\x7F]/g, '');
|
||||
});
|
||||
|
||||
env.addFilter('urlJoin', function(str) {
|
||||
const base = str[0];
|
||||
const relative = str[1];
|
||||
return relative
|
||||
? base.replace(/\/+$/, '') + '/' + relative.replace(/^\/+/, '')
|
||||
: base;
|
||||
});
|
||||
|
||||
const searchTmplSrc = pathFn.join(hexo.theme_dir, './source/xml/local-search.xml');
|
||||
const searchTmpl = nunjucks.compile(fs.readFileSync(searchTmplSrc, 'utf8'), env);
|
||||
|
||||
const searchConfig = config.search;
|
||||
let searchField = searchConfig.field;
|
||||
const content = searchConfig.content && true;
|
||||
|
||||
let posts, pages;
|
||||
|
||||
if (searchField.trim() !== '') {
|
||||
searchField = searchField.trim();
|
||||
if (searchField === 'post') {
|
||||
posts = locals.posts.sort('-date');
|
||||
} else if (searchField === 'page') {
|
||||
pages = locals.pages;
|
||||
} else {
|
||||
posts = locals.posts.sort('-date');
|
||||
pages = locals.pages;
|
||||
}
|
||||
} else {
|
||||
posts = locals.posts.sort('-date');
|
||||
}
|
||||
|
||||
const xml = searchTmpl.render({
|
||||
config : config,
|
||||
posts : posts,
|
||||
pages : pages,
|
||||
content: content,
|
||||
url : hexo.config.root
|
||||
});
|
||||
|
||||
return {
|
||||
path: searchConfig.generate_path || '/local-search.xml',
|
||||
data: xml
|
||||
};
|
||||
});
|
||||
55
themes/fluid/scripts/generators/pages.js
Normal file
55
themes/fluid/scripts/generators/pages.js
Normal file
@ -0,0 +1,55 @@
|
||||
/* global hexo */
|
||||
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// generate 404 page
|
||||
if (!fs.existsSync(path.join(hexo.source_dir, '404.html'))) {
|
||||
hexo.extend.generator.register('_404', function(locals) {
|
||||
if (this.theme.config.page404.enable !== false) {
|
||||
return {
|
||||
path : '404.html',
|
||||
data : locals.theme,
|
||||
layout: '404'
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// generate tags Page
|
||||
hexo.extend.generator.register('_tags', function(locals) {
|
||||
if (this.theme.config.tag.enable !== false) {
|
||||
return {
|
||||
path : 'tags/index.html',
|
||||
data : locals.theme,
|
||||
layout: 'tags'
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// generate categories Page
|
||||
hexo.extend.generator.register('_categories', function(locals) {
|
||||
if (this.theme.config.category.enable !== false) {
|
||||
return {
|
||||
path : 'categories/index.html',
|
||||
data : locals.theme,
|
||||
layout: 'categories'
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// generate links page
|
||||
hexo.extend.generator.register('_links', function(locals) {
|
||||
if (this.theme.config.links.enable !== false) {
|
||||
return {
|
||||
path : 'links/index.html',
|
||||
data : locals.theme,
|
||||
layout: 'links'
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// generate index page
|
||||
hexo.extend.generator.register('index', require('./index-generator'));
|
||||
25
themes/fluid/scripts/helpers/date.js
Normal file
25
themes/fluid/scripts/helpers/date.js
Normal file
@ -0,0 +1,25 @@
|
||||
/* global hexo */
|
||||
|
||||
'use strict';
|
||||
|
||||
const moment = require('moment');
|
||||
const { isMoment } = moment;
|
||||
|
||||
hexo.extend.helper.register('compare_date', function(date1, date2) {
|
||||
if (!date1) {
|
||||
return -1;
|
||||
}
|
||||
if (!date2) {
|
||||
return 1;
|
||||
}
|
||||
const m1 = isMoment(date1) ? date1 : moment(date1);
|
||||
const m2 = isMoment(date2) ? date2 : moment(date2);
|
||||
const diff = m1.diff(m2);
|
||||
if (diff < 0) {
|
||||
return -1;
|
||||
} else if (diff > 0) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
9
themes/fluid/scripts/helpers/engine.js
Normal file
9
themes/fluid/scripts/helpers/engine.js
Normal file
@ -0,0 +1,9 @@
|
||||
/* global hexo */
|
||||
|
||||
'use strict';
|
||||
|
||||
hexo.extend.helper.register('inject_point', function(point) {
|
||||
return this.theme.injects[point]
|
||||
.map(item => this.partial(item.layout, item.locals, item.options))
|
||||
.join('');
|
||||
});
|
||||
40
themes/fluid/scripts/helpers/export-config.js
Normal file
40
themes/fluid/scripts/helpers/export-config.js
Normal file
@ -0,0 +1,40 @@
|
||||
/* global hexo */
|
||||
|
||||
'use strict';
|
||||
|
||||
const url = require('url');
|
||||
const urlJoin = require('../utils/url-join');
|
||||
|
||||
/**
|
||||
* Export theme config to js
|
||||
*/
|
||||
hexo.extend.helper.register('export_config', function () {
|
||||
let { config, theme, fluid_version } = this;
|
||||
const exportConfig = {
|
||||
hostname: url.parse(config.url).hostname || config.url,
|
||||
root: config.root,
|
||||
version: fluid_version,
|
||||
typing: theme.fun_features.typing,
|
||||
anchorjs: theme.fun_features.anchorjs,
|
||||
progressbar: theme.fun_features.progressbar,
|
||||
code_language: theme.code.language,
|
||||
copy_btn: theme.code.copy_btn,
|
||||
image_caption: theme.post.image_caption,
|
||||
image_zoom: theme.post.image_zoom,
|
||||
toc: theme.post.toc,
|
||||
lazyload: theme.lazyload,
|
||||
web_analytics: theme.web_analytics,
|
||||
search_path: urlJoin(config.root, theme.search.path),
|
||||
include_content_in_search: theme.search.content,
|
||||
};
|
||||
return `<script id="fluid-configs">
|
||||
var Fluid = window.Fluid || {};
|
||||
Fluid.ctx = Object.assign({}, Fluid.ctx)
|
||||
var CONFIG = ${JSON.stringify(exportConfig)};
|
||||
|
||||
if (CONFIG.web_analytics.follow_dnt) {
|
||||
var dntVal = navigator.doNotTrack || window.doNotTrack || navigator.msDoNotTrack;
|
||||
Fluid.ctx.dnt = dntVal && (dntVal.startsWith('1') || dntVal.startsWith('yes') || dntVal.startsWith('on'));
|
||||
}
|
||||
</script>`;
|
||||
});
|
||||
24
themes/fluid/scripts/helpers/import.js
Normal file
24
themes/fluid/scripts/helpers/import.js
Normal file
@ -0,0 +1,24 @@
|
||||
/* global hexo */
|
||||
|
||||
'use strict';
|
||||
|
||||
hexo.extend.helper.register('import_js', function(base, relative, ex = '') {
|
||||
if (!Array.isArray(this.page.script_snippets)) {
|
||||
this.page.script_snippets = [];
|
||||
}
|
||||
this.page.script_snippets.push(this.js_ex(base, relative, ex));
|
||||
});
|
||||
|
||||
hexo.extend.helper.register('import_script', function(snippet) {
|
||||
if (!Array.isArray(this.page.script_snippets)) {
|
||||
this.page.script_snippets = [];
|
||||
}
|
||||
this.page.script_snippets.push(snippet);
|
||||
});
|
||||
|
||||
hexo.extend.helper.register('import_css', function(base, relative, ex = '') {
|
||||
if (!Array.isArray(this.page.css_snippets)) {
|
||||
this.page.css_snippets = [];
|
||||
}
|
||||
this.page.css_snippets.push(this.css_ex(base, relative, ex));
|
||||
});
|
||||
7
themes/fluid/scripts/helpers/injects.js
Normal file
7
themes/fluid/scripts/helpers/injects.js
Normal file
@ -0,0 +1,7 @@
|
||||
/* global hexo */
|
||||
|
||||
'use strict';
|
||||
|
||||
hexo.extend.helper.register('point_injected', function(type) {
|
||||
return hexo.theme.config.injects[type] && hexo.theme.config.injects[type].length > 0;
|
||||
});
|
||||
25
themes/fluid/scripts/helpers/page.js
Normal file
25
themes/fluid/scripts/helpers/page.js
Normal file
@ -0,0 +1,25 @@
|
||||
/* global hexo */
|
||||
|
||||
'use strict';
|
||||
|
||||
hexo.extend.helper.register('prev_post', function prev_post(post) {
|
||||
const prev = post.prev;
|
||||
if (!prev) {
|
||||
return null;
|
||||
}
|
||||
if (prev.hide) {
|
||||
return prev_post(prev);
|
||||
}
|
||||
return prev;
|
||||
});
|
||||
|
||||
hexo.extend.helper.register('next_post', function next_post(post) {
|
||||
const next = post.next;
|
||||
if (!next) {
|
||||
return null;
|
||||
}
|
||||
if (next.hide) {
|
||||
return next_post(next);
|
||||
}
|
||||
return next;
|
||||
});
|
||||
50
themes/fluid/scripts/helpers/scope.js
Normal file
50
themes/fluid/scripts/helpers/scope.js
Normal file
@ -0,0 +1,50 @@
|
||||
/* global hexo */
|
||||
|
||||
'use strict';
|
||||
|
||||
const pageInScope = (page, scope) => {
|
||||
switch (scope) {
|
||||
case 'home':
|
||||
return Boolean(page.__index);
|
||||
case 'post':
|
||||
case 'posts':
|
||||
return Boolean(page.__post);
|
||||
case 'archives':
|
||||
case 'archive':
|
||||
return Boolean(page.archive);
|
||||
case 'categories':
|
||||
case 'category':
|
||||
return page.layout === 'categories' || page.layout === 'category';
|
||||
case 'tags':
|
||||
case 'tag':
|
||||
return page.layout === 'tags' || page.layout === 'tag';
|
||||
case 'about':
|
||||
return page.layout === 'about';
|
||||
case 'links':
|
||||
case 'link':
|
||||
return page.layout === 'links';
|
||||
case '404':
|
||||
return page.layout === '404';
|
||||
case 'page':
|
||||
case 'custom':
|
||||
return Boolean(page.__page);
|
||||
}
|
||||
};
|
||||
|
||||
hexo.extend.helper.register('in_scope', function(scope) {
|
||||
if (!scope || scope.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Array.isArray(scope)) {
|
||||
for (const each of scope) {
|
||||
if (pageInScope(this.page, each)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return pageInScope(this.page, scope);
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
17
themes/fluid/scripts/helpers/url.js
Normal file
17
themes/fluid/scripts/helpers/url.js
Normal file
@ -0,0 +1,17 @@
|
||||
/* global hexo */
|
||||
|
||||
'use strict';
|
||||
|
||||
const urlJoin = require('../utils/url-join');
|
||||
|
||||
hexo.extend.helper.register('css_ex', function(base, relative, ex = '') {
|
||||
return `<link ${ex} rel="stylesheet" href="${this.url_for(urlJoin(base, relative))}" />`;
|
||||
});
|
||||
|
||||
hexo.extend.helper.register('js_ex', function(base, relative, ex = '') {
|
||||
return `<script ${ex} src="${this.url_for(urlJoin(base, relative))}" ></script>`;
|
||||
});
|
||||
|
||||
hexo.extend.helper.register('url_join', function(base, relative) {
|
||||
return this.url_for(urlJoin(base, relative));
|
||||
});
|
||||
29
themes/fluid/scripts/helpers/utils.js
Normal file
29
themes/fluid/scripts/helpers/utils.js
Normal file
@ -0,0 +1,29 @@
|
||||
/* global hexo */
|
||||
|
||||
'use strict';
|
||||
|
||||
const md5 = require('../utils/crypto');
|
||||
const { decodeURL } = require('hexo-util');
|
||||
const compareVersions = require('../../scripts/utils/compare-versions');
|
||||
|
||||
hexo.extend.helper.register('md5', function(string) {
|
||||
return md5(string);
|
||||
});
|
||||
|
||||
hexo.extend.helper.register('require_version', function(current, require) {
|
||||
const verRe = current.match(/[@/](\d{1,2})\.?(\d{0,2})\.?(\d{0,2})/);
|
||||
if (verRe && verRe.length >= 4) {
|
||||
const ver = `${verRe[1]}.${verRe[2] || 'x'}.${verRe[3] || 'x'}`;
|
||||
return compareVersions(ver, require) >= 0;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
hexo.extend.helper.register('deduplicate', function(arr) {
|
||||
if (!Array.isArray(arr)) {
|
||||
return [];
|
||||
}
|
||||
return [...new Set(arr)];
|
||||
});
|
||||
|
||||
hexo.extend.helper.register('decode_url', decodeURL);
|
||||
42
themes/fluid/scripts/helpers/wordcount.js
Normal file
42
themes/fluid/scripts/helpers/wordcount.js
Normal file
@ -0,0 +1,42 @@
|
||||
/* global hexo */
|
||||
|
||||
'use strict';
|
||||
|
||||
const { stripHTML } = require('hexo-util');
|
||||
|
||||
const getWordCount = (post) => {
|
||||
// post.origin is the original post content of hexo-blog-encrypt
|
||||
const content = stripHTML(post.origin || post.content).replace(/\r?\n|\r/g, '').replace(/\s+/g, '');
|
||||
|
||||
if (!post.wordcount) {
|
||||
const zhCount = (content.match(/[\u4E00-\u9FA5]/g) || []).length;
|
||||
const enCount = (content.replace(/[\u4E00-\u9FA5]/g, '').match(/[a-zA-Z0-9_\u0392-\u03c9\u0400-\u04FF]+|[\u4E00-\u9FFF\u3400-\u4dbf\uf900-\ufaff\u3040-\u309f\uac00-\ud7af\u0400-\u04FF]+|[\u00E4\u00C4\u00E5\u00C5\u00F6\u00D6]+|\w+/g) || []).length;
|
||||
post.wordcount = zhCount + enCount
|
||||
}
|
||||
return post.wordcount;
|
||||
};
|
||||
|
||||
const symbolsCount = (count) => {
|
||||
if (count > 9999) {
|
||||
count = Math.round(count / 1000) + 'k'; // > 9999 => 11k
|
||||
} else if (count > 999) {
|
||||
count = (Math.round(count / 100) / 10) + 'k'; // > 999 => 1.1k
|
||||
} // < 999 => 111
|
||||
return count;
|
||||
};
|
||||
|
||||
hexo.extend.helper.register('min2read', (post, { awl, wpm }) => {
|
||||
return Math.floor(getWordCount(post) / ((awl || 2) * (wpm || 60))) + 1;
|
||||
});
|
||||
|
||||
hexo.extend.helper.register('wordcount', (post) => {
|
||||
return symbolsCount(getWordCount(post));
|
||||
});
|
||||
|
||||
hexo.extend.helper.register('wordtotal', (site) => {
|
||||
let count = 0;
|
||||
site.posts.forEach(post => {
|
||||
count += getWordCount(post);
|
||||
});
|
||||
return symbolsCount(count);
|
||||
});
|
||||
18
themes/fluid/scripts/tags/button.js
Normal file
18
themes/fluid/scripts/tags/button.js
Normal file
@ -0,0 +1,18 @@
|
||||
/* global hexo */
|
||||
|
||||
'use strict';
|
||||
|
||||
const button = (args) => {
|
||||
args = args.join(' ').split(',');
|
||||
const url = (args[0] || '').trim();
|
||||
const text = (args[1] || '').trim();
|
||||
const title = (args[2] || '').trim();
|
||||
|
||||
!url && hexo.log.warn('[Fluid] Button url must be defined!');
|
||||
|
||||
return `<a class="btn" href="${url}" ${title.length > 0 ? ` title="${title}"` : ''} target="_blank">${text}</a>`;
|
||||
};
|
||||
|
||||
// {% btn url, text, title %}
|
||||
hexo.extend.tag.register('button', button, { ends: false });
|
||||
hexo.extend.tag.register('btn', button, { ends: false });
|
||||
30
themes/fluid/scripts/tags/checkbox.js
Normal file
30
themes/fluid/scripts/tags/checkbox.js
Normal file
@ -0,0 +1,30 @@
|
||||
/* global hexo */
|
||||
|
||||
'use strict';
|
||||
|
||||
const checkbox = (args) => {
|
||||
args = args[0] === ',' ? args.slice(1) : args;
|
||||
args = args.join(' ').split(',');
|
||||
const text = (args[0] || '').trim();
|
||||
|
||||
!text && hexo.log.warn('[Fluid] Checkbox text must be defined!');
|
||||
|
||||
if (text === 'checked' || text === 'true' || text === 'false') {
|
||||
const checked = text === 'checked' || text === 'true';
|
||||
return `<input type="checkbox" disabled ${checked ? 'checked="checked"' : ''}>`;
|
||||
}
|
||||
|
||||
const checked = (args[1] || '').length > 0 && args[1].trim() !== 'false';
|
||||
const inline = (args[2] || '').length > 0 && args[2].trim() !== 'false';
|
||||
const disabled = (args[3] || '').length > 0 && args[3].trim() !== 'false';
|
||||
const content = hexo.render.renderSync({ text: text, engine: 'markdown' }).replace(/(<p>)|(<\/p>)/g, '').replace(/<br>/g, '');
|
||||
|
||||
return `${!inline ? '<div>' : ''}
|
||||
<input type="checkbox" ${disabled ? 'disabled' : ''} ${checked ? 'checked="checked"' : ''}>${content}
|
||||
${!inline ? '</div>' : ''}`;
|
||||
|
||||
};
|
||||
|
||||
// {% cb text, checked?, inline?, disabled? %}
|
||||
hexo.extend.tag.register('checkbox', checkbox, { ends: false });
|
||||
hexo.extend.tag.register('cb', checkbox, { ends: false });
|
||||
22
themes/fluid/scripts/tags/fold.js
Normal file
22
themes/fluid/scripts/tags/fold.js
Normal file
@ -0,0 +1,22 @@
|
||||
const md5 = require('../utils/crypto');
|
||||
|
||||
hexo.extend.tag.register('fold', (args, content) => {
|
||||
args = args.join(' ').split('@');
|
||||
const classes = args[0] || 'default';
|
||||
const text = args[1] || '';
|
||||
const id = 'collapse-' + md5(content).slice(0, 8);
|
||||
|
||||
return `
|
||||
<div class="fold">
|
||||
<div class="fold-title fold-${classes.trim()} collapsed" data-toggle="collapse" href="#${id}" role="button" aria-expanded="false" aria-controls="${id}">
|
||||
<div class="fold-arrow">▶</div>${text}
|
||||
</div>
|
||||
<div class="fold-collapse collapse" id="${id}">
|
||||
<div class="fold-content">
|
||||
${hexo.render.renderSync({ text: content, engine: 'markdown' }).split('\n').join('')}
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}, {
|
||||
ends: true
|
||||
});
|
||||
78
themes/fluid/scripts/tags/group-image.js
Normal file
78
themes/fluid/scripts/tags/group-image.js
Normal file
@ -0,0 +1,78 @@
|
||||
/* global hexo */
|
||||
|
||||
'use strict';
|
||||
|
||||
const DEFAULT_LAYOUTS = {
|
||||
2 : [1, 1],
|
||||
3 : [2, 1],
|
||||
4 : [2, 2],
|
||||
5 : [3, 2],
|
||||
6 : [3, 3],
|
||||
7 : [3, 2, 2],
|
||||
8 : [3, 2, 3],
|
||||
9 : [3, 3, 3],
|
||||
10: [3, 2, 2, 3]
|
||||
};
|
||||
|
||||
const groupBy = (total, layout) => {
|
||||
const r = [];
|
||||
for (let count of total) {
|
||||
r.push(layout.slice(0, count));
|
||||
layout = layout.slice(count);
|
||||
}
|
||||
return r;
|
||||
};
|
||||
|
||||
const templates = {
|
||||
|
||||
dispatch: (images, total, layout) => {
|
||||
const valid = layout && (layout.reduce((prev, current) => prev + current) === total);
|
||||
const _layout = valid ? layout : DEFAULT_LAYOUTS[total];
|
||||
return _layout ? templates.getHTML(groupBy(_layout, images)) : templates.defaults(images);
|
||||
},
|
||||
|
||||
defaults: (images) => {
|
||||
const ROW_SIZE = 3;
|
||||
const rows = images.length / ROW_SIZE;
|
||||
const imageArr = [];
|
||||
|
||||
for (let i = 0; i < rows; i++) {
|
||||
imageArr.push(images.slice(i * ROW_SIZE, (i + 1) * ROW_SIZE));
|
||||
}
|
||||
|
||||
return templates.getHTML(imageArr);
|
||||
},
|
||||
|
||||
getHTML: (rows) => {
|
||||
return rows.map(row => {
|
||||
return `<div class="group-image-row">${templates.getColumnHTML(row)}</div>`;
|
||||
}).join('');
|
||||
},
|
||||
|
||||
getColumnHTML: (images) => {
|
||||
return images.map(image => {
|
||||
return `<div class="group-image-wrap">${image}</div>`;
|
||||
}).join('');
|
||||
}
|
||||
};
|
||||
|
||||
const groupImage = (args, content) => {
|
||||
const total = parseInt(args[0], 10);
|
||||
const layout = args[1] && args[1].split('-').map((v) => parseInt(v, 10));
|
||||
|
||||
content = hexo.render.renderSync({ text: content, engine: 'markdown' });
|
||||
|
||||
const images = content.match(/<img[\s\S]*?>/g);
|
||||
|
||||
return `<div class="group-image-container">${templates.dispatch(images, total, layout)}</div>`;
|
||||
};
|
||||
|
||||
/*
|
||||
{% groupimage total n1-n2-n3-... %}
|
||||

|
||||

|
||||

|
||||
{% endgroupimage %}
|
||||
*/
|
||||
hexo.extend.tag.register('groupimage', groupImage, { ends: true });
|
||||
hexo.extend.tag.register('gi', groupImage, { ends: true });
|
||||
16
themes/fluid/scripts/tags/label.js
Normal file
16
themes/fluid/scripts/tags/label.js
Normal file
@ -0,0 +1,16 @@
|
||||
/* global hexo */
|
||||
|
||||
'use strict';
|
||||
|
||||
const label = (args) => {
|
||||
args = args.join(' ').split('@');
|
||||
const classes = args[0] || 'default';
|
||||
const text = args[1] || '';
|
||||
|
||||
!text && hexo.log.warn('[Fluid] Label text must be defined!');
|
||||
|
||||
return `<span class="label label-${classes.trim()}">${text}</span>`;
|
||||
};
|
||||
|
||||
// {% label class @text %}
|
||||
hexo.extend.tag.register('label', label, { ends: false });
|
||||
19
themes/fluid/scripts/tags/mermaid.js
Normal file
19
themes/fluid/scripts/tags/mermaid.js
Normal file
@ -0,0 +1,19 @@
|
||||
/* global hexo */
|
||||
|
||||
'use strict';
|
||||
|
||||
function mermaid(args, content) {
|
||||
return `
|
||||
<pre>
|
||||
<code class="mermaid" ${args.join(' ')}>
|
||||
${content}
|
||||
</code>
|
||||
</pre>`;
|
||||
}
|
||||
|
||||
/*
|
||||
{% mermaid %}
|
||||
text
|
||||
{% endmermaid %}
|
||||
*/
|
||||
hexo.extend.tag.register('mermaid', mermaid, { ends: true });
|
||||
19
themes/fluid/scripts/tags/note.js
Normal file
19
themes/fluid/scripts/tags/note.js
Normal file
@ -0,0 +1,19 @@
|
||||
/* global hexo */
|
||||
|
||||
'use strict';
|
||||
|
||||
const note = (args, content) => {
|
||||
if (!args || !args[0] || args[0].toLowerCase() === "default") {
|
||||
args = [ hexo.theme.config.post.updated.note_class || "info"];
|
||||
}
|
||||
return `<div class="note note-${args.join(' ')}">
|
||||
${hexo.render.renderSync({ text: content, engine: 'markdown' }).split('\n').join('')}
|
||||
</div>`;
|
||||
};
|
||||
|
||||
/*
|
||||
{% note class %}
|
||||
text
|
||||
{% endnote %}
|
||||
*/
|
||||
hexo.extend.tag.register('note', note, { ends: true });
|
||||
118
themes/fluid/scripts/utils/compare-versions.js
Normal file
118
themes/fluid/scripts/utils/compare-versions.js
Normal file
@ -0,0 +1,118 @@
|
||||
'use strict';
|
||||
// Ref: https://github.com/omichelsen/compare-versions
|
||||
|
||||
/* global define */
|
||||
(function (root, factory) {
|
||||
/* istanbul ignore next */
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define([], factory);
|
||||
} else if (typeof exports === 'object') {
|
||||
module.exports = factory();
|
||||
} else {
|
||||
root.compareVersions = factory();
|
||||
}
|
||||
}(this, function () {
|
||||
|
||||
var semver = /^v?(?:\d+)(\.(?:[x*]|\d+)(\.(?:[x*]|\d+)(\.(?:[x*]|\d+))?(?:-[\da-z\-]+(?:\.[\da-z\-]+)*)?(?:\+[\da-z\-]+(?:\.[\da-z\-]+)*)?)?)?$/i;
|
||||
|
||||
function indexOrEnd(str, q) {
|
||||
return str.indexOf(q) === -1 ? str.length : str.indexOf(q);
|
||||
}
|
||||
|
||||
function split(v) {
|
||||
var c = v.replace(/^v/, '').replace(/\+.*$/, '');
|
||||
var patchIndex = indexOrEnd(c, '-');
|
||||
var arr = c.substring(0, patchIndex).split('.');
|
||||
arr.push(c.substring(patchIndex + 1));
|
||||
return arr;
|
||||
}
|
||||
|
||||
function tryParse(v) {
|
||||
return isNaN(Number(v)) ? v : Number(v);
|
||||
}
|
||||
|
||||
function validate(version) {
|
||||
if (typeof version !== 'string') {
|
||||
throw new TypeError('Invalid argument expected string');
|
||||
}
|
||||
if (!semver.test(version)) {
|
||||
throw new Error('Invalid argument not valid semver (\''+version+'\' received)');
|
||||
}
|
||||
}
|
||||
|
||||
function compareVersions(v1, v2) {
|
||||
[v1, v2].forEach(validate);
|
||||
|
||||
var s1 = split(v1);
|
||||
var s2 = split(v2);
|
||||
|
||||
for (var i = 0; i < Math.max(s1.length - 1, s2.length - 1); i++) {
|
||||
var n1 = parseInt(s1[i] || 0, 10);
|
||||
var n2 = parseInt(s2[i] || 0, 10);
|
||||
|
||||
if (n1 > n2) return 1;
|
||||
if (n2 > n1) return -1;
|
||||
}
|
||||
|
||||
var sp1 = s1[s1.length - 1];
|
||||
var sp2 = s2[s2.length - 1];
|
||||
|
||||
if (sp1 && sp2) {
|
||||
var p1 = sp1.split('.').map(tryParse);
|
||||
var p2 = sp2.split('.').map(tryParse);
|
||||
|
||||
for (i = 0; i < Math.max(p1.length, p2.length); i++) {
|
||||
if (p1[i] === undefined || typeof p2[i] === 'string' && typeof p1[i] === 'number') return -1;
|
||||
if (p2[i] === undefined || typeof p1[i] === 'string' && typeof p2[i] === 'number') return 1;
|
||||
|
||||
if (p1[i] > p2[i]) return 1;
|
||||
if (p2[i] > p1[i]) return -1;
|
||||
}
|
||||
} else if (sp1 || sp2) {
|
||||
return sp1 ? -1 : 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
var allowedOperators = [
|
||||
'>',
|
||||
'>=',
|
||||
'=',
|
||||
'<',
|
||||
'<='
|
||||
];
|
||||
|
||||
var operatorResMap = {
|
||||
'>': [1],
|
||||
'>=': [0, 1],
|
||||
'=': [0],
|
||||
'<=': [-1, 0],
|
||||
'<': [-1]
|
||||
};
|
||||
|
||||
function validateOperator(op) {
|
||||
if (typeof op !== 'string') {
|
||||
throw new TypeError('Invalid operator type, expected string but got ' + typeof op);
|
||||
}
|
||||
if (allowedOperators.indexOf(op) === -1) {
|
||||
throw new TypeError('Invalid operator, expected one of ' + allowedOperators.join('|'));
|
||||
}
|
||||
}
|
||||
|
||||
compareVersions.validate = function(version) {
|
||||
return typeof version === 'string' && semver.test(version);
|
||||
}
|
||||
|
||||
compareVersions.compare = function (v1, v2, operator) {
|
||||
// Validate operator
|
||||
validateOperator(operator);
|
||||
|
||||
// since result of compareVersions can only be -1 or 0 or 1
|
||||
// a simple map can be used to replace switch
|
||||
var res = compareVersions(v1, v2);
|
||||
return operatorResMap[operator].indexOf(res) > -1;
|
||||
}
|
||||
|
||||
return compareVersions;
|
||||
}));
|
||||
9
themes/fluid/scripts/utils/crypto.js
Normal file
9
themes/fluid/scripts/utils/crypto.js
Normal file
@ -0,0 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
const crypto = require('crypto');
|
||||
|
||||
const md5 = (content) => {
|
||||
return crypto.createHash('md5').update(content).digest('hex');
|
||||
}
|
||||
|
||||
module.exports = md5;
|
||||
36
themes/fluid/scripts/utils/object.js
Normal file
36
themes/fluid/scripts/utils/object.js
Normal file
@ -0,0 +1,36 @@
|
||||
'use strict';
|
||||
|
||||
const isObject = (obj) => {
|
||||
return obj && typeof obj === 'object' && !Array.isArray(obj);
|
||||
};
|
||||
|
||||
const isNotEmptyObject = (obj) => {
|
||||
return obj && typeof obj === 'object' && Object.getOwnPropertyNames(obj).length !== 0;
|
||||
};
|
||||
|
||||
const isEmptyObject = (obj) => {
|
||||
return !isNotEmptyObject(obj);
|
||||
};
|
||||
|
||||
const merge = (target, ...sources) => {
|
||||
for (const source of sources) {
|
||||
for (const key in source) {
|
||||
if (!Object.prototype.hasOwnProperty.call(source, key)) {
|
||||
continue;
|
||||
}
|
||||
if (isObject(target[key]) && isObject(source[key])) {
|
||||
merge(target[key], source[key]);
|
||||
} else {
|
||||
target[key] = source[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
return target;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
isObject,
|
||||
isNotEmptyObject,
|
||||
isEmptyObject,
|
||||
merge
|
||||
};
|
||||
15
themes/fluid/scripts/utils/resolve.js
Normal file
15
themes/fluid/scripts/utils/resolve.js
Normal file
@ -0,0 +1,15 @@
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
|
||||
const resolveModule = (name, file = '') => {
|
||||
let dir;
|
||||
try {
|
||||
dir = path.dirname(require.resolve(`${name}/package.json`));
|
||||
} catch (error) {
|
||||
return '';
|
||||
}
|
||||
return `${dir}/${file}`;
|
||||
};
|
||||
|
||||
module.exports = resolveModule;
|
||||
12
themes/fluid/scripts/utils/url-join.js
Normal file
12
themes/fluid/scripts/utils/url-join.js
Normal file
@ -0,0 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
const urlJoin = function(base, relative) {
|
||||
if (relative && /^https*:\/\//.test(relative)) {
|
||||
return relative;
|
||||
}
|
||||
return relative
|
||||
? base.replace(/\/+$/, '') + '/' + relative.replace(/^\/+/, '')
|
||||
: base;
|
||||
};
|
||||
|
||||
module.exports = urlJoin;
|
||||
Reference in New Issue
Block a user