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.
Files changed (67) hide show
  1. package/README.md +4 -6
  2. package/include/hexo/feed.js +5 -5
  3. package/include/hexo/filter.js +25 -1
  4. package/include/hexo/generator/archive.js +116 -0
  5. package/include/hexo/generator/home.js +64 -0
  6. package/include/hexo/generator/index.js +82 -0
  7. package/include/hexo/generator/md_generator.js +87 -0
  8. package/include/hexo/generator/page.js +55 -0
  9. package/include/hexo/generator/tag.js +84 -0
  10. package/include/hexo/helper.js +38 -0
  11. package/include/hexo/i18n.js +183 -0
  12. package/include/hexo/renderer.js +131 -0
  13. package/include/util/article_font.js +132 -0
  14. package/include/util/i18n.js +280 -0
  15. package/include/util/theme.js +84 -0
  16. package/languages/en.yml +28 -0
  17. package/languages/zh-CN.yml +28 -0
  18. package/layout/archive.jsx +131 -127
  19. package/layout/common/article.jsx +283 -16
  20. package/layout/common/article_info.jsx +339 -0
  21. package/layout/common/article_media.jsx +11 -4
  22. package/layout/common/comment.jsx +15 -7
  23. package/layout/common/footer.jsx +6 -5
  24. package/layout/common/head.jsx +121 -32
  25. package/layout/common/navbar.jsx +195 -65
  26. package/layout/common/theme_selector.jsx +16 -14
  27. package/layout/layout.jsx +43 -5
  28. package/layout/misc/open_graph.jsx +162 -66
  29. package/layout/misc/paginator.jsx +2 -8
  30. package/layout/plugin/cookie_consent.jsx +252 -53
  31. package/layout/plugin/swup.jsx +1 -1
  32. package/layout/search/insight.jsx +1 -1
  33. package/layout/tag.jsx +3 -2
  34. package/layout/tags.jsx +81 -73
  35. package/package.json +18 -5
  36. package/scripts/index.js +1 -0
  37. package/source/css/archive.css +225 -180
  38. package/source/css/default.css +1162 -98
  39. package/source/css/responsive.css +426 -0
  40. package/source/css/shiki/shiki.css +12 -2081
  41. package/source/css/tags.css +183 -0
  42. package/source/css/twikoo.css +1049 -1045
  43. package/source/img/favicon.svg +1 -6
  44. package/source/img/og_image.webp +0 -0
  45. package/source/js/article-font-utils.js +99 -0
  46. package/source/js/busuanzi.js +91 -24
  47. package/source/js/components/chat.js +169 -50
  48. package/source/js/components/image-carousel.js +152 -108
  49. package/source/js/components/sidenote.js +210 -0
  50. package/source/js/components/text-image-section.js +78 -90
  51. package/source/js/components/theme-stacked.js +65 -33
  52. package/source/js/components/tree.js +30 -16
  53. package/source/js/decrypt.js +7 -2
  54. package/source/js/main.js +428 -5
  55. package/source/js/swup.js +39 -0
  56. package/source/js/theme-selector.js +26 -16
  57. package/include/hexo/generator.js +0 -53
  58. package/layout/misc/article_licensing.jsx +0 -99
  59. package/source/css/responsive/desktop.css +0 -36
  60. package/source/css/responsive/mobile.css +0 -29
  61. package/source/css/responsive/tablet.css +0 -43
  62. package/source/css/responsive/touch.css +0 -155
  63. package/source/img/logo.svg +0 -9
  64. package/source/js/archive-breadcrumb.js +0 -132
  65. package/source/js/host/cookieconsent/3.1.1/build/cookieconsent.min.css +0 -6
  66. package/source/js/host/cookieconsent/3.1.1/build/cookieconsent.min.js +0 -1
  67. 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`, `Cattpuccin Latte`
16
- - **Dark Themes**: `Catppuccin Mocha`, `Catppuccin Macchiato`, `Tokyo Night`
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)
@@ -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
  }
@@ -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
+ };
@@ -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
  };