first commit

This commit is contained in:
Missdrop
2025-07-16 16:30:56 +00:00
commit 7ee33927cb
11326 changed files with 1230901 additions and 0 deletions

66
node_modules/hexo-theme-redefine/scripts/config-export.js generated vendored Executable file
View File

@@ -0,0 +1,66 @@
/* main hexo */
"use strict";
const url = require("url");
const fs = require("fs");
const path = require("path");
const yaml = require("js-yaml");
const { version } = require("../package.json");
/**
* Export theme config to js
*/
hexo.extend.helper.register("export_config", function () {
let hexo_config = {
hostname: new URL(this.config.url).hostname || this.config.url,
root: this.config.root,
language: this.config.language,
};
if (this.config.search) {
hexo_config.path = this.config.search.path;
}
let theme_config = {
articles: this.theme.articles,
colors: this.theme.colors,
global: this.theme.global,
home_banner: this.theme.home_banner,
plugins: this.theme.plugins,
version: version,
code_block: this.theme.code_block,
navbar: this.theme.navbar,
page_templates: this.theme.page_templates,
home: this.theme.home,
footerStart: this.theme.footer.start,
};
const languageDir = path.join(__dirname, "../languages");
let file = fs
.readdirSync(languageDir)
.find((v) => v === `${this.config.language}.yml`);
file = languageDir + "/" + (file ? file : "en.yml");
let languageContent = fs.readFileSync(file, "utf8");
try {
languageContent = yaml.load(languageContent);
} catch (e) {
console.log(e);
}
let data_config = {
masonry: false,
};
if (this.theme.masonry) {
data_config.masonry = true;
}
return `<script id="hexo-configurations">
window.config = ${JSON.stringify(hexo_config)};
window.theme = ${JSON.stringify(theme_config)};
window.lang_ago = ${JSON.stringify(languageContent["ago"])};
window.data = ${JSON.stringify(data_config)};
</script>`;
});

37
node_modules/hexo-theme-redefine/scripts/data-handle.js generated vendored Executable file
View File

@@ -0,0 +1,37 @@
hexo.on('generateBefore', function () {
if (hexo.locals.get) {
const data = hexo.locals.get('data');
if (data) {
// theme config file handle
if (data._config) {
hexo.theme.config = data._config;
} else if (data.redefine) {
hexo.theme.config = data.redefine;
} else if (data._redefine) {
hexo.theme.config = data._redefine;
}
// friends link file handle
if (data.links || data.link) {
hexo.theme.config.links = (data.links || data.link);
}
if (data.essays || data.essay || data.shuoshuo) {
hexo.theme.config.essays = (data.essays || data.essay || data.shuoshuo);
}
if (data.masonry || data.gallery || data.photos) {
hexo.theme.config.masonry = (data.masonry || data.gallery || data.photos);
}
if (data.bookmarks || data.tools) {
hexo.theme.config.bookmarks = data.bookmarks || data.tools;
}
}
}
});

16
node_modules/hexo-theme-redefine/scripts/events/404.js generated vendored Executable file
View File

@@ -0,0 +1,16 @@
/**
* Theme Redefine
* 404 error page
*/
hexo.extend.generator.register('404', function (locals) {
return {
path: '404.html',
layout: '404',
data: {
title: 'Page Not Found',
type: 'notfound',
page: locals.pages.findOne({ path: '404.html' })
}
}
});

View File

@@ -0,0 +1,139 @@
/**
* Theme Redefine
* welcome.js
*/
const { version } = require("../../package.json");
const https = require("https");
hexo.on("ready", async () => {
const timeout = 3000;
async function fetchRedefineInfo() {
return new Promise((resolve, reject) => {
https
.get(
`https://redefine-version.ohevan.com/api/v2/info`,
{ timeout: timeout },
(response) => {
if (response.statusCode < 200 || response.statusCode > 299) {
logFailedInfo();
return reject(
new Error(
`Failed to load page, status code: ${response.statusCode}`,
),
);
}
let data = "";
response.on("data", (chunk) => {
data += chunk;
});
response.on("end", () => {
try {
const jsonData = JSON.parse(data);
if (jsonData.status !== "success") {
logFailedInfo();
return reject(
new Error(`Failed to fetch data: ${jsonData.message}`),
);
}
const redefineData = jsonData.data;
logInfo(redefineData);
checkVersionAndCDNAvailability(redefineData);
resolve();
} catch (error) {
logFailedInfo();
reject(new Error(`JSON parse failed: ${error.message}`));
}
});
},
)
.on("error", (error) => {
reject(error);
});
});
}
try {
await fetchRedefineInfo();
} catch (error) {
hexo.log.warn(`Check latest version failed: ${error}`);
hexo.locals.set(`cdnTestStatus_cdnjs`, 404);
hexo.locals.set(`cdnTestStatus_zstatic`, 404);
hexo.locals.set(`cdnTestStatus_npmMirror`, 404);
}
});
function logInfo(data) {
hexo.log.info(
`
+======================================================================================+
| |
| _____ _ _ _____ __ __ _____ ____ _____ ____ _____ _____ ___ _ _ _____ |
| |_ _| | | | ____| \\/ | ____| | _ \\| ____| _ \\| ____| ___|_ _| \\ | | ____| |
| | | | |_| | _| | |\\/| | _| | |_) | _| | | | | _| | |_ | || \\| | _| |
| | | | _ | |___| | | | |___ | _ <| |___| |_| | |___| _| | || |\\ | |___ |
| |_| |_| |_|_____|_| |_|_____| |_| \\_\\_____|____/|_____|_| |___|_| \\_|_____| |
| |
| current v${version} latest v${data.npmVersion} |
| https://github.com/EvanNotFound/hexo-theme-redefine |
+======================================================================================+
`,
);
}
function logFailedInfo() {
hexo.log.info(
`
+======================================================================================+
| |
| _____ _ _ _____ __ __ _____ ____ _____ ____ _____ _____ ___ _ _ _____ |
| |_ _| | | | ____| \\/ | ____| | _ \\| ____| _ \\| ____| ___|_ _| \\ | | ____| |
| | | | |_| | _| | |\\/| | _| | |_) | _| | | | | _| | |_ | || \\| | _| |
| | | | _ | |___| | | | |___ | _ <| |___| |_| | |___| _| | || |\\ | |___ |
| |_| |_| |_|_____|_| |_|_____| |_| \\_\\_____|____/|_____|_| |___|_| \\_|_____| |
| |
| current v${version} fetch latest failed |
| https://github.com/EvanNotFound/hexo-theme-redefine |
+======================================================================================+
`,
);
}
function checkVersionAndCDNAvailability(data) {
if (data.npmVersion > version) {
hexo.log.warn(
`\x1b[33m%s\x1b[0m`,
`Redefine v${version} is outdated, please update to v${data.npmVersion}!`,
);
}
if (data.npmMirrorCDN) {
hexo.log.info(
`\x1b[32m%s\x1b[0m`,
`CDN available: NPMMirror (Recommended)`,
);
hexo.locals.set(`cdnTestStatus_npmMirror`, 200);
} else {
hexo.log.warn(`\x1b[31m%s\x1b[0m`, `NPMMirror CDN is unavailable yet.`);
hexo.locals.set(`cdnTestStatus_npmMirror`, 404);
}
if (data.zstaticCDN) {
hexo.log.info(`\x1b[32m%s\x1b[0m`, `CDN available: ZStatic`);
hexo.locals.set(`cdnTestStatus_zstatic`, 200);
} else {
hexo.log.warn(`\x1b[31m%s\x1b[0m`, `ZStatic CDN is unavailable yet.`);
hexo.locals.set(`cdnTestStatus_zstatic`, 404);
}
if (data.cdnjsCDN) {
hexo.log.info(`\x1b[32m%s\x1b[0m`, `CDN available: CDNJS`);
hexo.locals.set(`cdnTestStatus_cdnjs`, 200);
} else {
hexo.log.warn(`\x1b[31m%s\x1b[0m`, `CDNJS CDN is unavailable yet.`);
hexo.locals.set(`cdnTestStatus_cdnjs`, 404);
}
}

