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