hexo-theme-gnix 9.0.0 → 11.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -6
- package/include/hexo/feed.js +5 -5
- package/include/hexo/filter.js +25 -1
- package/include/hexo/generator/archive.js +116 -0
- package/include/hexo/generator/home.js +64 -0
- package/include/hexo/generator/index.js +82 -0
- package/include/hexo/generator/md_generator.js +87 -0
- package/include/hexo/generator/page.js +55 -0
- package/include/hexo/generator/tag.js +84 -0
- package/include/hexo/helper.js +38 -0
- package/include/hexo/i18n.js +183 -0
- package/include/hexo/renderer.js +131 -0
- package/include/util/article_font.js +132 -0
- package/include/util/i18n.js +280 -0
- package/include/util/theme.js +84 -0
- package/languages/en.yml +28 -0
- package/languages/zh-CN.yml +28 -0
- package/layout/archive.jsx +131 -127
- package/layout/common/article.jsx +283 -16
- package/layout/common/article_info.jsx +339 -0
- package/layout/common/article_media.jsx +11 -4
- package/layout/common/comment.jsx +15 -7
- package/layout/common/footer.jsx +6 -5
- package/layout/common/head.jsx +121 -32
- package/layout/common/navbar.jsx +195 -65
- package/layout/common/theme_selector.jsx +16 -14
- package/layout/layout.jsx +43 -5
- package/layout/misc/open_graph.jsx +162 -66
- package/layout/misc/paginator.jsx +2 -8
- package/layout/plugin/cookie_consent.jsx +252 -53
- package/layout/plugin/swup.jsx +1 -1
- package/layout/search/insight.jsx +1 -1
- package/layout/tag.jsx +3 -2
- package/layout/tags.jsx +81 -73
- package/package.json +18 -5
- package/scripts/index.js +1 -0
- package/source/css/archive.css +225 -180
- package/source/css/default.css +1162 -98
- package/source/css/responsive.css +426 -0
- package/source/css/shiki/shiki.css +12 -2081
- package/source/css/tags.css +183 -0
- package/source/css/twikoo.css +1049 -1045
- package/source/img/favicon.svg +1 -6
- package/source/img/og_image.webp +0 -0
- package/source/js/article-font-utils.js +99 -0
- package/source/js/busuanzi.js +91 -24
- package/source/js/components/chat.js +169 -50
- package/source/js/components/image-carousel.js +152 -108
- package/source/js/components/sidenote.js +210 -0
- package/source/js/components/text-image-section.js +78 -90
- package/source/js/components/theme-stacked.js +65 -33
- package/source/js/components/tree.js +30 -16
- package/source/js/decrypt.js +7 -2
- package/source/js/main.js +428 -5
- package/source/js/swup.js +39 -0
- package/source/js/theme-selector.js +26 -16
- package/include/hexo/generator.js +0 -53
- package/layout/misc/article_licensing.jsx +0 -99
- package/source/css/responsive/desktop.css +0 -36
- package/source/css/responsive/mobile.css +0 -29
- package/source/css/responsive/tablet.css +0 -43
- package/source/css/responsive/touch.css +0 -155
- package/source/img/logo.svg +0 -9
- package/source/js/archive-breadcrumb.js +0 -132
- package/source/js/host/cookieconsent/3.1.1/build/cookieconsent.min.css +0 -6
- package/source/js/host/cookieconsent/3.1.1/build/cookieconsent.min.js +0 -1
- package/source/js/swup.bundle.js +0 -1
package/README.md
CHANGED
|
@@ -12,8 +12,8 @@ hexo config theme gnix
|
|
|
12
12
|
Support multiple light and dark themes:
|
|
13
13
|
|
|
14
14
|
- **System Theme**: Follow system theme automatically, use `Nord` for light mode and `Mocha` for dark mode by default
|
|
15
|
-
- **Light Themes**: `Nord`, `
|
|
16
|
-
- **Dark Themes**: `Catppuccin Mocha`, `
|
|
15
|
+
- **Light Themes**: `Nord`, `Catppuccin Latte`, `Song Porcelain`
|
|
16
|
+
- **Dark Themes**: `Catppuccin Mocha`, `Nord Night`, `Rosé Pine`, `Tokyo Night`
|
|
17
17
|
|
|
18
18
|
<table border="1">
|
|
19
19
|
<tr>
|
|
@@ -25,10 +25,6 @@ Support multiple light and dark themes:
|
|
|
25
25
|
|
|
26
26
|
## Components
|
|
27
27
|
|
|
28
|
-
```shell
|
|
29
|
-
bun i hexo-renderer-markdown-exit
|
|
30
|
-
```
|
|
31
|
-
|
|
32
28
|
### Table, Math, Quote, Callout & Tabs, Highlight
|
|
33
29
|
|
|
34
30
|
<table>
|
|
@@ -64,3 +60,5 @@ bun i hexo-renderer-markdown-exit
|
|
|
64
60
|
- [ppoffice/hexo-theme-icarus: A simple, delicate, and modern theme for the static site generator Hexo.](https://github.com/ppoffice/hexo-theme-icarus)
|
|
65
61
|
- [D0n9X1n/hexo-blog-encrypt: Yet, just another hexo plugin for security.](https://github.com/D0n9X1n/hexo-blog-encrypt)
|
|
66
62
|
- [hexojs/hexo-generator-feed: Feed generator for Hexo.](https://github.com/hexojs/hexo-generator-feed)
|
|
63
|
+
- [hexojs/hexo-generator-tag: Tag generator for Hexo.](https://github.com/hexojs/hexo-generator-tag)
|
|
64
|
+
- [hexojs/hexo-generator-archive: Archive generator for Hexo.](https://github.com/hexojs/hexo-generator-archive)
|
package/include/hexo/feed.js
CHANGED
|
@@ -120,15 +120,15 @@ module.exports = (hexo) => {
|
|
|
120
120
|
const currentYear = new Date().getFullYear();
|
|
121
121
|
|
|
122
122
|
return {
|
|
123
|
-
title: context.title,
|
|
124
|
-
description: context.subtitle || context.description,
|
|
123
|
+
title: context.config.title,
|
|
124
|
+
description: context.config.subtitle || context.config.description,
|
|
125
125
|
url,
|
|
126
126
|
feedUrl,
|
|
127
127
|
icon,
|
|
128
128
|
hub,
|
|
129
|
-
language: context.language,
|
|
130
|
-
author: { name: context.author, email: context.email },
|
|
131
|
-
copyright: context.author && `All rights reserved ${currentYear}, ${context.author}`,
|
|
129
|
+
language: context.config.language,
|
|
130
|
+
author: { name: context.config.author, email: context.config.email },
|
|
131
|
+
copyright: context.config.author && `All rights reserved ${currentYear}, ${context.config.author}`,
|
|
132
132
|
updated: latestPost.updated ? latestPost.updated.toDate() : latestPost.date.toDate(),
|
|
133
133
|
};
|
|
134
134
|
}
|
package/include/hexo/filter.js
CHANGED
|
@@ -26,9 +26,10 @@ module.exports = (hexo) => {
|
|
|
26
26
|
};
|
|
27
27
|
|
|
28
28
|
function stripConfig(source, reservedKeys) {
|
|
29
|
+
const localOnlyKeys = ["i18n"];
|
|
29
30
|
const result = {};
|
|
30
31
|
Object.keys(source)
|
|
31
|
-
.filter((key) => !key.startsWith("_") && !reservedKeys.includes(key) && typeof source[key] !== "function")
|
|
32
|
+
.filter((key) => !key.startsWith("_") && !reservedKeys.includes(key) && !localOnlyKeys.includes(key) && typeof source[key] !== "function")
|
|
32
33
|
.forEach((key) => {
|
|
33
34
|
result[key] = source[key];
|
|
34
35
|
});
|
|
@@ -66,4 +67,27 @@ module.exports = (hexo) => {
|
|
|
66
67
|
|
|
67
68
|
return locals;
|
|
68
69
|
});
|
|
70
|
+
|
|
71
|
+
hexo.extend.filter.register("after_render:html", (data) => {
|
|
72
|
+
if (!data.includes("data-page-head") || !data.includes("</head>")) {
|
|
73
|
+
return data;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const seenHeadTags = new Set();
|
|
77
|
+
const headTags = [];
|
|
78
|
+
const html = data.replace(/<link\b(?=[^>]*\sdata-page-head(?:=(?:"[^"]*"|'[^']*'|[^\s>]+))?)[^>]*>/gi, (tag) => {
|
|
79
|
+
const cleanTag = tag.replace(/\sdata-page-head(?:=(?:"[^"]*"|'[^']*'|[^\s>]+))?/i, "");
|
|
80
|
+
if (!seenHeadTags.has(cleanTag)) {
|
|
81
|
+
seenHeadTags.add(cleanTag);
|
|
82
|
+
headTags.push(cleanTag);
|
|
83
|
+
}
|
|
84
|
+
return "";
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
if (!headTags.length) {
|
|
88
|
+
return data;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return html.replace("</head>", `${headTags.join("")}</head>`);
|
|
92
|
+
});
|
|
69
93
|
};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
const pagination = require("hexo-pagination");
|
|
2
|
+
const { filterByLanguage, getLanguage, getLanguageBasePath, getLanguageKeys, isI18nEnabled } = require("../../util/i18n");
|
|
3
|
+
|
|
4
|
+
const fmtNum = (num) => num.toString().padStart(2, "0");
|
|
5
|
+
|
|
6
|
+
module.exports = (hexo) => {
|
|
7
|
+
hexo.extend.generator.register("archive", function (locals) {
|
|
8
|
+
const { config } = this;
|
|
9
|
+
const fullConfig = Object.assign({}, config, config.theme_config, hexo.theme.config);
|
|
10
|
+
const themeConfig = hexo.theme.config.archive_generator || {};
|
|
11
|
+
let archiveDir = config.archive_dir || "archives";
|
|
12
|
+
const paginationDir = config.pagination_dir || "page";
|
|
13
|
+
const perPage = themeConfig.per_page ?? 0;
|
|
14
|
+
const { Query } = this.model("Post");
|
|
15
|
+
const result = [];
|
|
16
|
+
|
|
17
|
+
if (!archiveDir.endsWith("/")) archiveDir += "/";
|
|
18
|
+
|
|
19
|
+
function generate(path, posts, options = {}) {
|
|
20
|
+
options.archive = true;
|
|
21
|
+
result.push(
|
|
22
|
+
...pagination(path, posts, {
|
|
23
|
+
perPage,
|
|
24
|
+
layout: ["archive", "index"],
|
|
25
|
+
format: `${paginationDir}/%d/`,
|
|
26
|
+
data: options,
|
|
27
|
+
}),
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function generateLanguageArchives(langKey = null) {
|
|
32
|
+
const languageBase = langKey ? getLanguageBasePath(fullConfig, langKey) : "";
|
|
33
|
+
const allPostsSource = locals.posts.sort(themeConfig.order_by || "-date");
|
|
34
|
+
const allPosts = langKey ? filterByLanguage(allPostsSource, langKey, fullConfig) : allPostsSource;
|
|
35
|
+
|
|
36
|
+
if (!allPosts.length) return;
|
|
37
|
+
|
|
38
|
+
const baseArchiveDir = languageBase + archiveDir;
|
|
39
|
+
const languageData = langKey
|
|
40
|
+
? {
|
|
41
|
+
i18n_lang: langKey,
|
|
42
|
+
lang: getLanguage(fullConfig, langKey).locale,
|
|
43
|
+
}
|
|
44
|
+
: {};
|
|
45
|
+
|
|
46
|
+
generate(baseArchiveDir, allPosts, languageData);
|
|
47
|
+
|
|
48
|
+
const yearly = themeConfig.yearly ?? true;
|
|
49
|
+
const monthly = themeConfig.monthly ?? true;
|
|
50
|
+
const daily = themeConfig.daily ?? false;
|
|
51
|
+
|
|
52
|
+
if (!yearly) return;
|
|
53
|
+
|
|
54
|
+
const posts = {};
|
|
55
|
+
|
|
56
|
+
allPosts.forEach((post) => {
|
|
57
|
+
const date = post.date;
|
|
58
|
+
const year = date.year();
|
|
59
|
+
const month = date.month() + 1;
|
|
60
|
+
|
|
61
|
+
if (!Object.hasOwn(posts, year)) {
|
|
62
|
+
posts[year] = [[], [], [], [], [], [], [], [], [], [], [], [], []];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
posts[year][0].push(post);
|
|
66
|
+
posts[year][month].push(post);
|
|
67
|
+
|
|
68
|
+
if (daily) {
|
|
69
|
+
const day = date.date();
|
|
70
|
+
if (!Object.hasOwn(posts[year][month], "day")) {
|
|
71
|
+
posts[year][month].day = {};
|
|
72
|
+
}
|
|
73
|
+
(posts[year][month].day[day] || (posts[year][month].day[day] = [])).push(post);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const years = Object.keys(posts);
|
|
78
|
+
|
|
79
|
+
for (let i = 0, len = years.length; i < len; i++) {
|
|
80
|
+
const year = +years[i];
|
|
81
|
+
const data = posts[year];
|
|
82
|
+
const url = `${baseArchiveDir + year}/`;
|
|
83
|
+
if (!data[0].length) continue;
|
|
84
|
+
|
|
85
|
+
generate(url, new Query(data[0]), Object.assign({ year }, languageData));
|
|
86
|
+
|
|
87
|
+
if (!monthly && !daily) continue;
|
|
88
|
+
|
|
89
|
+
for (let month = 1; month <= 12; month++) {
|
|
90
|
+
const monthData = data[month];
|
|
91
|
+
if (!monthData.length) continue;
|
|
92
|
+
|
|
93
|
+
if (monthly) {
|
|
94
|
+
generate(`${url + fmtNum(month)}/`, new Query(monthData), Object.assign({ year, month }, languageData));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (!daily) continue;
|
|
98
|
+
|
|
99
|
+
for (let day = 1; day <= 31; day++) {
|
|
100
|
+
const dayData = monthData.day[day];
|
|
101
|
+
if (!dayData || !dayData.length) continue;
|
|
102
|
+
generate(`${url + fmtNum(month)}/${fmtNum(day)}/`, new Query(dayData), Object.assign({ year, month, day }, languageData));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (isI18nEnabled(fullConfig)) {
|
|
109
|
+
getLanguageKeys(fullConfig).forEach((langKey) => generateLanguageArchives(langKey));
|
|
110
|
+
return result;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
generateLanguageArchives();
|
|
114
|
+
return result;
|
|
115
|
+
});
|
|
116
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
const pagination = require("hexo-pagination");
|
|
2
|
+
const { filterByLanguage, getDefaultLanguageKey, getLanguage, getLanguageBasePath, getLanguageKeys, isI18nEnabled } = require("../../util/i18n");
|
|
3
|
+
|
|
4
|
+
function redirectTo(path) {
|
|
5
|
+
const target = path.startsWith("/") ? path : `/${path}`;
|
|
6
|
+
return `<!doctype html><html><head><meta charset="utf-8"><meta name="robots" content="noindex"><meta http-equiv="refresh" content="0;url=${target}"><link rel="canonical" href="${target}"></head><body><a href="${target}">Continue</a></body></html>`;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
module.exports = (hexo) => {
|
|
10
|
+
hexo.extend.generator.register("index", function (locals) {
|
|
11
|
+
const config = this.config;
|
|
12
|
+
const fullConfig = Object.assign({}, config, config.theme_config, hexo.theme.config);
|
|
13
|
+
const themeConfig = hexo.theme.config.index_generator || {};
|
|
14
|
+
const orderBy = themeConfig.order_by ?? "-date";
|
|
15
|
+
const perPage = themeConfig.per_page ?? config.per_page ?? 16;
|
|
16
|
+
const layout = themeConfig.layout ?? ["index", "archive"];
|
|
17
|
+
const paginationDir = themeConfig.pagination_dir ?? config.pagination_dir ?? "page";
|
|
18
|
+
const path = themeConfig.path ?? "";
|
|
19
|
+
|
|
20
|
+
if (!isI18nEnabled(fullConfig)) {
|
|
21
|
+
const posts = locals.posts.sort(orderBy);
|
|
22
|
+
posts.data.sort((a, b) => (b.sticky || 0) - (a.sticky || 0));
|
|
23
|
+
|
|
24
|
+
return pagination(path, posts, {
|
|
25
|
+
perPage,
|
|
26
|
+
layout,
|
|
27
|
+
format: `${paginationDir}/%d/`,
|
|
28
|
+
data: {
|
|
29
|
+
__index: true,
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const result = [];
|
|
35
|
+
const defaultLanguageBase = getLanguageBasePath(fullConfig, getDefaultLanguageKey(fullConfig));
|
|
36
|
+
|
|
37
|
+
if (defaultLanguageBase) {
|
|
38
|
+
result.push({
|
|
39
|
+
path: "index.html",
|
|
40
|
+
data: redirectTo(defaultLanguageBase),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
getLanguageKeys(fullConfig).forEach((langKey) => {
|
|
45
|
+
const posts = filterByLanguage(locals.posts.sort(orderBy), langKey, fullConfig);
|
|
46
|
+
posts.data.sort((a, b) => (b.sticky || 0) - (a.sticky || 0));
|
|
47
|
+
|
|
48
|
+
result.push(
|
|
49
|
+
...pagination(getLanguageBasePath(fullConfig, langKey) + path, posts, {
|
|
50
|
+
perPage,
|
|
51
|
+
layout,
|
|
52
|
+
format: `${paginationDir}/%d/`,
|
|
53
|
+
data: {
|
|
54
|
+
__index: true,
|
|
55
|
+
i18n_lang: langKey,
|
|
56
|
+
lang: getLanguage(fullConfig, langKey).locale,
|
|
57
|
+
},
|
|
58
|
+
}),
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
return result;
|
|
63
|
+
});
|
|
64
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
const util = require("hexo-util");
|
|
2
|
+
const { filterByLanguage, getLanguageBasePath, getLanguageKeys, getLocalizedTagPath, isI18nEnabled } = require("../../util/i18n");
|
|
3
|
+
|
|
4
|
+
function minify(str) {
|
|
5
|
+
return util
|
|
6
|
+
.stripHTML(str)
|
|
7
|
+
.trim()
|
|
8
|
+
.replace(/\s+/g, " ")
|
|
9
|
+
.replace(/&#(?:x([\da-fA-F]+)|([\d]+));/g, (_, hex, dec) => {
|
|
10
|
+
return String.fromCharCode(parseInt(hex || dec, hex ? 16 : 10));
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function mapPost(post, url_for) {
|
|
15
|
+
return {
|
|
16
|
+
title: util.escapeHTML(post.title).trim(),
|
|
17
|
+
text: post.password ? "该文章需要密码" : minify(post.content),
|
|
18
|
+
link: url_for(post.path),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function mapTag(tag, url_for, langKey = null, config = {}) {
|
|
23
|
+
return {
|
|
24
|
+
name: util.escapeHTML(tag.name).trim(),
|
|
25
|
+
slug: minify(tag.slug),
|
|
26
|
+
link: url_for(langKey ? `/${getLocalizedTagPath(tag, langKey, config)}` : tag.path),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
module.exports = (hexo) => {
|
|
31
|
+
const mdConfig = hexo.theme.config.md_generator || {};
|
|
32
|
+
if (mdConfig.enabled !== false) {
|
|
33
|
+
require("./md_generator")(hexo);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
hexo.extend.generator.register("insight", function (locals) {
|
|
37
|
+
const url_for = hexo.extend.helper.get("url_for").bind(this);
|
|
38
|
+
const fullConfig = Object.assign({}, this.config, this.config.theme_config, hexo.theme.config);
|
|
39
|
+
|
|
40
|
+
if (isI18nEnabled(fullConfig)) {
|
|
41
|
+
return getLanguageKeys(fullConfig).map((langKey) => {
|
|
42
|
+
const posts = filterByLanguage(locals.posts, langKey, fullConfig);
|
|
43
|
+
const tags = locals.tags
|
|
44
|
+
.filter((tag) => filterByLanguage(tag.posts, langKey, fullConfig).length)
|
|
45
|
+
.map((tag) => mapTag(tag, url_for, langKey, fullConfig));
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
path: `${getLanguageBasePath(fullConfig, langKey)}content.json`,
|
|
49
|
+
data: JSON.stringify({
|
|
50
|
+
posts: posts.map((post) => mapPost(post, url_for)),
|
|
51
|
+
tags,
|
|
52
|
+
}),
|
|
53
|
+
};
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
path: "/content.json",
|
|
59
|
+
data: JSON.stringify({
|
|
60
|
+
posts: locals.posts.map((post) => mapPost(post, url_for)),
|
|
61
|
+
tags: locals.tags.map((tag) => mapTag(tag, url_for)),
|
|
62
|
+
}),
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const tagConfig = hexo.theme.config.tag_generator || {};
|
|
67
|
+
if (tagConfig.enabled !== false) {
|
|
68
|
+
require("./tag")(hexo);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const archiveConfig = hexo.theme.config.archive_generator || {};
|
|
72
|
+
if (archiveConfig.enabled !== false) {
|
|
73
|
+
require("./archive")(hexo);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const indexConfig = hexo.theme.config.index_generator || {};
|
|
77
|
+
if (indexConfig.enabled !== false) {
|
|
78
|
+
require("./home")(hexo);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
require("./page")(hexo);
|
|
82
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
const fs = require("node:fs");
|
|
2
|
+
const path = require("node:path");
|
|
3
|
+
const { Readable } = require("node:stream");
|
|
4
|
+
|
|
5
|
+
const UTF8_BOM = Buffer.from([0xef, 0xbb, 0xbf]);
|
|
6
|
+
const MARKDOWN_EXTENSIONS = new Set([".md", ".markdown", ".mdown", ".mkd", ".mkdn"]);
|
|
7
|
+
|
|
8
|
+
function getPosts(posts) {
|
|
9
|
+
if (typeof posts?.toArray === "function") return posts.toArray();
|
|
10
|
+
if (Array.isArray(posts?.data)) return posts.data;
|
|
11
|
+
if (Array.isArray(posts)) return posts;
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function hasPassword(post) {
|
|
16
|
+
return Boolean(post?.password || post?.password === 0);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function isMarkdownSource(post) {
|
|
20
|
+
const source = post?.source || post?.full_source;
|
|
21
|
+
if (typeof source !== "string") return false;
|
|
22
|
+
return MARKDOWN_EXTENSIONS.has(path.extname(source).toLowerCase());
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function getHtmlRoutePath(routePath) {
|
|
26
|
+
if (typeof routePath !== "string") return null;
|
|
27
|
+
const normalized = routePath.replace(/^\/+/, "").replace(/\\/g, "/").replace(/\?.*$/, "");
|
|
28
|
+
if (!normalized || normalized.endsWith("/")) {
|
|
29
|
+
return `${normalized}index.html`;
|
|
30
|
+
}
|
|
31
|
+
return normalized;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function getMarkdownOutputPath(post) {
|
|
35
|
+
if (hasPassword(post) || !isMarkdownSource(post)) return null;
|
|
36
|
+
|
|
37
|
+
const htmlPath = getHtmlRoutePath(post?.path);
|
|
38
|
+
if (!htmlPath) return null;
|
|
39
|
+
|
|
40
|
+
const ext = path.posix.extname(htmlPath);
|
|
41
|
+
if (!ext) return `${htmlPath}.md`;
|
|
42
|
+
return `${htmlPath.slice(0, -ext.length)}.md`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function readMarkdownFile(sourcePath) {
|
|
46
|
+
let content;
|
|
47
|
+
try {
|
|
48
|
+
content = fs.readFileSync(sourcePath);
|
|
49
|
+
} catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const hasBom = content.length >= 3 && content.subarray(0, 3).equals(UTF8_BOM);
|
|
54
|
+
return hasBom ? content : Buffer.concat([UTF8_BOM, content]);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = (hexo) => {
|
|
58
|
+
hexo.extend.generator.register("md_generator", (locals) =>
|
|
59
|
+
getPosts(locals.posts)
|
|
60
|
+
.map((post) => {
|
|
61
|
+
const markdownPath = getMarkdownOutputPath(post);
|
|
62
|
+
const sourcePath = post?.full_source;
|
|
63
|
+
|
|
64
|
+
if (!markdownPath || typeof sourcePath !== "string") {
|
|
65
|
+
delete post.markdown_path;
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const data = readMarkdownFile(sourcePath);
|
|
70
|
+
if (!data) {
|
|
71
|
+
delete post.markdown_path;
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
post.markdown_path = markdownPath;
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
path: markdownPath,
|
|
79
|
+
data: {
|
|
80
|
+
data: () => Readable.from(data),
|
|
81
|
+
modified: true,
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
})
|
|
85
|
+
.filter(Boolean),
|
|
86
|
+
);
|
|
87
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const { getI18nKey, getLanguage, getLanguageBasePath, getPageLanguageKey, inferI18nKeyFromSource, isI18nEnabled, localizePath, trimSlashes } = require("../../util/i18n");
|
|
2
|
+
|
|
3
|
+
function getLocalizedPagePath(page, langKey, config) {
|
|
4
|
+
const sourcePrefix = `${trimSlashes(langKey)}/`;
|
|
5
|
+
let route = page.path || "";
|
|
6
|
+
|
|
7
|
+
if (route.startsWith(sourcePrefix)) {
|
|
8
|
+
route = route.slice(sourcePrefix.length);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return trimSlashes(localizePath(`/${route}`, langKey, config));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function decorateLocalizedPage(page, langKey, config) {
|
|
15
|
+
const language = getLanguage(config, langKey);
|
|
16
|
+
|
|
17
|
+
page.i18n_path = getLanguageBasePath(config, langKey);
|
|
18
|
+
page.i18n_lang = page.i18n_lang || langKey;
|
|
19
|
+
page.lang = language.locale;
|
|
20
|
+
page.language = language.locale;
|
|
21
|
+
page.i18n_key = page.i18n_key || getI18nKey(page) || inferI18nKeyFromSource(page.source);
|
|
22
|
+
page.path = getLocalizedPagePath(page, langKey, config);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
module.exports = (hexo) => {
|
|
26
|
+
hexo.extend.generator.register("page", function (locals) {
|
|
27
|
+
const config = Object.assign({}, this.config, this.config.theme_config, hexo.theme.config);
|
|
28
|
+
const i18nEnabled = isI18nEnabled(config);
|
|
29
|
+
|
|
30
|
+
return locals.pages.map((page) => {
|
|
31
|
+
const { layout } = page;
|
|
32
|
+
|
|
33
|
+
if (i18nEnabled) {
|
|
34
|
+
decorateLocalizedPage(page, getPageLanguageKey(page, config), config);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!layout || layout === "false" || layout === "off") {
|
|
38
|
+
return {
|
|
39
|
+
path: page.path,
|
|
40
|
+
data: page.content,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const layouts = ["page", "post", "index"];
|
|
45
|
+
if (layout !== "page") layouts.unshift(layout);
|
|
46
|
+
|
|
47
|
+
page.__page = true;
|
|
48
|
+
return {
|
|
49
|
+
path: page.path,
|
|
50
|
+
layout: layouts,
|
|
51
|
+
data: page,
|
|
52
|
+
};
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
const pagination = require("hexo-pagination");
|
|
2
|
+
const { filterByLanguage, getLanguage, getLanguageBasePath, getLanguageKeys, getLocalizedTagPath, isI18nEnabled } = require("../../util/i18n");
|
|
3
|
+
|
|
4
|
+
module.exports = (hexo) => {
|
|
5
|
+
hexo.extend.generator.register("tag", function (locals) {
|
|
6
|
+
const config = this.config;
|
|
7
|
+
const fullConfig = Object.assign({}, config, config.theme_config, hexo.theme.config);
|
|
8
|
+
const themeConfig = hexo.theme.config.tag_generator || {};
|
|
9
|
+
const perPage = themeConfig.per_page ?? 10;
|
|
10
|
+
const orderBy = themeConfig.order_by ?? "-date";
|
|
11
|
+
const paginationDir = config.pagination_dir || "page";
|
|
12
|
+
const tags = locals.tags;
|
|
13
|
+
const result = [];
|
|
14
|
+
|
|
15
|
+
function generateTagPages(langKey = null) {
|
|
16
|
+
const languageData = langKey
|
|
17
|
+
? {
|
|
18
|
+
i18n_lang: langKey,
|
|
19
|
+
lang: getLanguage(fullConfig, langKey).locale,
|
|
20
|
+
}
|
|
21
|
+
: {};
|
|
22
|
+
|
|
23
|
+
const languageTags = [];
|
|
24
|
+
|
|
25
|
+
tags.forEach((tag) => {
|
|
26
|
+
if (!tag.length) return;
|
|
27
|
+
|
|
28
|
+
const postsSource = tag.posts.sort(orderBy);
|
|
29
|
+
const posts = langKey ? filterByLanguage(postsSource, langKey, fullConfig) : postsSource;
|
|
30
|
+
if (!posts.length) return;
|
|
31
|
+
|
|
32
|
+
languageTags.push(tag);
|
|
33
|
+
|
|
34
|
+
const data = pagination(langKey ? getLocalizedTagPath(tag, langKey, fullConfig) : tag.path, posts, {
|
|
35
|
+
perPage,
|
|
36
|
+
layout: ["tag", "archive", "index"],
|
|
37
|
+
format: `${paginationDir}/%d/`,
|
|
38
|
+
data: Object.assign(
|
|
39
|
+
{
|
|
40
|
+
tag: tag.name,
|
|
41
|
+
},
|
|
42
|
+
languageData,
|
|
43
|
+
),
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
result.push(...data);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const tagDir = config.tag_dir || "tags";
|
|
50
|
+
const tagDirWithSlash = tagDir.endsWith("/") ? tagDir : `${tagDir}/`;
|
|
51
|
+
const indexPath = langKey ? `${getLanguageBasePath(fullConfig, langKey) + tagDirWithSlash}` : tagDirWithSlash;
|
|
52
|
+
|
|
53
|
+
result.push({
|
|
54
|
+
path: indexPath,
|
|
55
|
+
layout: ["tags"],
|
|
56
|
+
data: Object.assign(
|
|
57
|
+
{
|
|
58
|
+
base: indexPath,
|
|
59
|
+
total: 1,
|
|
60
|
+
current: 1,
|
|
61
|
+
current_url: indexPath,
|
|
62
|
+
prev: 0,
|
|
63
|
+
prev_link: "",
|
|
64
|
+
next: 0,
|
|
65
|
+
next_link: "",
|
|
66
|
+
tags: languageTags,
|
|
67
|
+
__tags: true,
|
|
68
|
+
},
|
|
69
|
+
languageData,
|
|
70
|
+
),
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (isI18nEnabled(fullConfig)) {
|
|
75
|
+
getLanguageKeys(fullConfig).forEach((langKey) => generateTagPages(langKey));
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Generate individual tag pages with pagination
|
|
80
|
+
generateTagPages();
|
|
81
|
+
|
|
82
|
+
return result;
|
|
83
|
+
});
|
|
84
|
+
};
|
package/include/hexo/helper.js
CHANGED
|
@@ -13,6 +13,16 @@ function getCDN(cdn, pkg, version, filename) {
|
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
const {
|
|
17
|
+
getLanguageLabel,
|
|
18
|
+
getPageLanguageKey,
|
|
19
|
+
getPageLocale,
|
|
20
|
+
getLocalizedTagPath,
|
|
21
|
+
isExternalUrl,
|
|
22
|
+
isI18nEnabled,
|
|
23
|
+
localizePath,
|
|
24
|
+
} = require("../util/i18n");
|
|
25
|
+
|
|
16
26
|
module.exports = (hexo) => {
|
|
17
27
|
hexo.extend.helper.register("cdn", function (_package, version, filename) {
|
|
18
28
|
cdn = this.config.providers?.cdn ? this.config.providers.cdn : "jsdelivr";
|
|
@@ -21,4 +31,32 @@ module.exports = (hexo) => {
|
|
|
21
31
|
hexo.extend.helper.register("is_tags", function (page = null) {
|
|
22
32
|
return (page === null ? this.page : page).__tags === true;
|
|
23
33
|
});
|
|
34
|
+
hexo.extend.helper.register("is_i18n_enabled", function () {
|
|
35
|
+
return isI18nEnabled(this.config);
|
|
36
|
+
});
|
|
37
|
+
hexo.extend.helper.register("language_key", function (page = null) {
|
|
38
|
+
return getPageLanguageKey(page || this.page, this.config);
|
|
39
|
+
});
|
|
40
|
+
hexo.extend.helper.register("language_locale", function (page = null) {
|
|
41
|
+
return getPageLocale(page || this.page, this.config);
|
|
42
|
+
});
|
|
43
|
+
hexo.extend.helper.register("language_label", function (keyOrLocale) {
|
|
44
|
+
return getLanguageLabel(this.config, keyOrLocale);
|
|
45
|
+
});
|
|
46
|
+
hexo.extend.helper.register("localized_path", function (targetPath, langKey = null) {
|
|
47
|
+
return localizePath(targetPath, langKey || getPageLanguageKey(this.page, this.config), this.config);
|
|
48
|
+
});
|
|
49
|
+
hexo.extend.helper.register("localized_url_for", function (targetPath, langKey = null) {
|
|
50
|
+
const localized = localizePath(targetPath, langKey || getPageLanguageKey(this.page, this.config), this.config);
|
|
51
|
+
const urlFor = hexo.extend.helper.get("url_for").bind(this);
|
|
52
|
+
return isExternalUrl(localized) ? localized : urlFor(localized);
|
|
53
|
+
});
|
|
54
|
+
hexo.extend.helper.register("localized_tag_url", function (tag, langKey = null) {
|
|
55
|
+
const urlFor = hexo.extend.helper.get("url_for").bind(this);
|
|
56
|
+
if (!isI18nEnabled(this.config)) {
|
|
57
|
+
return urlFor(typeof tag === "string" ? tag : tag.path);
|
|
58
|
+
}
|
|
59
|
+
const route = getLocalizedTagPath(tag, langKey || getPageLanguageKey(this.page, this.config), this.config);
|
|
60
|
+
return urlFor(`/${route}`);
|
|
61
|
+
});
|
|
24
62
|
};
|