View File

@@ -0,0 +1,27 @@
/* delete mask */
"use strict";
hexo.extend.filter.register(
"after_post_render",
function (data) {
const theme = this.theme;
// 处理del标签的代码
const regPureDelTag = /<del(?:\s+[^>]*)?>((?:(?!<\/?del[\s>])[^])*)<\/del>/g;
data.content = data.content.replace(
regPureDelTag,
function (match, html) {
// 只有在配置为true时才添加mask类
if (theme.config.articles.style.delete_mask === true) {
return `<del class="mask">${html}</del>`;
}
return match;
}
);
return data;
},
0
);

202
node_modules/hexo-theme-redefine/scripts/filters/encrypt.js generated vendored Executable file
View File

@@ -0,0 +1,202 @@
/* main hexo, __dirname */
"use strict";
const crypto = require("crypto");
const fs = require("fs");
const path = require("path");
const log = hexo.log;
const { hbeTheme } = require("./lib/hbe.default.js");
const defaultConfig = {
abstract:
"Here's something encrypted, password is required to continue reading.",
message: "Hey, password is required here.",
theme: "default",
wrong_pass_message:
"Oh, this is an invalid password. Check and try again, please.",
wrong_hash_message:
"OOPS, these decrypted content may changed, but you can still have a look.",
silent: false,
};
const keySalt = textToArray("too young too simple");
const ivSalt = textToArray("sometimes naive!");
// As we can't detect the wrong password with AES-CBC,
// so adding an empty tag and check it when decrption.
const knownPrefix = "<hbe-prefix></hbe-prefix>";
// disable log
var silent = false;
// use default theme
var theme = "default";
hexo.extend.filter.register(
"after_post_render",
(data) => {
const tagEncryptPairs = [];
let password = data.password;
let tagUsed = false;
// use a empty password to disable category encryption
if (password === "") {
return data;
}
if (hexo.config.encrypt === undefined) {
hexo.config.encrypt = [];
}
if ("encrypt" in hexo.config && "tags" in hexo.config.encrypt) {
hexo.config.encrypt.tags.forEach((tagObj) => {
tagEncryptPairs[tagObj.name] = tagObj.password;
});
}
if (data.tags) {
data.tags.forEach((cTag) => {
if (tagEncryptPairs.hasOwnProperty(cTag.name)) {
tagUsed = password ? tagUsed : cTag.name;
password = password || tagEncryptPairs[cTag.name];
}
});
}
if (password == undefined) {
return data;
}
password = password.toString();
// make sure toc can work.
data.origin = data.content;
// Let's rock n roll
const config = Object.assign(defaultConfig, hexo.config.encrypt, data);
silent = config.silent;
theme = config.theme.trim().toLowerCase();
// deprecate the template keyword
if (config.template) {
dlog(
"warn",
'Looks like you use a deprecated property "template" to set up template, consider to use "theme"? See https://github.com/D0n9X1n/hexo-blog-encrypt#encrypt-theme',
);
}
// read theme from file
let template = hbeTheme;
if (tagUsed === false) {
dlog(
"info",
`hexo-blog-encrypt: encrypting "${data.title.trim()}" based on the password configured in Front-matter with theme: ${theme}.`,
);
} else {
dlog(
"info",
`hexo-blog-encrypt: encrypting "${data.title.trim()}" based on Tag: "${tagUsed}" with theme ${theme}.`,
);
}
data.content = knownPrefix + data.content.trim();
data.encrypt = true;
const key = crypto.pbkdf2Sync(password, keySalt, 1024, 32, "sha256");
const iv = crypto.pbkdf2Sync(password, ivSalt, 512, 16, "sha256");
const cipher = crypto.createCipheriv("aes-256-cbc", key, iv);
const hmac = crypto.createHmac("sha256", key);
let encryptedData = cipher.update(data.content, "utf8", "hex");
hmac.update(data.content, "utf8");
encryptedData += cipher.final("hex");
const hmacDigest = hmac.digest("hex");
data.content = template
.replace(/{{hbeEncryptedData}}/g, encryptedData)
.replace(/{{hbeHmacDigest}}/g, hmacDigest)
.replace(/{{hbeWrongPassMessage}}/g, config.wrong_pass_message)
.replace(/{{hbeWrongHashMessage}}/g, config.wrong_hash_message)
.replace(/{{hbeMessage}}/g, config.message);
data.content += `<link href="${hexo.config.root}css/hbe.style.css" rel="stylesheet" type="text/css"><script data-swup-reload-script type="module" src="${hexo.config.root}js/plugins/hbe.js"></script>
<script data-swup-reload-script type="module">
import {initHBE} from "${hexo.config.root}js/plugins/hbe.js";
console.log("hexo-blog-encrypt: loaded.");
initHBE();
</script>
`;
data.excerpt = data.more = config.abstract;
return data;
},
1000,
);
hexo.extend.generator.register("hexo-blog-encrypt", () => [
{
data: () =>
fs.createReadStream(
path.resolve(__dirname, `../../source/assets/hbe.style.css`),
),
path: `css/hbe.style.css`,
},
{
data: () =>
fs.createReadStream(
path.resolve(__dirname, "../../source/js/plugins/hbe.js"),
),
path: "js/plugins/hbe.js",
},
]);
// log function
function dlog(level, x) {
switch (level) {
case "warn":
log.warn(x);
break;
case "info":
default:
if (silent) {
return;
}
log.info(x);
}
}
// Utils functions
function textToArray(s) {
var i = s.length;
var n = 0;
var ba = new Array();
for (var j = 0; j < i; ) {
var c = s.codePointAt(j);
if (c < 128) {
ba[n++] = c;
j++;
} else if (c > 127 && c < 2048) {
ba[n++] = (c >> 6) | 192;
ba[n++] = (c & 63) | 128;
j++;
} else if (c > 2047 && c < 65536) {
ba[n++] = (c >> 12) | 224;
ba[n++] = ((c >> 6) & 63) | 128;
ba[n++] = (c & 63) | 128;
j++;
} else {
ba[n++] = (c >> 18) | 240;
ba[n++] = ((c >> 12) & 63) | 128;
ba[n++] = ((c >> 6) & 63) | 128;
ba[n++] = (c & 63) | 128;
j += 2;
}
}
return new Uint8Array(ba);
}

