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,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);
});

View 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;
}
};

View 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;
}
};

View 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;
};

View 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;
});
}
};

View 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);
});
};

View 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');
});
};

View 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);
};

View 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);

View 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;
});

View 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;
});

View 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
}
});
};

View 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
};
});

View 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'));

View 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;
}
});

View 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('');
});

View 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>`;
});

View 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));
});

View 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;
});

View 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;
});

View 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;
});

View 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));
});

View 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);

View 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);
});

View 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 });

View 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 });

View 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
});

View 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-... %}
![](url)
![](url)
![](url)
{% endgroupimage %}
*/
hexo.extend.tag.register('groupimage', groupImage, { ends: true });
hexo.extend.tag.register('gi', groupImage, { ends: true });

View 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 });

View 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 });

View 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 });

View 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;
}));

View 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;

View 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
};

View 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;

View 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;