hexo-theme-gnix 11.0.0 → 13.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.
@@ -0,0 +1,191 @@
1
+ const { codeToHtml } = require("shiki");
2
+ const t = require("@shikijs/transformers");
3
+ const { transformerColorizedBrackets } = require("@shikijs/colorized-brackets");
4
+ const { mkdir, writeFile } = require("node:fs/promises");
5
+ const { dirname } = require("node:path");
6
+
7
+ const THEMES = {
8
+ light: "catppuccin-latte",
9
+ dark: "catppuccin-mocha",
10
+ song: "everforest-light",
11
+ nord: "nord",
12
+ tokyo: "tokyo-night",
13
+ rose: "rose-pine",
14
+ };
15
+
16
+ const TRANSFORMERS = [
17
+ t.transformerCompactLineOptions(),
18
+ t.transformerMetaHighlight(),
19
+ t.transformerMetaWordHighlight(),
20
+ t.transformerNotationDiff(),
21
+ t.transformerNotationErrorLevel(),
22
+ t.transformerNotationFocus(),
23
+ t.transformerNotationHighlight(),
24
+ t.transformerNotationWordHighlight(),
25
+ t.transformerRemoveLineBreak(),
26
+ t.transformerRemoveNotationEscape(),
27
+ t.transformerRenderWhitespace(),
28
+ transformerColorizedBrackets(),
29
+ ];
30
+
31
+ const SVG_WRAP =
32
+ '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="toggle-wrap" title="Toggle Wrap"><path d="m16 16-3 3 3 3"/><path d="M3 12h14.5a1 1 0 0 1 0 7H13"/><path d="M3 19h6"/><path d="M3 5h18"/></svg>';
33
+ const SVG_COPY =
34
+ '<div class="copy-notice"></div><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="copy-button"><rect width="8" height="4" x="8" y="2" rx="1" ry="1"/><path d="M8 4H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2"/><path d="M16 4h2a2 2 0 0 1 2 2v4"/><path d="M21 14H11"/><path d="m15 10-4 4 4 4"/></svg>';
35
+ const SVG_EXPAND =
36
+ '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="expand-icon"><path d="M12 22v-6"/><path d="M12 8V2"/><path d="M4 12H2"/><path d="M10 12H8"/><path d="M16 12h-2"/><path d="M22 12h-2"/><path d="m15 19-3 3-3-3"/><path d="m15 5-3-3-3 3"/></svg>';
37
+ const SVG_COLLAPSE =
38
+ '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="collapse-icon"><path d="M12 22v-6"/><path d="M12 8V2"/><path d="M4 12H2"/><path d="M10 12H8"/><path d="M16 12h-2"/><path d="M22 12h-2"/><path d="m15 19-3-3-3 3"/><path d="m15 5 3 3-3 3"/></svg>';
39
+
40
+ const RE_LINE = /<span class="line/g;
41
+
42
+ function escapeHtml(code) {
43
+ return code.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
44
+ }
45
+
46
+ function createShikiTools(lang, title, { lang: showLang, title: showTitle, wrapToggle, copyButton }) {
47
+ let left = '<div class="left"><div class="traffic-lights"> <span class="traffic-light red"></span> <span class="traffic-light yellow"></span> <span class="traffic-light green"></span> </div>';
48
+ if (showLang) left += `<div class="code-lang">${lang.toUpperCase()}</div>`;
49
+ left += "</div>";
50
+
51
+ let center = '<div class="center">';
52
+ if (showTitle && title) center += `\n<div class="code-title">${title}</div>`;
53
+ center += "\n</div>";
54
+
55
+ let right = '<div class="right">';
56
+ if (wrapToggle) right += `\n${SVG_WRAP}`;
57
+ if (copyButton) right += `\n${SVG_COPY}`;
58
+ right += "\n</div>";
59
+
60
+ return `<div class="shiki-tools">${left}${center}${right}</div>`;
61
+ }
62
+
63
+ async function writeCssAsync(cssGetter, cssOutputPath) {
64
+ if (!cssGetter || !cssOutputPath) return;
65
+ const css = cssGetter();
66
+ await mkdir(dirname(cssOutputPath), { recursive: true });
67
+ await writeFile(cssOutputPath, css, "utf8");
68
+ }
69
+
70
+ function computeCollapseAttributes(cfg, codeHtml) {
71
+ const codeLines = (codeHtml.match(RE_LINE) || []).length;
72
+ const shouldCollapse = cfg.collapseConfig.enable && codeLines > cfg.collapseConfig.maxLines;
73
+ return {
74
+ expandButton: shouldCollapse ? `<div class="code-expand-btn">${SVG_EXPAND}${SVG_COLLAPSE}</div>` : "",
75
+ collapseAttrs: shouldCollapse ? ` data-collapsible="true" data-max-lines="${cfg.collapseConfig.maxLines}" data-total-lines="${codeLines}"` : "",
76
+ };
77
+ }
78
+
79
+ function parseConfig(renderOptions) {
80
+ const options = renderOptions || {};
81
+ const { toolbar_items: ti = {}, style_to_class: stc } = options;
82
+
83
+ let enabledTransformers;
84
+ if (!options.transformers || options.transformers.includes("all")) {
85
+ enabledTransformers = [...TRANSFORMERS];
86
+ } else {
87
+ enabledTransformers = options.transformers.map((name) => TRANSFORMERS.find((tr) => tr.name === name)).filter(Boolean);
88
+ }
89
+
90
+ let toClass = null;
91
+ if (stc && stc.enable) {
92
+ toClass = t.transformerStyleToClass({ classPrefix: stc.class_prefix || "_sk_" });
93
+ enabledTransformers.push(toClass);
94
+ }
95
+
96
+ const maxLines = options.code_collapse != null ? options.code_collapse : 30;
97
+
98
+ return {
99
+ themes: THEMES,
100
+ excludes: options.exclude_languages || ["mermaid"],
101
+ aliases: options.language_aliases || {},
102
+ collapseConfig: { enable: maxLines > 0, maxLines },
103
+ styleToClass: {
104
+ enable: !!(stc && stc.enable),
105
+ cssGetter: toClass ? toClass.getCSS : undefined,
106
+ css_output_path: stc ? stc.css_output_path : undefined,
107
+ },
108
+ transformers: enabledTransformers,
109
+ toolbarItems: {
110
+ lang: ti.lang != null ? ti.lang : true,
111
+ title: ti.title != null ? ti.title : true,
112
+ wrapToggle: ti.wrapToggle != null ? ti.wrapToggle : true,
113
+ copyButton: ti.copyButton != null ? ti.copyButton : true,
114
+ },
115
+ };
116
+ }
117
+
118
+ function renderCode(md, renderOptions) {
119
+ const cfg = parseConfig(renderOptions);
120
+
121
+ md.renderer.rules.fence = async (tokens, idx) => {
122
+ const token = tokens[idx];
123
+ if (!token) return "";
124
+
125
+ const code = token.content;
126
+ const lang = token.info.split(/\s+/)[0] || "";
127
+ const attrs = token.info.split(/\s+/).slice(1).join(" ");
128
+
129
+ if (cfg.excludes.includes(lang)) {
130
+ const escaped = escapeHtml(code);
131
+ return `<pre><code class="${lang}">${escaped}</code></pre>`;
132
+ }
133
+ const normalizedCode = code.replace(/\r?\n$/, "");
134
+ const mappedLang = cfg.aliases[lang] || lang;
135
+ let codeHtml;
136
+ try {
137
+ codeHtml = await codeToHtml(normalizedCode, {
138
+ lang: mappedLang,
139
+ themes: cfg.themes,
140
+ transformers: cfg.transformers,
141
+ });
142
+ } catch (err) {
143
+ console.warn(`[shiki] Language \`${mappedLang}\` is not supported, falling back to \`txt\`.`);
144
+ codeHtml = await codeToHtml(normalizedCode, {
145
+ lang: "txt",
146
+ themes: cfg.themes,
147
+ transformers: cfg.transformers,
148
+ });
149
+ }
150
+ await writeCssAsync(cfg.styleToClass.cssGetter, cfg.styleToClass.css_output_path);
151
+ codeHtml = codeHtml.replace(/<pre[^>]*>/, (match) => match.replace(/\s*style\s*=\s*"[^"]*"\s*tabindex="0"/, ""));
152
+
153
+ const title = attrs || "";
154
+ const shikiToolsHtml = createShikiTools(lang || "", title, cfg.toolbarItems);
155
+ const { expandButton, collapseAttrs } = computeCollapseAttributes(cfg, codeHtml);
156
+ return `<figure class="shiki" ${collapseAttrs}> ${shikiToolsHtml} ${codeHtml}${expandButton} </figure>`;
157
+ };
158
+
159
+ md.renderer.rules.code_inline = async (tokens, idx, _options, _env, self) => {
160
+ const token = tokens[idx];
161
+ if (!token) return "";
162
+
163
+ const content = token.content.trim();
164
+ const match = content.match(/^\{(\w+)\}\s+(.+)$/);
165
+ if (match === null) {
166
+ return `<code${self.renderAttrs(token)}>${escapeHtml(content)}</code>`;
167
+ }
168
+ const [, lang, code] = match;
169
+ if (!lang || !code) return `<code>${content}</code>`;
170
+ let highlighted;
171
+ try {
172
+ highlighted = await codeToHtml(code, {
173
+ lang: lang,
174
+ themes: cfg.themes,
175
+ structure: "inline",
176
+ });
177
+ } catch (err) {
178
+ console.warn(`[shiki] Language \`${lang}\` is not supported, falling back to \`txt\`.`);
179
+ highlighted = await codeToHtml(code, {
180
+ lang: "txt",
181
+ themes: cfg.themes,
182
+ structure: "inline",
183
+ });
184
+ }
185
+ return `<code${self.renderAttrs(token)}>${highlighted}</code>`;
186
+ };
187
+ }
188
+
189
+ module.exports = renderCode;
190
+ module.exports.default = renderCode;
191
+
@@ -0,0 +1,184 @@
1
+ // Adapted from hexo-generator-sitemap (MIT, (c) Tommy Chen)
2
+ // https://github.com/hexojs/hexo-generator-sitemap
3
+
4
+ const { extname } = require("node:path");
5
+ const { encodeURL, url_for } = require("hexo-util");
6
+
7
+ const DEFAULT_CONFIG = {
8
+ path: ["sitemap.xml", "sitemap.txt"],
9
+ rel: false,
10
+ tags: true,
11
+ categories: true,
12
+ };
13
+
14
+ const DEFAULT_SKIP_PATTERNS = ["**/*.js", "**/*.css"];
15
+ const REL_SITEMAP_RE = /rel=['|"]?sitemap['|"]?/i;
16
+ const HEAD_RE = /<head>(?!<\/head>).+?<\/head>/s;
17
+ const REGEX_ESCAPE_CHARS = "\\^$.+()[]{}|";
18
+
19
+ function globToRegExp(pattern) {
20
+ let regex = "";
21
+ for (let i = 0; i < pattern.length; i++) {
22
+ const ch = pattern[i];
23
+ if (ch === "*") {
24
+ if (pattern[i + 1] === "*") {
25
+ regex += ".*";
26
+ i++;
27
+ if (pattern[i + 1] === "/") i++;
28
+ } else {
29
+ regex += "[^/]*";
30
+ }
31
+ } else if (ch === "?") {
32
+ regex += "[^/]";
33
+ } else if (REGEX_ESCAPE_CHARS.includes(ch)) {
34
+ regex += `\\${ch}`;
35
+ } else {
36
+ regex += ch;
37
+ }
38
+ }
39
+ return new RegExp(`^${regex}$`);
40
+ }
41
+
42
+ function buildMatcher(patterns) {
43
+ const compiled = patterns.map(globToRegExp);
44
+ return (value) => compiled.some((re) => re.test(value));
45
+ }
46
+
47
+ function normalizePaths(rawPath) {
48
+ const paths = Array.isArray(rawPath) ? rawPath : typeof rawPath === "string" ? [rawPath] : DEFAULT_CONFIG.path;
49
+ return paths.filter((p) => typeof p === "string" && p.trim()).map((p) => (extname(p) ? p : `${p}.xml`));
50
+ }
51
+
52
+ function formatDate(date) {
53
+ return date.toISOString().substring(0, 10);
54
+ }
55
+
56
+ function getLastMod(post) {
57
+ const value = post.updated || post.date;
58
+ if (!value) return null;
59
+ if (typeof value.toDate === "function") return value.toDate();
60
+ return value instanceof Date ? value : null;
61
+ }
62
+
63
+ function getSortKey(post) {
64
+ const value = post.updated;
65
+ if (!value) return 0;
66
+ if (typeof value.valueOf === "function") return value.valueOf();
67
+ return value instanceof Date ? value.getTime() : 0;
68
+ }
69
+
70
+ function renderXml({ posts, tags, categories, siteUrl, now }) {
71
+ const formattedNow = formatDate(now);
72
+
73
+ const postEntries = posts
74
+ .map((post) => {
75
+ const lastMod = getLastMod(post);
76
+ const lastModLine = lastMod ? `\n <lastmod>${formatDate(lastMod)}</lastmod>` : "";
77
+ return ` <url>
78
+ <loc>${encodeURL(post.permalink)}</loc>${lastModLine}
79
+ <changefreq>monthly</changefreq>
80
+ <priority>0.6</priority>
81
+ </url>`;
82
+ })
83
+ .join("\n");
84
+
85
+ const taxonomyEntries = (items, freq, priority) =>
86
+ items
87
+ .map(
88
+ (item) => ` <url>
89
+ <loc>${encodeURL(item.permalink)}</loc>
90
+ <lastmod>${formattedNow}</lastmod>
91
+ <changefreq>${freq}</changefreq>
92
+ <priority>${priority}</priority>
93
+ </url>`,
94
+ )
95
+ .join("\n");
96
+
97
+ const sections = [
98
+ postEntries,
99
+ ` <url>
100
+ <loc>${encodeURL(siteUrl)}</loc>
101
+ <lastmod>${formattedNow}</lastmod>
102
+ <changefreq>daily</changefreq>
103
+ <priority>1.0</priority>
104
+ </url>`,
105
+ taxonomyEntries(tags, "weekly", "0.2"),
106
+ taxonomyEntries(categories, "weekly", "0.2"),
107
+ ].filter(Boolean);
108
+
109
+ return `<?xml version="1.0" encoding="UTF-8"?>
110
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
111
+ ${sections.join("\n\n")}
112
+ </urlset>
113
+ `;
114
+ }
115
+
116
+ function renderTxt({ posts, tags, categories, siteUrl }) {
117
+ const lines = [
118
+ ...posts.map((post) => encodeURL(post.permalink)),
119
+ encodeURL(siteUrl),
120
+ ...tags.map((tag) => encodeURL(tag.permalink)),
121
+ ...categories.map((cat) => encodeURL(cat.permalink)),
122
+ ];
123
+ return `${lines.join("\n")}\n`;
124
+ }
125
+
126
+ const RENDERERS = {
127
+ ".xml": renderXml,
128
+ ".txt": renderTxt,
129
+ };
130
+
131
+ module.exports = (hexo) => {
132
+ const config = Object.assign({}, DEFAULT_CONFIG, hexo.config.sitemap);
133
+ config.path = normalizePaths(config.path);
134
+ hexo.config.sitemap = config;
135
+
136
+ hexo.extend.generator.register("sitemap", function (locals) {
137
+ const skipPatterns = [...DEFAULT_SKIP_PATTERNS];
138
+ const userSkip = this.config.skip_render;
139
+ if (Array.isArray(userSkip)) {
140
+ skipPatterns.push(...userSkip);
141
+ } else if (typeof userSkip === "string" && userSkip.length > 0) {
142
+ skipPatterns.push(userSkip);
143
+ }
144
+
145
+ const isSkipped = buildMatcher(skipPatterns);
146
+ const posts = []
147
+ .concat(locals.posts.toArray(), locals.pages.toArray())
148
+ .filter((post) => post.sitemap !== false && !isSkipped(post.source))
149
+ .sort((a, b) => getSortKey(b) - getSortKey(a));
150
+
151
+ if (posts.length === 0) {
152
+ config.rel = false;
153
+ return;
154
+ }
155
+
156
+ const context = {
157
+ posts,
158
+ tags: config.tags ? locals.tags.toArray() : [],
159
+ categories: config.categories ? locals.categories.toArray() : [],
160
+ siteUrl: this.config.url,
161
+ now: new Date(),
162
+ };
163
+
164
+ return config.path
165
+ .map((p) => {
166
+ const renderer = RENDERERS[extname(p)];
167
+ return renderer ? { path: p, data: renderer(context) } : null;
168
+ })
169
+ .filter(Boolean);
170
+ });
171
+
172
+ if (config.rel === true) {
173
+ hexo.extend.filter.register("after_render:html", function (data) {
174
+ const sitemapConfig = hexo.config.sitemap;
175
+ if (!sitemapConfig.rel || REL_SITEMAP_RE.test(data)) return data;
176
+
177
+ const xmlPath = sitemapConfig.path.find((p) => extname(p) === ".xml");
178
+ if (!xmlPath) return data;
179
+
180
+ const tag = `<link rel="sitemap" type="application/xml" title="Sitemap" href="${url_for.call(this, xmlPath)}">`;
181
+ return data.replace(HEAD_RE, (str) => str.replace("</head>", `${tag}</head>`));
182
+ });
183
+ }
184
+ };
package/languages/en.yml CHANGED
@@ -40,16 +40,10 @@ article:
40
40
  article_title: "Title"
41
41
  url: "URL"
42
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"
43
+ translation_info: "Translation"
44
+ translation_original: "Original"
45
+ translation_llm_reviewed: "LLM · reviewed"
46
+ translation_llm_unreviewed: "LLM · unreviewed"
53
47
  licensing:
54
48
  author: "Author"
55
49
  created_at: "Posted on"
@@ -40,16 +40,10 @@ article:
40
40
  article_title: "标题"
41
41
  url: "链接"
42
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: "人工翻译"
43
+ translation_info: "关于翻译"
44
+ translation_original: "原文"
45
+ translation_llm_reviewed: "LLM 翻译 · 已人工审阅"
46
+ translation_llm_unreviewed: "LLM 翻译 · 未人工审阅"
53
47
  licensing:
54
48
  author: "Author"
55
49
  created_at: "Posted"
@@ -99,40 +99,65 @@ module.exports = class extends Component {
99
99
  <div class="article-title-actions">
100
100
  {hasComment && (
101
101
  <button type="button" class="article-action-btn" popovertarget="article-comment-popover" aria-label={commentsLabel} title={commentsLabel}>
102
- <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" role="img" aria-label={commentsLabel}>
102
+ <svg
103
+ xmlns="http://www.w3.org/2000/svg"
104
+ width="24"
105
+ height="24"
106
+ viewBox="0 0 24 24"
107
+ fill="none"
108
+ stroke="currentColor"
109
+ stroke-width="2"
110
+ stroke-linecap="round"
111
+ stroke-linejoin="round"
112
+ role="img"
113
+ aria-label={commentsLabel}
114
+ >
103
115
  <title>{commentsLabel}</title>
104
- <g fill="none">
105
- <path d="m12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035q-.016-.005-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093q.019.005.029-.008l.004-.014l-.034-.614q-.005-.018-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z" />
106
- <path
107
- fill="currentColor"
108
- d="M16 4a3 3 0 0 1 2.995 2.824L19 7v2a3 3 0 0 1 2.995 2.824L22 12v4a3 3 0 0 1-2.824 2.995L19 19v.966c0 1.02-1.143 1.594-1.954 1.033l-.096-.072L14.638 19H11a3 3 0 0 1-1.998-.762l-.14-.134L7 19.5c-.791.593-1.906.075-1.994-.879L5 18.5V17a3 3 0 0 1-2.995-2.824L2 14V7a3 3 0 0 1 2.824-2.995L5 4zm3 7h-8a1 1 0 0 0-1 1v4a1 1 0 0 0 1 1h3.638a2 2 0 0 1 1.28.464l1.088.906A1.5 1.5 0 0 1 18.5 17h.5a1 1 0 0 0 1-1v-4a1 1 0 0 0-1-1m-3-5H5a1 1 0 0 0-1 1v7a1 1 0 0 0 1 1h.5A1.5 1.5 0 0 1 7 16.5v.5l1.01-.757A3 3 0 0 1 8 16v-4a3 3 0 0 1 3-3h6V7a1 1 0 0 0-1-1"
109
- />
110
- </g>
116
+ <path d="M2.992 16.342a2 2 0 0 1 .094 1.167l-1.065 3.29a1 1 0 0 0 1.236 1.168l3.413-.998a2 2 0 0 1 1.099.092 10 10 0 1 0-4.777-4.719" />
117
+ <path d="M8 12h.01" />
118
+ <path d="M12 12h.01" />
119
+ <path d="M16 12h.01" />
111
120
  </svg>
112
121
  </button>
113
122
  )}
114
123
  <button type="button" class="article-action-btn" popovertarget="article-font-settings" aria-label={helper.__("article.font_settings")} title={helper.__("article.font_settings")}>
115
- <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" role="img" aria-label={helper.__("article.font_settings")}>
124
+ <svg
125
+ xmlns="http://www.w3.org/2000/svg"
126
+ width="24"
127
+ height="24"
128
+ viewBox="0 0 24 24"
129
+ fill="none"
130
+ stroke="currentColor"
131
+ stroke-width="2"
132
+ stroke-linecap="round"
133
+ stroke-linejoin="round"
134
+ role="img"
135
+ aria-label={helper.__("article.font_settings")}
136
+ >
116
137
  <title>{helper.__("article.font_settings")}</title>
117
- <g fill="none" fill-rule="evenodd">
118
- <path d="m12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035q-.016-.005-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093q.019.005.029-.008l.004-.014l-.034-.614q-.005-.018-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z" />
119
- <path
120
- fill="currentColor"
121
- d="M21 12a1 1 0 0 1 1 1v6a1 1 0 0 1-1.911.412a4 4 0 1 1 0-6.824A1 1 0 0 1 21 12M8 4c.732 0 1.381.473 1.605 1.17l4.347 13.524a1 1 0 0 1-1.904.612L10.664 15H5.336l-1.384 4.306a1 1 0 0 1-1.904-.612L6.395 5.17A1.69 1.69 0 0 1 8 4m10 10a2 2 0 1 0 0 4a2 2 0 0 0 0-4M8 6.712L5.979 13h4.042z"
122
- />
123
- </g>
138
+ <path d="M12 4v16" />
139
+ <path d="M4 7V5a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v2" />
140
+ <path d="M9 20h6" />
124
141
  </svg>
125
142
  </button>
126
143
  <button type="button" class="article-action-btn" popovertarget="article-info-popover" aria-label={helper.__("article.article_info")} title={helper.__("article.article_info")}>
127
- <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" role="img" aria-label={helper.__("article.article_info")}>
144
+ <svg
145
+ xmlns="http://www.w3.org/2000/svg"
146
+ width="24"
147
+ height="24"
148
+ viewBox="0 0 24 24"
149
+ fill="none"
150
+ stroke="currentColor"
151
+ stroke-width="2"
152
+ stroke-linecap="round"
153
+ stroke-linejoin="round"
154
+ role="img"
155
+ aria-label={helper.__("article.article_info")}
156
+ >
128
157
  <title>{helper.__("article.article_info")}</title>
129
- <g fill="none">
130
- <path d="m12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035q-.016-.005-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093q.019.005.029-.008l.004-.014l-.034-.614q-.005-.018-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z" />
131
- <path
132
- fill="currentColor"
133
- d="M12 2c5.523 0 10 4.477 10 10s-4.477 10-10 10S2 17.523 2 12S6.477 2 12 2m0 2a8 8 0 1 0 0 16a8 8 0 0 0 0-16m-.01 6c.558 0 1.01.452 1.01 1.01v5.124A1 1 0 0 1 12.5 18h-.49A1.01 1.01 0 0 1 11 16.99V12a1 1 0 1 1 0-2zM12 7a1 1 0 1 1 0 2a1 1 0 0 1 0-2"
134
- />
135
- </g>
158
+ <circle cx="12" cy="12" r="10" />
159
+ <path d="M12 16v-4" />
160
+ <path d="M12 8h.01" />
136
161
  </svg>
137
162
  </button>
138
163
  </div>
@@ -1,36 +1,11 @@
1
1
  const { Component } = require("../../include/util/common");
2
2
 
3
- function getTranslatedValue(helper, key, fallback) {
4
- const translated = helper.__(key);
5
- return translated === key ? fallback : translated;
6
- }
7
-
8
- function getTranslationMethodLabel(method, helper) {
9
- if (!method) return "";
10
- const key = `article.translation_methods.${method}`;
11
- return getTranslatedValue(helper, key, method);
12
- }
13
-
14
- function getOriginalWorkValue(page, helper) {
15
- const value = page.i18n?.original ?? page.original;
16
- if (typeof value !== "boolean") return null;
17
- return value ? helper.__("article.yes") : helper.__("article.no");
18
- }
19
-
20
- function getTranslationNote(page, helper) {
21
- const translation = page.i18n?.translation || page.translation;
22
- if (!translation) return null;
23
- if (typeof translation === "string") return translation;
24
-
25
- const parts = [];
26
- const method = getTranslationMethodLabel(translation.method, helper);
27
- if (method) parts.push(method);
28
- if (typeof translation.reviewed === "boolean") {
29
- parts.push(helper.__(translation.reviewed ? "article.translation_reviewed" : "article.translation_not_reviewed"));
30
- }
31
- if (translation.note) parts.push(translation.note);
32
-
33
- return parts.length ? parts.join(" · ") : null;
3
+ function getTranslationInfo(page, helper) {
4
+ if (!page.i18n) return null;
5
+ const t = page.i18n.translation;
6
+ if (t === 1) return helper.__("article.translation_llm_reviewed");
7
+ if (t === 2) return helper.__("article.translation_llm_unreviewed");
8
+ return helper.__("article.translation_original");
34
9
  }
35
10
 
36
11
  function LanguageIcon({ title }) {
@@ -51,8 +26,7 @@ module.exports = class extends Component {
51
26
  render() {
52
27
  const { page, config, helper } = this.props;
53
28
  const { article } = config;
54
- const originalWorkValue = getOriginalWorkValue(page, helper);
55
- const translationNote = getTranslationNote(page, helper);
29
+ const translationInfo = getTranslationInfo(page, helper);
56
30
 
57
31
  const markdownSourceUrl = page.markdown_path ? helper.url_for(page.markdown_path) : null;
58
32
  const markdownSourceLabel = helper.__("article.markdown_source");
@@ -171,25 +145,14 @@ module.exports = class extends Component {
171
145
  </div>
172
146
  </div>
173
147
  )}
174
- {originalWorkValue && (
175
- <div class="article-info-item">
176
- <div class="article-info-icon">
177
- <LanguageIcon title={helper.__("article.original_work")} />
178
- </div>
179
- <div class="article-info-content">
180
- <span class="article-info-label">{helper.__("article.original_work")}</span>
181
- <span class="article-info-value">{originalWorkValue}</span>
182
- </div>
183
- </div>
184
- )}
185
- {translationNote && (
148
+ {translationInfo && (
186
149
  <div class="article-info-item">
187
150
  <div class="article-info-icon">
188
- <LanguageIcon title={helper.__("article.translation_note")} />
151
+ <LanguageIcon title={helper.__("article.translation_info")} />
189
152
  </div>
190
153
  <div class="article-info-content">
191
- <span class="article-info-label">{helper.__("article.translation_note")}</span>
192
- <span class="article-info-value">{translationNote}</span>
154
+ <span class="article-info-label">{helper.__("article.translation_info")}</span>
155
+ <span class="article-info-value">{translationInfo}</span>
193
156
  </div>
194
157
  </div>
195
158
  )}