View File

@@ -0,0 +1,9 @@
hexo.extend.filter.register('after_post_render', function(data) {
if (this.theme.config.articles.style.image_caption !== false) {
const class_name='image-caption';
if (data.layout === 'post' || data.layout === 'page' || data.layout === 'about') {
data.content = data.content.replace(/(<img [^>]*alt="([^"]+)"[^>]*>)/g, `<figure class="${class_name}">$1<figcaption>$2</figcaption></figure>`);
}
}
return data;
});

View File

@@ -0,0 +1,24 @@
'use strict'
hexo.extend.filter.register(
'after_post_render',
function (data) {
const theme = hexo.theme.config;
if (!theme.articles.lazyload || !theme.articles.lazyload) return;
data.content = data.content.replace(
// Match 'img' tags width the src attribute.
/<img([^>]*)src="([^"]*)"([^>\/]*)\/?\s*>/gim,
function (match, attrBegin, src, attrEnd) {
// Exit if the src doesn't exists.
if (!src) return match;
return `<img ${attrBegin}
lazyload
src="/images/loading.svg"
data-src="${src}"
${attrEnd}
>`
}
)
},
1
);

View File

@@ -0,0 +1,15 @@
const hbeTheme = `
<div class="hbe hbe-container" id="hexo-blog-encrypt" data-wpm="{{hbeWrongPassMessage}}" data-whm="{{hbeWrongHashMessage}}">
<script id="hbeData" type="hbeData" data-hmacdigest="{{hbeHmacDigest}}">{{hbeEncryptedData}}</script>
<div class="hbe hbe-content">
<div class="hbe hbe-input hbe-input-default">
<input class="hbe hbe-input-field hbe-input-field-default" type="password" id="hbePass">
<label class="hbe hbe-input-label hbe-input-label-default" for="hbePass">
<span class="hbe hbe-input-label-content hbe-input-label-content-default">{{hbeMessage}}</span>
</label>
</div>
</div>
</div>
`;
module.exports = { hbeTheme };

View File

@@ -0,0 +1,42 @@
/* main hexo */
"use strict";
hexo.extend.filter.register(
"after_post_render",
function (data) {
const theme = this.theme;
const config = this.config;
const url = new URL(config.url);
const siteHost = url.hostname || config.url;
// Match 'a' tags that don't contain html children.
const regPureATag = /<a([^>]*)href="([^"]*)"([^>]*)>([^<]*)<\/a>/gim;
data.content = data.content.replace(
regPureATag,
function (match, attrBegin, href, attrEnd, html) {
// Exit if the href attribute doesn't exists.
if (!href) return match;
let link = "";
try {
link = new URL(href);
} catch (e) {
// Invalid url, e.g. Anchor link.
return match;
}
// Exit if the url has same host with `config.url`, which means isn't an external link.
if (!link.protocol || link.hostname === siteHost) return match;
if (theme.config.articles.style.link_icon == false) {
return `<a class="link" ${attrBegin} href="${href}" ${attrEnd}>${html}</a>`;
} else {
return `<a class="link" ${attrBegin} href="${href}" ${attrEnd}>${html}<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a>`;
}
},
);
},
0,
);

View File

@@ -0,0 +1,9 @@
hexo.extend.filter.register('stylus:renderer', function (style) {
style.define('url-for', function (data) {
const urlRender = hexo.extend.helper.get('url_for').bind(hexo);
const url = urlRender(data.val);
return url;
});
})

View File

@@ -0,0 +1,11 @@
/*
'use strict';
hexo.extend.filter.register('after_post_render', function(data) {
const tableRegex = /<table(?![\s\S]*?class=["'].*?\bgutter\b.*?["'])[\s\S]*?<\/table>/g;
const wrappedTable = match => `<div class="table-container">${match}</div>`;
data.content = data.content.replace(tableRegex, wrappedTable);
return data;
});
*/

View File

@@ -0,0 +1,38 @@
hexo.extend.helper.register('generateMeta', function (theme, page) {
const hexo = this;
let robots_content="";
if (page.robots) {
robots_content = page.robots
} else if (theme.seo && theme.seo.robots) {
if (hexo.is_home()) {
if (page.prev == 0) {
robots_content=theme.seo.robots.home_first_page
}else{
robots_content=theme.seo.robots.home_other_pages
}
} else if (hexo.is_archive()) {
robots_content=theme.seo.robots.archive
} else if (hexo.is_category()) {
robots_content=theme.seo.robots.category
} else if (hexo.is_tag()) {
robots_content=theme.seo.robots.tag
}
}
if(robots_content){
return `<meta name="robots" content="${robots_content}">`
}
});
/**
* hexo-auto-canonical
* https://github.com/hyunseob/hexo-auto-canonical.git
* Copyright (c) 2015, HyunSeob
* Licensed under the MIT license.
*/
hexo.extend.helper.register('autoCanonical', function (config, page) {
var base_url = config.url;
if (config.url.charAt(config.url.length - 1) !== '/') base_url += '/';
return '<link rel="canonical" href="' + base_url + page.canonical_path.replace('index.html', '').toLowerCase() + '"/>';
});

View File

