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
@@ -0,0 +1,280 @@
1
+ const path = require("node:path");
2
+
3
+ const DEFAULT_LANGUAGES = {
4
+ cn: {
5
+ label: "Chinese",
6
+ locale: "zh-CN",
7
+ prefix: "cn",
8
+ },
9
+ en: {
10
+ label: "English",
11
+ locale: "en",
12
+ prefix: "en",
13
+ },
14
+ };
15
+
16
+ function trimSlashes(value) {
17
+ return String(value || "").replace(/^\/+|\/+$/g, "");
18
+ }
19
+
20
+ function normalizeLanguageKey(value) {
21
+ return trimSlashes(value).trim();
22
+ }
23
+
24
+ function normalizePrefix(value, fallback) {
25
+ const prefix = trimSlashes(value == null ? fallback : value);
26
+ return prefix ? `${prefix}/` : "";
27
+ }
28
+
29
+ function normalizeLocale(value) {
30
+ return String(value || "").replace(/_/g, "-");
31
+ }
32
+
33
+ function isExternalUrl(value) {
34
+ return /^(?:[a-z][a-z\d+.-]*:)?\/\//i.test(value) || /^(?:mailto|tel|data):/i.test(value) || value.startsWith("#");
35
+ }
36
+
37
+ const i18nConfigCache = new WeakMap();
38
+
39
+ function getI18nConfig(config = {}) {
40
+ if (i18nConfigCache.has(config)) {
41
+ return i18nConfigCache.get(config);
42
+ }
43
+
44
+ const configI18n = config.i18n || {};
45
+ const themeI18n = config.theme_config?.i18n || {};
46
+ const raw = configI18n.enabled === true || configI18n.languages ? configI18n : themeI18n.enabled === true || themeI18n.languages ? themeI18n : configI18n;
47
+ const rawLanguages = raw.languages || DEFAULT_LANGUAGES;
48
+ const languages = {};
49
+
50
+ Object.keys(rawLanguages).forEach((key) => {
51
+ const normalizedKey = normalizeLanguageKey(key);
52
+ const value = typeof rawLanguages[key] === "string" ? { locale: rawLanguages[key] } : rawLanguages[key] || {};
53
+ const locale = normalizeLocale(value.locale || value.lang || value.language || normalizedKey);
54
+ const prefix = value.prefix ?? value.path ?? value.url_prefix;
55
+ languages[normalizedKey] = {
56
+ key: normalizedKey,
57
+ label: value.label || value.name || locale || normalizedKey,
58
+ locale,
59
+ prefix: normalizePrefix(prefix, normalizedKey),
60
+ };
61
+ });
62
+
63
+ const keys = Object.keys(languages);
64
+ const configuredDefault = normalizeLanguageKey(raw.default || raw.default_language || raw.defaultLanguage || keys[0]);
65
+ const defaultLanguage = languages[configuredDefault] ? configuredDefault : keys[0];
66
+
67
+ const result = {
68
+ enabled: raw.enabled === true,
69
+ defaultLanguage,
70
+ languages,
71
+ };
72
+
73
+ i18nConfigCache.set(config, result);
74
+ return result;
75
+ }
76
+
77
+ function isI18nEnabled(config = {}) {
78
+ return getI18nConfig(config).enabled;
79
+ }
80
+
81
+ function getLanguageKeys(config = {}) {
82
+ return Object.keys(getI18nConfig(config).languages);
83
+ }
84
+
85
+ function getDefaultLanguageKey(config = {}) {
86
+ return getI18nConfig(config).defaultLanguage;
87
+ }
88
+
89
+ function getLanguage(config = {}, key) {
90
+ const i18n = getI18nConfig(config);
91
+ const normalizedKey = normalizeLanguageKey(key);
92
+ return i18n.languages[normalizedKey] || i18n.languages[i18n.defaultLanguage];
93
+ }
94
+
95
+ function getLanguageKeyFromLocale(config = {}, locale) {
96
+ const normalizedLocale = normalizeLocale(locale).toLowerCase();
97
+ if (!normalizedLocale) return null;
98
+
99
+ const i18n = getI18nConfig(config);
100
+ if (i18n.languages[normalizedLocale]) return normalizedLocale;
101
+
102
+ return (
103
+ Object.keys(i18n.languages).find((key) => {
104
+ const language = i18n.languages[key];
105
+ return language.locale.toLowerCase() === normalizedLocale;
106
+ }) || null
107
+ );
108
+ }
109
+
110
+ function getLanguageKeyFromPath(value, config = {}) {
111
+ const normalized = trimSlashes(value);
112
+ if (!normalized) return null;
113
+
114
+ const i18n = getI18nConfig(config);
115
+ return (
116
+ Object.keys(i18n.languages).find((key) => {
117
+ const prefix = trimSlashes(i18n.languages[key].prefix);
118
+ return prefix && (normalized === prefix || normalized.startsWith(`${prefix}/`));
119
+ }) || null
120
+ );
121
+ }
122
+
123
+ function getLanguageKeyFromSource(value, config = {}) {
124
+ const normalized = trimSlashes(value).replace(/\\/g, "/");
125
+ if (!normalized) return null;
126
+
127
+ const firstSegment = normalized.split("/")[0];
128
+ const i18n = getI18nConfig(config);
129
+ return i18n.languages[firstSegment] ? firstSegment : null;
130
+ }
131
+
132
+ function getPageLanguageKey(page = {}, config = {}) {
133
+ if (!isI18nEnabled(config)) {
134
+ const configuredLanguage = Array.isArray(config.language) ? config.language[0] : config.language;
135
+ return getLanguageKeyFromLocale(config, page.lang || page.language || configuredLanguage) || normalizeLanguageKey(page.lang || page.language || configuredLanguage) || getDefaultLanguageKey(config);
136
+ }
137
+
138
+ const explicit = normalizeLanguageKey(page.i18n_lang || page.i18n?.lang || page.i18n?.language);
139
+ if (explicit && getLanguage(config, explicit)?.key === explicit) return explicit;
140
+
141
+ const fromSource = getLanguageKeyFromSource(page.source || page.full_source || "", config);
142
+ if (fromSource) return fromSource;
143
+
144
+ const pathDescriptor = Object.getOwnPropertyDescriptor(page, "path");
145
+ const pathValue = pathDescriptor && typeof pathDescriptor.get !== "function" ? pathDescriptor.value : "";
146
+ const fromPath = getLanguageKeyFromPath(pathValue || page.canonical_path || "", config);
147
+ if (fromPath) return fromPath;
148
+
149
+ const fromLocale = getLanguageKeyFromLocale(config, page.lang || page.language);
150
+ if (fromLocale) return fromLocale;
151
+
152
+ return getDefaultLanguageKey(config);
153
+ }
154
+
155
+ function getPageLocale(page = {}, config = {}) {
156
+ if (!isI18nEnabled(config)) {
157
+ const configuredLanguage = Array.isArray(config.language) ? config.language[0] : config.language;
158
+ return normalizeLocale(page.lang || page.language || configuredLanguage || "");
159
+ }
160
+
161
+ const language = getLanguage(config, getPageLanguageKey(page, config));
162
+ return language?.locale || normalizeLocale(page.lang || page.language) || "";
163
+ }
164
+
165
+ function getLanguageLabel(config = {}, keyOrLocale) {
166
+ const key = getLanguage(config, keyOrLocale)?.key === normalizeLanguageKey(keyOrLocale) ? normalizeLanguageKey(keyOrLocale) : getLanguageKeyFromLocale(config, keyOrLocale);
167
+ const language = getLanguage(config, key || getDefaultLanguageKey(config));
168
+ return language?.label || keyOrLocale;
169
+ }
170
+
171
+ function getLanguageBasePath(config = {}, key) {
172
+ return getLanguage(config, key)?.prefix || "";
173
+ }
174
+
175
+ function joinRoute(...parts) {
176
+ const joined = parts.map(trimSlashes).filter(Boolean).join("/");
177
+ return joined ? `${joined}/` : "";
178
+ }
179
+
180
+ function stripLanguagePrefix(value, config = {}) {
181
+ const normalized = trimSlashes(value);
182
+ const key = getLanguageKeyFromPath(normalized, config);
183
+ if (!key) return normalized;
184
+
185
+ const prefix = trimSlashes(getLanguageBasePath(config, key));
186
+ return normalized.slice(prefix.length).replace(/^\/+/, "");
187
+ }
188
+
189
+ function localizePath(value, key, config = {}) {
190
+ if (!isI18nEnabled(config) || typeof value !== "string" || !value.trim() || isExternalUrl(value)) {
191
+ return value;
192
+ }
193
+
194
+ const [pathAndQuery, hash = ""] = value.split("#");
195
+ const [pathname, query = ""] = pathAndQuery.split("?");
196
+ const route = stripLanguagePrefix(pathname, config);
197
+ const base = trimSlashes(getLanguageBasePath(config, key || getDefaultLanguageKey(config)));
198
+ const normalizedRoute = trimSlashes(route);
199
+ const joined = [base, normalizedRoute].filter(Boolean).join("/");
200
+ const hasFileExtension = /\.[^/]+$/.test(normalizedRoute);
201
+ const localized = `/${joined}${joined && !hasFileExtension ? "/" : ""}`;
202
+ const queryPart = query ? `?${query}` : "";
203
+ const hashPart = hash ? `#${hash}` : "";
204
+ return `${localized}${queryPart}${hashPart}`;
205
+ }
206
+
207
+ function toArray(collection) {
208
+ if (!collection) return [];
209
+ if (typeof collection.toArray === "function") return collection.toArray();
210
+ if (Array.isArray(collection.data)) return collection.data;
211
+ if (Array.isArray(collection)) return collection;
212
+ return [];
213
+ }
214
+
215
+ function filterByLanguage(collection, key, config = {}) {
216
+ if (!isI18nEnabled(config)) return collection;
217
+ if (typeof collection?.filter === "function" && !Array.isArray(collection)) {
218
+ return collection.filter((item) => getPageLanguageKey(item, config) === key);
219
+ }
220
+ return toArray(collection).filter((item) => getPageLanguageKey(item, config) === key);
221
+ }
222
+
223
+ function getI18nKey(item = {}) {
224
+ return item.i18n_key || item.i18n?.key || item.translation_key || item.slug || inferI18nKeyFromSource(item.source);
225
+ }
226
+
227
+ function getLocalizedTagPath(tag, key, config = {}) {
228
+ const tagDir = config.tag_dir || "tags";
229
+ const tagPath = typeof tag === "string" ? tag : tag?.path || tag?.slug || tag?.name || "";
230
+ let slug = trimSlashes(tagPath);
231
+ const tagDirPrefix = `${trimSlashes(tagDir)}/`;
232
+
233
+ if (slug.startsWith(tagDirPrefix)) {
234
+ slug = slug.slice(tagDirPrefix.length);
235
+ }
236
+
237
+ return joinRoute(getLanguageBasePath(config, key), tagDir, slug);
238
+ }
239
+
240
+ function inferI18nKeyFromSource(source) {
241
+ if (typeof source !== "string") return "";
242
+ const normalized = trimSlashes(source).replace(/\\/g, "/");
243
+ const ext = path.posix.extname(normalized);
244
+ const withoutExt = ext ? normalized.slice(0, -ext.length) : normalized;
245
+ const parts = withoutExt.split("/").filter(Boolean);
246
+ if (!parts.length) return "";
247
+
248
+ const last = parts[parts.length - 1];
249
+ if (last === "index" && parts.length >= 2) {
250
+ return parts[parts.length - 2];
251
+ }
252
+
253
+ return last;
254
+ }
255
+
256
+ module.exports = {
257
+ filterByLanguage,
258
+ getDefaultLanguageKey,
259
+ getI18nConfig,
260
+ getI18nKey,
261
+ getLanguage,
262
+ getLanguageBasePath,
263
+ getLanguageKeyFromLocale,
264
+ getLanguageKeyFromPath,
265
+ getLanguageKeyFromSource,
266
+ getLanguageKeys,
267
+ getLanguageLabel,
268
+ getLocalizedTagPath,
269
+ getPageLanguageKey,
270
+ getPageLocale,
271
+ inferI18nKeyFromSource,
272
+ isExternalUrl,
273
+ isI18nEnabled,
274
+ joinRoute,
275
+ localizePath,
276
+ normalizeLocale,
277
+ stripLanguagePrefix,
278
+ toArray,
279
+ trimSlashes,
280
+ };
@@ -0,0 +1,84 @@
1
+ const STORAGE_KEY = "themePreference";
2
+ const DEFAULT_THEME = "system";
3
+
4
+ const SYSTEM_THEME = Object.freeze({
5
+ dark: "mocha",
6
+ light: "nord",
7
+ });
8
+
9
+ const THEME_OPTIONS = Object.freeze([
10
+ { label: "SYSTEM", name: "System", value: DEFAULT_THEME },
11
+ { label: "LATTE", name: "Catppuccin Latte", value: "latte", colorScheme: "light" },
12
+ { label: "NORD", name: "Nord Light", value: "nord", colorScheme: "light" },
13
+ { label: "SONG CI", name: "Song Porcelain", value: "song_ci", colorScheme: "light" },
14
+ { label: "NORD NIGHT", name: "Nord Night", value: "nord_night", colorScheme: "night" },
15
+ { label: "ROSE PINE", name: "Rosé Pine", value: "rose_pine", colorScheme: "night" },
16
+ { label: "MOCHA", name: "Catppuccin Mocha", value: "mocha", colorScheme: "night" },
17
+ { label: "TOKYO NIGHT", name: "Tokyo Night", value: "tokyo_night", colorScheme: "night" },
18
+ ]);
19
+
20
+ function getConcreteThemeOptions() {
21
+ return THEME_OPTIONS.filter((theme) => theme.value !== DEFAULT_THEME);
22
+ }
23
+
24
+ function getThemeClassMap() {
25
+ return Object.fromEntries(getConcreteThemeOptions().map((theme) => [theme.value, theme.colorScheme]));
26
+ }
27
+
28
+ function getClientThemeConfig() {
29
+ return {
30
+ storageKey: STORAGE_KEY,
31
+ defaultTheme: DEFAULT_THEME,
32
+ systemTheme: SYSTEM_THEME,
33
+ themes: THEME_OPTIONS,
34
+ themeClassMap: getThemeClassMap(),
35
+ };
36
+ }
37
+
38
+ function stringifyForScript(value) {
39
+ return JSON.stringify(value)
40
+ .replace(/</g, "\\u003c")
41
+ .replace(/\u2028/g, "\\u2028")
42
+ .replace(/\u2029/g, "\\u2029");
43
+ }
44
+
45
+ function getThemeInitScript() {
46
+ const config = stringifyForScript(getClientThemeConfig());
47
+
48
+ return `
49
+ (function() {
50
+ var config = ${config};
51
+ var themeClassMap = config.themeClassMap || {};
52
+ var stored = null;
53
+
54
+ window.__GNIX_THEME_CONFIG__ = config;
55
+
56
+ try {
57
+ stored = localStorage.getItem(config.storageKey);
58
+ } catch (_) {}
59
+
60
+ var theme = stored === config.defaultTheme || Object.prototype.hasOwnProperty.call(themeClassMap, stored)
61
+ ? stored
62
+ : config.defaultTheme;
63
+ var resolvedTheme = theme === config.defaultTheme
64
+ ? window.matchMedia("(prefers-color-scheme: dark)").matches ? config.systemTheme.dark : config.systemTheme.light
65
+ : theme;
66
+ var html = document.documentElement;
67
+ var themeClass = themeClassMap[resolvedTheme];
68
+
69
+ html.setAttribute("data-theme", resolvedTheme);
70
+ if (themeClass) html.classList.add(themeClass);
71
+ })();
72
+ `;
73
+ }
74
+
75
+ module.exports = {
76
+ DEFAULT_THEME,
77
+ STORAGE_KEY,
78
+ SYSTEM_THEME,
79
+ THEME_OPTIONS,
80
+ getClientThemeConfig,
81
+ getConcreteThemeOptions,
82
+ getThemeClassMap,
83
+ getThemeInitScript,
84
+ };
package/languages/en.yml CHANGED
@@ -14,6 +14,13 @@ common:
14
14
  page:
15
15
  one: "Page"
16
16
  other: "Pages"
17
+ navbar:
18
+ language_switch: "Switch to %s"
19
+ language_unavailable_title: "Translation unavailable"
20
+ language_unavailable: "This page is not available in %s yet."
21
+ language_stay: "Stay here"
22
+ language_home: "Go to home"
23
+ language_close: "Close"
17
24
  article:
18
25
  created_at: "Posted&nbsp;%s"
19
26
  more: "Read more"
@@ -22,6 +29,27 @@ article:
22
29
  word_count:
23
30
  one: "About %d word"
24
31
  other: "About %d words"
32
+ font_settings: "Font Settings"
33
+ article_info: "Article Info"
34
+ close: "Close"
35
+ created_time: "Created"
36
+ updated_time: "Updated"
37
+ license: "License"
38
+ location: "Location"
39
+ author: "Author"
40
+ article_title: "Title"
41
+ url: "URL"
42
+ markdown_source: "Markdown source"
43
+ original_work: "Original work"
44
+ translation_note: "Translation note"
45
+ translation_reviewed: "manually reviewed"
46
+ translation_not_reviewed: "not manually reviewed"
47
+ yes: "Yes"
48
+ no: "No"
49
+ translation_methods:
50
+ llm: "LLM translation"
51
+ machine: "Machine translation"
52
+ human: "Human translation"
25
53
  licensing:
26
54
  author: "Author"
27
55
  created_at: "Posted on"
@@ -14,6 +14,13 @@ common:
14
14
  page:
15
15
  one: "PAGE"
16
16
  other: "PAGE"
17
+ navbar:
18
+ language_switch: "切换到%s"
19
+ language_unavailable_title: "暂无对应翻译"
20
+ language_unavailable: "此页面暂未提供%s版本。"
21
+ language_stay: "留在此页"
22
+ language_home: "回到主页"
23
+ language_close: "关闭"
17
24
  article:
18
25
  created_at: "%s"
19
26
  more: "Read"
@@ -22,6 +29,27 @@ article:
22
29
  word_count:
23
30
  one: "约%d字"
24
31
  other: "约%d字"
32
+ font_settings: "字体设置"
33
+ article_info: "文章信息"
34
+ close: "关闭"
35
+ created_time: "创建时间"
36
+ updated_time: "更新时间"
37
+ license: "许可证"
38
+ location: "位置"
39
+ author: "作者"
40
+ article_title: "标题"
41
+ url: "链接"
42
+ markdown_source: "Markdown 源码"
43
+ original_work: "原语言作品"
44
+ translation_note: "翻译说明"
45
+ translation_reviewed: "已人工审阅"
46
+ translation_not_reviewed: "未人工审阅"
47
+ yes: "是"
48
+ no: "否"
49
+ translation_methods:
50
+ llm: "LLM 翻译"
51
+ machine: "机器翻译"
52
+ human: "人工翻译"
25
53
  licensing:
26
54
  author: "Author"
27
55
  created_at: "Posted"