@@ -0,0 +1,153 @@
/*
pageData is an object that defines various page types and their associated rendering details.
Each page type includes the following properties:
- titles: An array of possible titles for the page, which are used to identify the page type.
- types: An array of page types that can be matched; the type takes precedence over the title for identification.
- partial: The path to the partial template that will be used to render the content of the page.
- layout: Specifies the layout style for the page. "raw" indicates that no theme layout will be applied, while "default" means the standard theme layout (container) will be used.
*/
const pageData = {
home: {
titles: ["home", "首页"],
types: ["home"],
partial: "pages/home/home-content",
layout: "raw",
},
archive: {
titles: ["archive", "归档"],
types: ["archive", "archives"],
partial: "pages/archive/archive",
layout: "raw",
},
post: {
titles: ["post", "文章"],
types: ["post", "posts"],
partial: "pages/post/article-content",
layout: "raw",
},
categories: {
titles: ["category", "categories"],
types: ["category", "categories"],
partial: "pages/category/categories",
layout: "default",
},
categoryDetail: {
titles: [],
types: [],
partial: "pages/category/category-detail",
layout: "default",
},
tags: {
titles: ["tag", "tags"],
types: ["tag", "tags"],
partial: "pages/tag/tags",
layout: "default",
},
tagDetail: {
titles: [],
types: [],
partial: "pages/tag/tag-detail",
layout: "default",
},
notFound: {
titles: ["404", "notfound"],
types: ["404", "notfound"],
partial: "pages/notfound/notfound",
layout: "raw",
},
friends: {
titles: ["friends", "links", "link", "friend", "友情链接"],
types: ["links", "link"],
partial: "pages/friends/friends-link",
layout: "default",
},
shuoshuo: {
titles: ["shuoshuo", "说说"],
types: ["essays", "essay", "shuoshuo"],
partial: "pages/shuoshuo/essays",
layout: "default",
},
masonry: {
titles: ["gallery", "瀑布流", "相册", "photos", "photo"],
types: ["masonry", "gallery", "瀑布流", "相册", "photos", "photo"],
partial: "pages/masonry/masonry",
layout: "default",
},
bookmarks: {
titles: [],
types: ["bookmarks", "bookmark", "tools"],
partial: "pages/bookmarks/bookmarks",
layout: "raw",
},
pageTemplate: {
titles: [],
types: [],
partial: "pages/page-template",
layout: "default",
},
};
hexo.extend.helper.register("getAllPageData", function () {
return pageData;
});
hexo.extend.helper.register("getPageData", function (page) {
if (this.is_home()) return pageData.home;
if (this.is_archive()) return pageData.archive;
if (this.is_post()) return pageData.post;
if (this.is_category()) return pageData.categoryDetail;
if (this.is_tag()) return pageData.tagDetail;
const currentPageConfig = Object.entries(pageData).find(([type, config]) => {
return config.types.includes(page.template || page.type) || config.titles.includes(page.title?.toLowerCase());
});
return currentPageConfig ? pageData[currentPageConfig[0]] : null;
});
hexo.extend.helper.register("getPagePartialPath", function (page) {
const matchesPageType = (type) => {
const config = pageData[type];
return (
config.types.includes(page.template || page.type) ||
config.titles.includes(page.title?.toLowerCase())
);
};
// Check built-in page types first
if (this.is_home()) return pageData.home.partial;
if (this.is_post()) return pageData.post.partial;
// Check custom page types
for (const [type, config] of Object.entries(pageData)) {
if (matchesPageType(type) && config.layout === "raw") {
return config.partial;
} else if (this.is_archive() && pageData.archive.layout === "raw") { // return raw layout for archive page
return pageData.archive.partial;
} else if (this.is_category() && pageData.categoryDetail.layout === "raw") { // return raw layout for category page
return pageData.categoryDetail.partial;
} else if (this.is_tag() && pageData.tagDetail.layout === "raw") { // return raw layout for tag page
return pageData.tagDetail.partial;
}
}
return pageData.pageTemplate.partial;
});
hexo.extend.helper.register("getPageTitle", function (page) {
const pageData = this.getPageData(page);
let type = null;
// Determine the type based on page properties
if (this.is_home()) type = "home";
else if (this.is_archive()) type = "archive";
else if (this.is_post()) type = "post";
else {
type = pageData.type;
}
// const config = type ? pageData[type] : null;
return page.title || this.__(type) || "Untitled";
});

View File

@@ -0,0 +1,367 @@
/*
* Optimized by: EvanNotFound
*/
const articleLibrary = [];
const corpus = [];
let flag = null;
hexo.extend.filter.register("template_locals", function (localVariables) {
const cfg = hexo.theme.config.articles.recommendation;
if (!cfg.enable) {
return localVariables;
}
if (!flag) {
flag = 1;
fetchData(localVariables.site.posts, cfg);
fetchData(localVariables.site.pages, cfg);
articleRecommendation(cfg);
}
return localVariables;
});
function fetchData(s, cfg) {
s.each(function (p) {
if (["post", "docs"].includes(p.layout)) {
articleLibrary.push({
path: p.path,
title: p.title || p.seo_title || p.short_title,
headimg: p.thumbnail || p.banner || p.cover || cfg.placeholder,
});
corpus.push(tokenize(p.raw));
}
});
}
function cleanData(data) {
const symbolLists = [
",",
".",
"?",
"!",
":",
";",
"、",
"……",
"~",
"&",
"@",
"#",
"",
"。",
"",
"",
"",
"",
"·",
"…",
"",
"",
"",
"",
"“",
"”",
"",
"",
"〝",
"〞",
'"',
"'",
"",
"",
"´",
"",
"(",
")",
"【",
"】",
"《",
"》",
"",
"",
"﹝",
"﹞",
"<",
">",
"(",
")",
"[",
"]",
"«",
"»",
"",
"",
"",
"",
"〈",
"〉",
"{",
"}",
"",
"",
"「",
"」",
"",
"",
"〖",
"〗",
"『",
"』",
"︵",
"︷",
"︹",
"︿",
"︽",
"﹁",
"﹃",
"︻",
"︗",
"/",
"|",
"\\",
"︶",
"︸",
"︺",
"﹀",
"︾",
"﹂",
"﹄",
"﹄",
"︼",
"︘",
"",
"",
"",
"_",
"¯",
"_",
" ̄",
"",
"﹋",
"",
"﹉",
"",
"﹊",
"`",
"ˋ",
"¦",
"︴",
"¡",
"¿",
"^",
"ˇ",
"­",
"¨",
"ˊ",
" ",
" ",
"%",
"*",
"-",
"+",
"=",
"¥",
"$",
"",
"",
];
data = data.replace(/\s/g, " ");
data = data.replace(/\!\[(.*?)\]\(.*?\)/g, (_a, b) => {
return b;
});
data = data.replace(
/(http|ftp|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&amp;:/~\+#]*[\w\-\@?^=%&amp;/~\+#])?/g,
" ",
);
for (const symbol of symbolLists) {
data = data.replace(new RegExp("\\" + symbol, "g"), " ");
}
data = data.replace(/\d+/g, " ");
data = data.replace(/\s/g, " ");
return data;
}
function tokenize(data) {
const jieba = require("nodejieba");
return jieba
.cut(cleanData(data), true)
.filter((word) => word !== " " && !/^[0-9]*$/.test(word));
}
function cosineSimilarity(vector1, vector2) {
let numerator = 0;
let sqrt1 = 0;
let sqrt2 = 0;
if (vector1.length == vector2.length) {
for (let i = 0; i < vector1.length; i++) {
numerator += vector1[i] * vector2[i];
sqrt1 += vector1[i] * vector1[i];
sqrt2 += vector2[i] * vector2[i];
}
return numerator / (Math.sqrt(sqrt1) * Math.sqrt(sqrt2));
}
}
function createWordLibrary(allWords) {
const wordLibrary = {};
allWords.forEach((word) => {
wordLibrary[word] = 0;
});
return wordLibrary;
}
function calculateWordFrequency(wordLibrary, wordsInArticle) {
const wordOccurrenceLibrary = wordsInArticle.reduce(
(wordCountObj, wordName) => {
if (wordName in wordCountObj) {
wordCountObj[wordName]++;
}
return wordCountObj;
},
JSON.parse(JSON.stringify(wordLibrary)),
);
const wordFrequency = JSON.parse(JSON.stringify(wordLibrary));
for (const word of Object.keys(wordLibrary)) {
wordFrequency[word] = wordOccurrenceLibrary[word] / wordsInArticle.length;
}
return wordFrequency;
}
function articleRecommendation(cfg) {
const dataSet = {};
const similaritySet = {};
const recommendationSet = {};
let allWordsInAllArticles = [];
for (const wordList of corpus) {
allWordsInAllArticles = [
...new Set(allWordsInAllArticles.concat(wordList)),
];
}
const wordLibrary = createWordLibrary(allWordsInAllArticles);
const documentCountLibrary = JSON.parse(JSON.stringify(wordLibrary));
for (let i = 0; i < corpus.length; i++) {
const articlePath = articleLibrary[i].path;
const wordsInArticle = corpus[i];
const wordFrequency = calculateWordFrequency(wordLibrary, wordsInArticle);
dataSet[articlePath] = { wordFrequency };
for (const word of Object.keys(wordLibrary)) {
if (wordFrequency[word]) {
documentCountLibrary[word]++;
}
}
}
for (let i = 0; i < corpus.length; i++) {
const articlePath = articleLibrary[i].path;
dataSet[articlePath]["inverseDocumentFrequency"] = JSON.parse(
JSON.stringify(wordLibrary),
);
dataSet[articlePath]["wordFrequency-inverseDocumentFrequency"] = JSON.parse(
JSON.stringify(wordLibrary),
);
dataSet[articlePath]["wordFrequencyVector"] = [];
for (const word of Object.keys(wordLibrary)) {
const inverseDocumentFrequency = Math.log(
corpus.length / (documentCountLibrary[word] + 1),
);
const wordFrequencyInverseDocumentFrequency =
dataSet[articlePath]["wordFrequency"][word] * inverseDocumentFrequency;
dataSet[articlePath]["wordFrequencyVector"].push(
wordFrequencyInverseDocumentFrequency,
);
}
}
for (let i = 0; i < corpus.length; i++) {
const articlePath1 = articleLibrary[i].path;
similaritySet[articlePath1] = {};
for (let j = 0; j < corpus.length; j++) {
const articlePath2 = articleLibrary[j].path;
similaritySet[articlePath1][articlePath2] = cosineSimilarity(
dataSet[articlePath1]["wordFrequencyVector"],
dataSet[articlePath2]["wordFrequencyVector"],
);
}
for (let j = 0; j < corpus.length; j++) {
recommendationSet[articlePath1] = Object.keys(
similaritySet[articlePath1],
).sort(function (a, b) {
return similaritySet[articlePath1][b] - similaritySet[articlePath1][a]; // Descending order
});
}
const index = recommendationSet[articlePath1].indexOf(articlePath1);
if (index > -1) {
recommendationSet[articlePath1].splice(index, 1);
}
recommendationSet[articlePath1] = recommendationSet[articlePath1].slice(
0,
cfg.limit,
);
for (let j = 0; j < recommendationSet[articlePath1].length; j++) {
const e = recommendationSet[articlePath1][j];
recommendationSet[articlePath1][j] = articleLibrary.filter(
(w) => w.path == e,
)[0];
}
}
hexo.locals.set("recommendationSet", function () {
return recommendationSet;
});
}
hexo.extend.helper.register("articleRecommendationGenerator", function (post) {
if (!post) return "";
const cfg = hexo.theme.config.articles.recommendation;
if (!cfg.enable) {
return "";
}
for (const dir of cfg.skip_dirs) {
if (new RegExp("^" + dir, "g").test(post.path)) {
return "";
}
}
const recommendationSet = hexo.locals.get("recommendationSet");
const recommendedArticles = recommendationSet[post.path];
return userInterface(recommendedArticles, cfg);
});
function userInterface(recommendedArticles, cfg) {
let html = "";
let htmlMobile = "";
for (const item of recommendedArticles) {
html += itemInterface(item);
}
for (const itemMobile of recommendedArticles.slice(0, cfg.mobile_limit)) {
htmlMobile += itemInterface(itemMobile);
}
return `
<div class="recommended-article px-2 sm:px-6 md:px-8">
<div class="recommended-desktop">
<div class="recommended-article-header text-xl md:text-3xl font-bold mt-10">
<i aria-hidden="true"></i><span>${cfg.title}</span>
</div>
<div class="recommended-article-group">${html}</div>
</div>
<div class="recommended-mobile">
<div class="recommended-article-header text-xl md:text-3xl font-bold mt-10">
<i aria-hidden="true"></i><span>${cfg.title}</span>
</div>
<div class="recommended-article-group">${htmlMobile}</div>
</div>
</div>`;
}
function itemInterface(item) {
return `<a class="recommended-article-item" href="${
hexo.config.root + item.path
}" title="${item.title}" rel="bookmark">
<img src="${item.headimg}" alt="${item.title}" class="!max-w-none">
<span class="title">${item.title}</span>
</a>`;
}

View File

@@ -0,0 +1,251 @@
/* main hexo */
"use strict";
const url = require("url");
const { version } = require("../../package.json");
const themeVersion = version;
hexo.extend.helper.register("isHomePagePagination", function (pagePath, route) {
if (pagePath.length > 5 && route === "/") {
return pagePath.slice(0, 5) === "page/";
}
return false;
});
/* code block language display */
hexo.extend.filter.register("after_post_render", function (data) {
// Only process if not already processed
if (data._processedHighlight) return data;
// Updated pattern to include numbers and other special characters
const pattern = /<figure class="highlight ([^"]+)">([\s\S]*?)<\/figure>/g;
data.content = data.content.replace(pattern, function (match, p1, p2) {
// If already has code-container anywhere in the match, return unchanged
if (match.includes('code-container')) {
return match;
}
let language = p1 || "code";
if (language === "plain") {
language = "code";
}
return '<div class="code-container" data-rel="' +
language.charAt(0).toUpperCase() +
language.slice(1) +
'">' +
match.replace(
'<figure class="highlight ',
'<figure class="iseeu highlight '
) +
"</div>";
});
// Mark as processed
data._processedHighlight = true;
return data;
});
hexo.extend.helper.register("createNewArchivePosts", function (posts) {
const postList = [],
postYearList = [];
posts.forEach((post) => postYearList.push(post.date.year()));
Array.from(new Set(postYearList)).forEach((year) => {
postList.push({
year: year,
postList: [],
});
});
postList.sort((a, b) => b.year - a.year);
postList.forEach((item) => {
posts.forEach(
(post) => item.year === post.date.year() && item.postList.push(post),
);
});
postList.forEach((item) =>
item.postList.sort((a, b) => b.date.unix() - a.date.unix()),
);
return postList;
});
hexo.extend.helper.register(
"getAuthorLabel",
function (postCount, isAuto, labelList) {
let level = Math.floor(Math.log2(postCount));
level = level < 2 ? 1 : level - 1;
if (isAuto === false && Array.isArray(labelList) && labelList.length > 0) {
return level > labelList.length
? labelList[labelList.length - 1]
: labelList[level - 1];
} else {
return `Lv${level}`;
}
},
);
hexo.extend.helper.register("getPostUrl", function (rootUrl, path) {
if (rootUrl) {
let { href } = new URL(rootUrl);
if (href.substring(href.length - 1) !== "/") {
href = href + "/";
}
return href + path;
} else {
return path;
}
});
hexo.extend.helper.register("renderJS", function (path, options = {}) {
const _js = hexo.extend.helper.get("js").bind(hexo);
const { module = false, async = false, swupReload = false } = options;
if (Array.isArray(path)) {
path = path.map((p) => "js/build/" + p);
} else {
path = "js/build/" + path;
}
const cdnProviders = {
zstatic:
"https://s4.zstatic.net/ajax/libs/hexo-theme-redefine/:version/:path",
cdnjs:
"https://cdnjs.cloudflare.com/ajax/libs/hexo-theme-redefine/:version/:path",
unpkg: "https://unpkg.com/hexo-theme-redefine@:version/source/:path",
jsdelivr:
"https://cdn.jsdelivr.net/npm/hexo-theme-redefine@:version/source/:path",
aliyun:
"https://evan.beee.top/projects/hexo-theme-redefine@:version/source/:path",
npmmirror:
"https://registry.npmmirror.com/hexo-theme-redefine/:version/files/source/:path",
custom: this.theme.cdn.custom_url,
};
const cdnPathHandle = (path) => {
const cdnBase =
cdnProviders[this.theme.cdn.provider] || cdnProviders.npmmirror;
let scriptTag;
const typeAttr = module ? 'type="module"' : "";
// const asyncAttr = async ? "async" : "";
const swupAttr = swupReload ? "data-swup-reload-script" : "";
if (this.theme.cdn.enable) {
if (this.theme.cdn.provider === "custom") {
const customUrl = cdnBase
.replace(":version", themeVersion)
.replace(":path", path);
scriptTag = `<script ${typeAttr} src="${
this.theme.cdn.enable ? customUrl : _js({ src: path })
}" ${swupAttr}></script>`;
} else {
scriptTag = `<script ${typeAttr} src="${cdnBase
.replace(":version", themeVersion)
.replace(":path", path)}" ${swupAttr}></script>`;
}
} else {
scriptTag = _js({
src: path,
type: module ? "module" : undefined,
"data-swup-reload-script": swupReload ? "" : undefined,
// async: async,
});
}
return scriptTag;
};
let renderedScripts = "";
if (Array.isArray(path)) {
renderedScripts = path.map(cdnPathHandle).join("");
} else {
renderedScripts = cdnPathHandle(path);
}
return renderedScripts;
});
hexo.extend.helper.register("renderCSS", function (path) {
const _css = hexo.extend.helper.get("css").bind(hexo);
const cdnProviders = {
zstatic:
"https://s4.zstatic.net/ajax/libs/hexo-theme-redefine/:version/:path",
cdnjs:
"https://cdnjs.cloudflare.com/ajax/libs/hexo-theme-redefine/:version/:path",
unpkg: "https://unpkg.com/hexo-theme-redefine@:version/source/:path",
jsdelivr:
"https://cdn.jsdelivr.net/npm/hexo-theme-redefine@:version/source/:path",
aliyun:
"https://evan.beee.top/projects/hexo-theme-redefine@:version/source/:path",
npmmirror:
"https://registry.npmmirror.com/hexo-theme-redefine/:version/files/source/:path",
custom: this.theme.cdn.custom_url,
};
const cdnPathHandle = (path) => {
const cdnBase =
cdnProviders[this.theme.cdn.provider] || cdnProviders.npmmirror;
let cssLink;
if (this.theme.cdn.enable) {
if (this.theme.cdn.provider === "custom") {
const customUrl = cdnBase
.replace(":version", themeVersion)
.replace(":path", path);
cssLink = `<link rel="stylesheet" href="${customUrl}">`;
} else {
cssLink = `<link rel="stylesheet" href="${cdnBase
.replace(":version", themeVersion)
.replace(":path", path)}">`;
}
} else {
cssLink = _css(path);
}
return cssLink;
};
if (Array.isArray(path)) {
return path.map(cdnPathHandle).join("");
} else {
return cdnPathHandle(path);
}
});
hexo.extend.helper.register("getThemeVersion", function () {
return themeVersion;
});
hexo.extend.helper.register("checkDeprecation", function (condition, id, message) {
if (condition) {
// Use Set to ensure each warning is only logged once per Hexo process
if (!global.deprecationWarnings) {
global.deprecationWarnings = new Set();
}
if (!global.deprecationWarnings.has(id)) {
hexo.log.warn(`${message}`);
global.deprecationWarnings.add(id);
}
return true;
}
return false;
});
hexo.extend.helper.register("configOptions", function (obj, indent = ' ') {
if (!obj || typeof obj !== 'object') return '';
return Object.entries(obj)
.filter(([key, value]) => value !== undefined && value !== null && value !== '')
.map(([key, value]) => {
if (typeof value === 'string') {
return `${indent}${key}: '${value}',`;
}
return `${indent}${key}: ${value},`;
})
.join('\n');
});

View File

@@ -0,0 +1,33 @@
hexo.extend.helper.register('waline_reaction_config', function(reactionConfig) {
if (Array.isArray(reactionConfig) && reactionConfig.length) {
return `[${reactionConfig.map(item => `'${item}'`).join(', ')}]`;
} else {
return Boolean(reactionConfig);
}
});
hexo.extend.helper.register('waline_array_config', function(arrayConfig) {
if (Array.isArray(arrayConfig) && arrayConfig.length) {
return `[${arrayConfig.map(item => `'${item}'`).join(', ')}]`;
}
return '[]';
});
hexo.extend.helper.register('waline_config_options', function(config) {
if (!config || typeof config !== 'object') return '';
const formatValue = (value) => {
if (typeof value === 'string') {
return `'${value}'`;
}
if (Array.isArray(value)) {
return `[${value.map(item => typeof item === 'string' ? `'${item}'` : item).join(', ')}]`;
}
return value;
};
return Object.entries(config)
.filter(([key, value]) => value !== undefined && value !== null && value !== '')
.map(([key, value]) => ` ${key}: ${formatValue(value)},`)
.join('\n');
});

85
node_modules/hexo-theme-redefine/scripts/modules/btn.js generated vendored Executable file
View File

@@ -0,0 +1,85 @@
"use strict";
/**
* Single Button module for Hexo theme Redefine
* Creates standalone button elements
*/
/**
* Creates a single button
*
* @param {Array} args - Button arguments (class, text, url, icon)
* @returns {String} HTML for a single button
*/
function postBtn(args) {
// Parse arguments - support both '::' and ',' as separators
let parsedArgs;
const argsStr = args.join(" ");
if (argsStr.includes("::")) {
parsedArgs = argsStr.split("::");
} else {
parsedArgs = argsStr.split(",");
}
// Default values
let cls = "";
let text = "";
let url = "";
let icon = "";
// Parse arguments based on count
switch(parsedArgs.length) {
case 4: // class, text, url, icon
cls = parsedArgs[0];
text = parsedArgs[1];
url = parsedArgs[2];
icon = parsedArgs[3];
break;
case 3:
// Check if third arg is an icon (contains fa-)
if (parsedArgs[2].includes("fa-")) {
// text, url, icon
text = parsedArgs[0];
url = parsedArgs[1];
icon = parsedArgs[2];
} else {
// class, text, url
cls = parsedArgs[0];
text = parsedArgs[1];
url = parsedArgs[2];
}
break;
case 2: // text, url
text = parsedArgs[0];
url = parsedArgs[1];
break;
case 1: // text only
text = parsedArgs[0];
break;
}
// Clean up values
cls = cls.trim();
icon = icon.trim();
text = text.trim();
url = url.trim();
// Build attributes
const hrefAttr = url ? `href='${url}'` : '';
const classAttr = cls ? `${cls}` : '';
// Build button HTML
if (icon) {
return `<a class="button ${classAttr}" ${hrefAttr} title='${text}'><i class='${icon}'></i> ${text}</a>`;
}
return `<a class="button ${classAttr}" ${hrefAttr} title='${text}'>${text}</a>`;
}
// Register Hexo tags
hexo.extend.tag.register("btn", postBtn);
hexo.extend.tag.register("button", postBtn);

71
node_modules/hexo-theme-redefine/scripts/modules/btns.js generated vendored Executable file
View File

@@ -0,0 +1,71 @@
'use strict';
/**
* Buttons module for Hexo theme Redefine
* Creates button containers and button elements
*/
/**
* Creates a buttons container
*
* @param {Array} args - Class names for the container
* @param {String} content - Button content (cells)
* @returns {String} HTML for the buttons container
*/
function postBtns(args, content) {
const classes = args.join(' ');
return `<div class="btns ${classes}">
${content}
</div>`;
}
/**
* Creates a single button cell
*
* @param {Array} args - Button properties (text, url, icon/image)
* @returns {String} HTML for a single button
*/
function postCell(args, content) {
// Parse arguments - support both '::' and ',' as separators
let parsedArgs;
const argsStr = args.join(' ');
if (argsStr.includes('::')) {
parsedArgs = argsStr.split('::');
} else {
parsedArgs = argsStr.split(',');
}
// Extract button properties
const text = (parsedArgs[0] || '').trim();
const url = (parsedArgs[1] || '').trim();
const buttonClasses = ['button'];
// Handle URL
const hrefAttr = url ? `href='${url}'` : '';
// Handle icon or image
let iconOrImage = '';
if (parsedArgs.length > 2) {
const iconOrImgSrc = parsedArgs[2].trim();
// Check if it's a FontAwesome icon
if (iconOrImgSrc.includes('fa-')) {
iconOrImage = `<i class='${iconOrImgSrc}'></i> `;
} else {
// Use specified image or default
const imgSrc = iconOrImgSrc || hexo.theme.config.default.image;
iconOrImage = `<img src='${imgSrc}'>`;
}
}
// Return complete button HTML
return `<a class="${buttonClasses.join(' ')}" ${hrefAttr} title='${text}'>${iconOrImage}${text}</a>`;
}
// Register Hexo tags
hexo.extend.tag.register('btns', postBtns, {ends: true});
hexo.extend.tag.register('buttons', postBtns, {ends: true});
hexo.extend.tag.register('cell', postCell);

34
node_modules/hexo-theme-redefine/scripts/modules/folding.js generated vendored Executable file
View File

@@ -0,0 +1,34 @@
'use strict';
const postFolding = (args, content) => {
// Parse arguments - support both '::' and ',' as delimiters
const delimiter = args.join(' ').includes('::') ? '::' : ',';
const [style, title = ''] = args.join(' ').split(delimiter).map(arg => arg.trim());
// Render markdown content
const renderedContent = hexo.render.renderSync({
text: content,
engine: 'markdown'
}).split('\n').join('');
// Replace heading tags with paragraph tags that have heading classes
const processedContent = renderedContent.replace(
/<(h[1-6])>/g,
(_, tag) => `<p class='${tag}'>`
).replace(
/<\/(h[1-6])>/g,
() => '</p>'
);
// Build the HTML with or without custom style
const styleAttr = style ? ` class="${style}"` : '';
return `<details${styleAttr} data-header-exclude>
<summary><i class="fa-solid fa-chevron-right"></i>${title} </summary>
<div class='content'>
${processedContent}
</div>
</details>`;
};
hexo.extend.tag.register('folding', postFolding, {ends: true});

View File

@@ -0,0 +1,72 @@
function postNoteLarge(args, content) {
if (args.length === 0) {
args.push("default", "Warning");
}
let icon = "";
let title = "";
// Extract color from args[0]
const color = args[0];
// Process all arguments after color to build icon and title
const remainingArgs = args.slice(1);
// Find all fa- prefixed arguments
const faArgs = [];
const faIndices = [];
remainingArgs.forEach((arg, index) => {
if (arg && arg.startsWith('fa-')) {
faArgs.push(arg);
faIndices.push(index);
}
});
// Handle FontAwesome icons based on how many fa- args we found
if (faArgs.length === 2) {
// Two fa- args: first is style, second is icon
icon = `<i class="notel-icon ${faArgs[0]} ${faArgs[1]}"></i>`;
// Remove both FA args (remove higher index first to preserve positions)
remainingArgs.splice(faIndices[1], 1);
remainingArgs.splice(faIndices[0], 1);
} else if (faArgs.length === 1) {
// One fa- arg: it's the icon, default to fa-solid
icon = `<i class="notel-icon fa-solid ${faArgs[0]}"></i>`;
// Remove the FA arg
remainingArgs.splice(faIndices[0], 1);
}
// Join all remaining args as the title
title = remainingArgs.join(" ");
// If no title was provided, set a default
if (!title) {
title = "Note";
}
return `
<div class="note-large ${color}">
<div class="notel-title rounded-t-lg p-3 font-bold text-lg flex flex-row gap-2 items-center">
${icon}${hexo.render.renderSync({
text: title,
engine: "markdown",
})}
</div>
<div class="notel-content">
${hexo.render.renderSync({
text: content,
engine: "markdown",
})}
</div>
</div>`;
}
hexo.extend.tag.register("noteL", postNoteLarge, { ends: true });
hexo.extend.tag.register("notel", postNoteLarge, { ends: true });
hexo.extend.tag.register("notelarge", postNoteLarge, { ends: true });
hexo.extend.tag.register("notel-large", postNoteLarge, { ends: true });
hexo.extend.tag.register("notes-large", postNoteLarge, { ends: true });
hexo.extend.tag.register("subwarning", postNoteLarge, { ends: true });

View File

@@ -0,0 +1,64 @@
function postNote(args, content) {
if (args.length === 0) {
args.push("default");
}
let icon = "";
// Extract style classes from all args except potential icon classes
let classes = [];
const remainingArgs = [...args]; // Copy args array
// Add first arg as main class
if (remainingArgs.length > 0) {
classes.push(remainingArgs[0]);
remainingArgs.shift();
}
// Find all fa- prefixed arguments
const faArgs = [];
const faIndices = [];
remainingArgs.forEach((arg, index) => {
if (arg && arg.startsWith('fa-')) {
faArgs.push(arg);
faIndices.push(index);
}
});
// Handle FontAwesome icons based on how many fa- args we found
if (faArgs.length === 2) {
// Two fa- args: first is style, second is icon
icon = `<i class="note-icon ${faArgs[0]} ${faArgs[1]}"></i>`;
// Remove both FA args (remove higher index first to preserve positions)
remainingArgs.splice(faIndices[1], 1);
remainingArgs.splice(faIndices[0], 1);
} else if (faArgs.length === 1) {
// One fa- arg: it's the icon, default to fa-solid
icon = `<i class="note-icon fa-solid ${faArgs[0]}"></i>`;
// Remove the FA arg
remainingArgs.splice(faIndices[0], 1);
}
// Add any remaining args as additional classes
classes = classes.concat(remainingArgs);
// Add icon-padding class if icon exists
if (icon) {
classes.push("icon-padding");
}
return `
<div class="note p-4 mb-4 rounded-small ${classes.join(" ")}">
${icon}${hexo.render.renderSync({
text: content,
engine: "markdown",
})}
</div>`;
}
hexo.extend.tag.register("note", postNote, { ends: true });
hexo.extend.tag.register("notes", postNote, { ends: true });
hexo.extend.tag.register("subnote", postNote, { ends: true });

99
node_modules/hexo-theme-redefine/scripts/modules/tabs.js generated vendored Executable file
View File

@@ -0,0 +1,99 @@
/**
* Module: Tabs
* hexo-theme-redefine
* By Evan
*/
'use strict';
const TAB_BLOCK_REGEX = /<!--\s*tab (.*?)\s*-->\n([\w\W\s\S]*?)<!--\s*endtab\s*-->/g;
const APLAYER_TAG_REGEX = /\<div.*class=\"aplayer aplayer-tag-marker\"(.|\n)*\<\/script\>/g;
const FANCYBOX_TAG_REGEX = /\<div.*galleryFlag(.|\n)*\<\/span\>\<\/div\>\<\/div\>/g;
function parseArgs(args) {
if (/::/g.test(args)) {
return args.join(' ').split('::');
} else {
return args.join(' ').split(',');
}
}
function extractMatches(content) {
const matches = [];
let match;
while ((match = TAB_BLOCK_REGEX.exec(content)) !== null) {
matches.push(match[1]);
matches.push(match[2]);
}
return matches;
}
function processAplayerTag(content) {
let aplayerTag = 0;
if (/class="aplayer aplayer-tag-marker"/g.test(content)) {
aplayerTag = APLAYER_TAG_REGEX.exec(content)[0];
content = content.replace(APLAYER_TAG_REGEX, "@aplayerTag@");
}
return { content, aplayerTag };
}
function processFancyboxTag(content) {
let fancyboxTag = 0;
if (/galleryFlag/g.test(content)) {
fancyboxTag = FANCYBOX_TAG_REGEX.exec(content)[0];
content = content.replace(FANCYBOX_TAG_REGEX, "@fancyboxTag@");
}
return { content, fancyboxTag };
}
function buildTabNavAndContent(matches, tabName, tabActive) {
let tabNav = '';
let tabContent = '';
for (let i = 0; i < matches.length; i += 2) {
const tabParameters = matches[i].split('@');
const postContent = matches[i + 1];
const tabCaption = tabParameters[0] || '';
const tabIcon = tabParameters[1] || '';
const tabHref = (tabName + ' ' + (i / 2 + 1)).toLowerCase().split(' ').join('-');
const { content: contentWithAplayerTag, aplayerTag } = processAplayerTag(postContent);
const { content: contentWithFancyboxTag, fancyboxTag } = processFancyboxTag(contentWithAplayerTag);
const renderedContent = hexo.render.renderSync({ text: contentWithFancyboxTag, engine: 'markdown' }).trim();
const finalContent = renderedContent.replace(/\<pre\>\<code\>.*@aplayerTag@.*\<\/code><\/pre>/, aplayerTag)
.replace(/.*@fancyboxTag@.*/, fancyboxTag);
const isActive = (tabActive > 0 && tabActive === (i / 2 + 1)) || (tabActive === 0 && i === 0) ? ' active' : '';
tabNav += `<li class="tab${isActive}"><a class="#${tabHref}">${tabIcon + tabCaption.trim()}</a></li>`;
tabContent += `<div class="tab-pane${isActive}" id="${tabHref}">${finalContent}</div>`;
}
return { tabNav, tabContent };
}
function postTabs(args, content) {
const [tabName, tabActive] = parseArgs(args);
const activeTabIndex = Number(tabActive) || 0;
!tabName && hexo.log.warn('Tabs block must have unique name!');
const matches = extractMatches(content);
const { tabNav, tabContent } = buildTabNavAndContent(matches, tabName, activeTabIndex);
const finalTabNav = `<ul class="nav-tabs">${tabNav}</ul>`;
const finalTabContent = `<div class="tab-content">${tabContent}</div>`;
return `<div class="tabs" id="tab-${tabName.toLowerCase().split(' ').join('-')}">${finalTabNav + finalTabContent}</div>`;
}
hexo.extend.tag.register('tabs', postTabs, { ends: true });
hexo.extend.tag.register('subtabs', postTabs, { ends: true });
hexo.extend.tag.register('subsubtabs', postTabs, { ends: true });