hexo-theme-gnix 9.0.0 → 10.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 -2
- 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/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 +5 -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
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
const { Component } = require("../../include/util/common");
|
|
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;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function LanguageIcon({ title }) {
|
|
37
|
+
return (
|
|
38
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" role="img" aria-label={title}>
|
|
39
|
+
<title>{title}</title>
|
|
40
|
+
<path d="m5 8 6 6" />
|
|
41
|
+
<path d="m4 14 6-6 2-3" />
|
|
42
|
+
<path d="M2 5h12" />
|
|
43
|
+
<path d="M7 2h1" />
|
|
44
|
+
<path d="m22 22-5-10-5 10" />
|
|
45
|
+
<path d="M14 18h6" />
|
|
46
|
+
</svg>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = class extends Component {
|
|
51
|
+
render() {
|
|
52
|
+
const { page, config, helper } = this.props;
|
|
53
|
+
const { article } = config;
|
|
54
|
+
const originalWorkValue = getOriginalWorkValue(page, helper);
|
|
55
|
+
const translationNote = getTranslationNote(page, helper);
|
|
56
|
+
|
|
57
|
+
const markdownSourceUrl = page.markdown_path ? helper.url_for(page.markdown_path) : null;
|
|
58
|
+
const markdownSourceLabel = helper.__("article.markdown_source");
|
|
59
|
+
const markdownSourceType = "text/markdown; charset=utf-8";
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<div id="article-info-popover" popover="auto" class="article-popover article-info-popover">
|
|
63
|
+
<div class="article-popover-header">
|
|
64
|
+
<h3>{helper.__("article.article_info")}</h3>
|
|
65
|
+
<button type="button" class="article-popover-close" popovertarget="article-info-popover" popovertargetaction="hide" aria-label={helper.__("article.close")}>
|
|
66
|
+
<svg
|
|
67
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
68
|
+
width="18"
|
|
69
|
+
height="18"
|
|
70
|
+
viewBox="0 0 24 24"
|
|
71
|
+
fill="none"
|
|
72
|
+
stroke="currentColor"
|
|
73
|
+
stroke-width="2"
|
|
74
|
+
stroke-linecap="round"
|
|
75
|
+
stroke-linejoin="round"
|
|
76
|
+
role="img"
|
|
77
|
+
aria-label="Close"
|
|
78
|
+
>
|
|
79
|
+
<title>Close</title>
|
|
80
|
+
<path d="M18 6 6 18" />
|
|
81
|
+
<path d="m6 6 12 12" />
|
|
82
|
+
</svg>
|
|
83
|
+
</button>
|
|
84
|
+
</div>
|
|
85
|
+
<div class="article-popover-body">
|
|
86
|
+
<div class="article-info-list">
|
|
87
|
+
{(page.author || config.author) && (
|
|
88
|
+
<div class="article-info-item">
|
|
89
|
+
<div class="article-info-icon">
|
|
90
|
+
<svg
|
|
91
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
92
|
+
width="18"
|
|
93
|
+
height="18"
|
|
94
|
+
viewBox="0 0 24 24"
|
|
95
|
+
fill="none"
|
|
96
|
+
stroke="currentColor"
|
|
97
|
+
stroke-width="2"
|
|
98
|
+
stroke-linecap="round"
|
|
99
|
+
stroke-linejoin="round"
|
|
100
|
+
role="img"
|
|
101
|
+
aria-label="Author"
|
|
102
|
+
>
|
|
103
|
+
<title>Author</title>
|
|
104
|
+
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" />
|
|
105
|
+
<circle cx="12" cy="7" r="4" />
|
|
106
|
+
</svg>
|
|
107
|
+
</div>
|
|
108
|
+
<div class="article-info-content">
|
|
109
|
+
<span class="article-info-label">{helper.__("article.author")}</span>
|
|
110
|
+
<span class="article-info-value">{page.author || config.author}</span>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
)}
|
|
114
|
+
{page.title && (
|
|
115
|
+
<div class="article-info-item">
|
|
116
|
+
<div class="article-info-icon">
|
|
117
|
+
<svg
|
|
118
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
119
|
+
width="18"
|
|
120
|
+
height="18"
|
|
121
|
+
viewBox="0 0 24 24"
|
|
122
|
+
fill="none"
|
|
123
|
+
stroke="currentColor"
|
|
124
|
+
stroke-width="2"
|
|
125
|
+
stroke-linecap="round"
|
|
126
|
+
stroke-linejoin="round"
|
|
127
|
+
role="img"
|
|
128
|
+
aria-label="Title"
|
|
129
|
+
>
|
|
130
|
+
<title>Title</title>
|
|
131
|
+
<path d="M4 20h16" />
|
|
132
|
+
<path d="M6 16h6" />
|
|
133
|
+
<path d="M6 12h12" />
|
|
134
|
+
<path d="M6 8h10" />
|
|
135
|
+
</svg>
|
|
136
|
+
</div>
|
|
137
|
+
<div class="article-info-content">
|
|
138
|
+
<span class="article-info-label">{helper.__("article.article_title")}</span>
|
|
139
|
+
<span class="article-info-value">{page.title}</span>
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
)}
|
|
143
|
+
{page.permalink && (
|
|
144
|
+
<div class="article-info-item">
|
|
145
|
+
<div class="article-info-icon">
|
|
146
|
+
<svg
|
|
147
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
148
|
+
width="18"
|
|
149
|
+
height="18"
|
|
150
|
+
viewBox="0 0 24 24"
|
|
151
|
+
fill="none"
|
|
152
|
+
stroke="currentColor"
|
|
153
|
+
stroke-width="2"
|
|
154
|
+
stroke-linecap="round"
|
|
155
|
+
stroke-linejoin="round"
|
|
156
|
+
role="img"
|
|
157
|
+
aria-label="URL"
|
|
158
|
+
>
|
|
159
|
+
<title>URL</title>
|
|
160
|
+
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
|
|
161
|
+
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
|
|
162
|
+
</svg>
|
|
163
|
+
</div>
|
|
164
|
+
<div class="article-info-content">
|
|
165
|
+
<span class="article-info-label">{helper.__("article.url")}</span>
|
|
166
|
+
<span class="article-info-value">
|
|
167
|
+
<a href={page.permalink} target="_blank" rel="noopener">
|
|
168
|
+
{decodeURI(page.permalink)}
|
|
169
|
+
</a>
|
|
170
|
+
</span>
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
)}
|
|
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 && (
|
|
186
|
+
<div class="article-info-item">
|
|
187
|
+
<div class="article-info-icon">
|
|
188
|
+
<LanguageIcon title={helper.__("article.translation_note")} />
|
|
189
|
+
</div>
|
|
190
|
+
<div class="article-info-content">
|
|
191
|
+
<span class="article-info-label">{helper.__("article.translation_note")}</span>
|
|
192
|
+
<span class="article-info-value">{translationNote}</span>
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
)}
|
|
196
|
+
{markdownSourceUrl && (
|
|
197
|
+
<div class="article-info-item">
|
|
198
|
+
<div class="article-info-icon">
|
|
199
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 640 512" role="img" aria-label={markdownSourceLabel}>
|
|
200
|
+
<title>{markdownSourceLabel}</title>
|
|
201
|
+
<path
|
|
202
|
+
fill="currentColor"
|
|
203
|
+
d="M593.8 59.1H46.2C20.7 59.1 0 79.8 0 105.2v301.5c0 25.5 20.7 46.2 46.2 46.2h547.7c25.5 0 46.2-20.7 46.1-46.1V105.2c0-25.4-20.7-46.1-46.2-46.1M338.5 360.6H277v-120l-61.5 76.9l-61.5-76.9v120H92.3V151.4h61.5l61.5 76.9l61.5-76.9h61.5v209.2zm135.3 3.1L381.5 256H443V151.4h61.5V256H566z"
|
|
204
|
+
/>
|
|
205
|
+
</svg>
|
|
206
|
+
</div>
|
|
207
|
+
<div class="article-info-content">
|
|
208
|
+
<span class="article-info-label">{markdownSourceLabel}</span>
|
|
209
|
+
<span class="article-info-value">
|
|
210
|
+
<a href={markdownSourceUrl} target="_blank" rel="noopener" type={markdownSourceType}>
|
|
211
|
+
{decodeURI(markdownSourceUrl)}
|
|
212
|
+
</a>
|
|
213
|
+
</span>
|
|
214
|
+
</div>
|
|
215
|
+
</div>
|
|
216
|
+
)}
|
|
217
|
+
{page.date && (
|
|
218
|
+
<div class="article-info-item">
|
|
219
|
+
<div class="article-info-icon">
|
|
220
|
+
<svg
|
|
221
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
222
|
+
width="18"
|
|
223
|
+
height="18"
|
|
224
|
+
viewBox="0 0 24 24"
|
|
225
|
+
fill="none"
|
|
226
|
+
stroke="currentColor"
|
|
227
|
+
stroke-width="2"
|
|
228
|
+
stroke-linecap="round"
|
|
229
|
+
stroke-linejoin="round"
|
|
230
|
+
role="img"
|
|
231
|
+
aria-label="Created time"
|
|
232
|
+
>
|
|
233
|
+
<title>Created time</title>
|
|
234
|
+
<circle cx="12" cy="12" r="10" />
|
|
235
|
+
<polyline points="12 6 12 12 16 14" />
|
|
236
|
+
</svg>
|
|
237
|
+
</div>
|
|
238
|
+
<div class="article-info-content">
|
|
239
|
+
<span class="article-info-label">{helper.__("article.created_time")}</span>
|
|
240
|
+
<span class="article-info-value">{helper.date(page.date, "YYYY-MM-DD HH:mm")}</span>
|
|
241
|
+
</div>
|
|
242
|
+
</div>
|
|
243
|
+
)}
|
|
244
|
+
{page.updated && (
|
|
245
|
+
<div class="article-info-item">
|
|
246
|
+
<div class="article-info-icon">
|
|
247
|
+
<svg
|
|
248
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
249
|
+
width="18"
|
|
250
|
+
height="18"
|
|
251
|
+
viewBox="0 0 24 24"
|
|
252
|
+
fill="none"
|
|
253
|
+
stroke="currentColor"
|
|
254
|
+
stroke-width="2"
|
|
255
|
+
stroke-linecap="round"
|
|
256
|
+
stroke-linejoin="round"
|
|
257
|
+
role="img"
|
|
258
|
+
aria-label="Updated time"
|
|
259
|
+
>
|
|
260
|
+
<title>Updated time</title>
|
|
261
|
+
<path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" />
|
|
262
|
+
<path d="M3 3v5h5" />
|
|
263
|
+
</svg>
|
|
264
|
+
</div>
|
|
265
|
+
<div class="article-info-content">
|
|
266
|
+
<span class="article-info-label">{helper.__("article.updated_time")}</span>
|
|
267
|
+
<span class="article-info-value">{helper.date(page.updated, "YYYY-MM-DD HH:mm")}</span>
|
|
268
|
+
</div>
|
|
269
|
+
</div>
|
|
270
|
+
)}
|
|
271
|
+
{article?.licenses && Object.keys(article.licenses).length > 0 && (
|
|
272
|
+
<div class="article-info-item">
|
|
273
|
+
<div class="article-info-icon">
|
|
274
|
+
<svg
|
|
275
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
276
|
+
width="18"
|
|
277
|
+
height="18"
|
|
278
|
+
viewBox="0 0 24 24"
|
|
279
|
+
fill="none"
|
|
280
|
+
stroke="currentColor"
|
|
281
|
+
stroke-width="2"
|
|
282
|
+
stroke-linecap="round"
|
|
283
|
+
stroke-linejoin="round"
|
|
284
|
+
role="img"
|
|
285
|
+
aria-label="License"
|
|
286
|
+
>
|
|
287
|
+
<title>License</title>
|
|
288
|
+
<path fill="none" d="M15 21H6a3 3 0 0 1-3-3v-1h10v2a2 2 0 0 0 4 0V5a2 2 0 1 1 2 2h-2m2-4H8a3 3 0 0 0-3 3v11M9 7h4m-4 4h4" />
|
|
289
|
+
<polyline points="14 2 14 8 20 8" />
|
|
290
|
+
</svg>
|
|
291
|
+
</div>
|
|
292
|
+
<div class="article-info-content">
|
|
293
|
+
<span class="article-info-label">{helper.__("article.license")}</span>
|
|
294
|
+
<span class="article-info-value">
|
|
295
|
+
{Object.keys(article.licenses).map((name, i) => (
|
|
296
|
+
<span key={name}>
|
|
297
|
+
{i > 0 && <span>, </span>}
|
|
298
|
+
<a href={article.licenses[name]} target="_blank" rel="noopener">
|
|
299
|
+
{name}
|
|
300
|
+
</a>
|
|
301
|
+
</span>
|
|
302
|
+
))}
|
|
303
|
+
</span>
|
|
304
|
+
</div>
|
|
305
|
+
</div>
|
|
306
|
+
)}
|
|
307
|
+
{page.location && (
|
|
308
|
+
<div class="article-info-item">
|
|
309
|
+
<div class="article-info-icon">
|
|
310
|
+
<svg
|
|
311
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
312
|
+
width="18"
|
|
313
|
+
height="18"
|
|
314
|
+
viewBox="0 0 24 24"
|
|
315
|
+
fill="none"
|
|
316
|
+
stroke="currentColor"
|
|
317
|
+
stroke-width="2"
|
|
318
|
+
stroke-linecap="round"
|
|
319
|
+
stroke-linejoin="round"
|
|
320
|
+
role="img"
|
|
321
|
+
aria-label="Location"
|
|
322
|
+
>
|
|
323
|
+
<title>Location</title>
|
|
324
|
+
<path d="M20 10c0 6-8 12-8 12s-8-6-8-12a8 8 0 0 1 16 0Z" />
|
|
325
|
+
<circle cx="12" cy="10" r="3" />
|
|
326
|
+
</svg>
|
|
327
|
+
</div>
|
|
328
|
+
<div class="article-info-content">
|
|
329
|
+
<span class="article-info-label">{helper.__("article.location")}</span>
|
|
330
|
+
<span class="article-info-value">{page.location}</span>
|
|
331
|
+
</div>
|
|
332
|
+
</div>
|
|
333
|
+
)}
|
|
334
|
+
</div>
|
|
335
|
+
</div>
|
|
336
|
+
</div>
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
};
|
|
@@ -1,18 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Article media component, used in article lists such as archive page and recent posts widget
|
|
3
3
|
*/
|
|
4
|
-
const { Component, dateFormatters, parseISO } = require("../../include/util/common");
|
|
4
|
+
const { Component, dateFormatters, isValidDate, parseISO } = require("../../include/util/common");
|
|
5
|
+
|
|
6
|
+
function formatDate(date, dateXml) {
|
|
7
|
+
if (date) return date;
|
|
8
|
+
|
|
9
|
+
const parsedDate = parseISO(dateXml);
|
|
10
|
+
return isValidDate(parsedDate) ? dateFormatters.shortDay.format(parsedDate) : "";
|
|
11
|
+
}
|
|
5
12
|
|
|
6
13
|
module.exports = class extends Component {
|
|
7
14
|
render() {
|
|
8
|
-
const { url, title, date } = this.props;
|
|
9
|
-
const formattedDate =
|
|
15
|
+
const { url, title, date, dateXml } = this.props;
|
|
16
|
+
const formattedDate = formatDate(date, dateXml);
|
|
10
17
|
|
|
11
18
|
return (
|
|
12
19
|
<article class="archive-item">
|
|
13
20
|
<div>
|
|
14
21
|
<p class="article-meta">
|
|
15
|
-
<
|
|
22
|
+
<time dateTime={dateXml || null}>{formattedDate}</time>
|
|
16
23
|
</p>
|
|
17
24
|
<a class="archive-title" href={url}>
|
|
18
25
|
{title}
|
|
@@ -2,20 +2,28 @@ const { Component, loadComponent } = require("../../include/util/common");
|
|
|
2
2
|
|
|
3
3
|
module.exports = class extends Component {
|
|
4
4
|
render() {
|
|
5
|
-
const { config, page, helper } = this.props;
|
|
5
|
+
const { config, page, helper, embedded = false } = this.props;
|
|
6
6
|
const { comment } = config;
|
|
7
7
|
if (!comment || typeof comment.type !== "string") {
|
|
8
8
|
return null;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
const content = (() => {
|
|
12
|
+
const Comment = loadComponent(`comment/${comment.type}`);
|
|
13
|
+
return <Comment config={config} page={page} helper={helper} comment={comment} />;
|
|
14
|
+
})();
|
|
15
|
+
|
|
16
|
+
if (embedded) {
|
|
17
|
+
return (
|
|
18
|
+
<div class="article-comment-panel" id="comments">
|
|
19
|
+
{content}
|
|
20
|
+
</div>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
11
24
|
return (
|
|
12
25
|
<div class="card" id="comments">
|
|
13
|
-
<div class="card-content">
|
|
14
|
-
{(() => {
|
|
15
|
-
const Comment = loadComponent(`comment/${comment.type}`);
|
|
16
|
-
return <Comment config={config} page={page} helper={helper} comment={comment} />;
|
|
17
|
-
})()}
|
|
18
|
-
</div>
|
|
26
|
+
<div class="card-content">{content}</div>
|
|
19
27
|
</div>
|
|
20
28
|
);
|
|
21
29
|
}
|
package/layout/common/footer.jsx
CHANGED
|
@@ -66,7 +66,7 @@ class Footer extends Component {
|
|
|
66
66
|
|
|
67
67
|
const footer_subdomains = (
|
|
68
68
|
<div class="footer-column footer-subdomains">
|
|
69
|
-
<p class="footer-heading">
|
|
69
|
+
<p class="footer-heading">Quick Links</p>
|
|
70
70
|
<div class="footer-links">
|
|
71
71
|
{Object.keys(subdomains).length
|
|
72
72
|
? Object.keys(subdomains).map((name) => {
|
|
@@ -95,9 +95,10 @@ class Footer extends Component {
|
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
module.exports = cacheComponent(Footer, "common.footer", (props) => {
|
|
98
|
-
const { config, helper, site } = props;
|
|
98
|
+
const { config, helper, page, site } = props;
|
|
99
99
|
const { url_for, _p, date } = helper;
|
|
100
100
|
const { title, author, footer, plugins } = config;
|
|
101
|
+
const langKey = helper.language_key(page);
|
|
101
102
|
|
|
102
103
|
const links = {};
|
|
103
104
|
if (footer?.links) {
|
|
@@ -116,7 +117,7 @@ module.exports = cacheComponent(Footer, "common.footer", (props) => {
|
|
|
116
117
|
const link = footer.subdomains[name];
|
|
117
118
|
const targetUrl = typeof link === "string" ? link : link.url;
|
|
118
119
|
subdomains[name] = {
|
|
119
|
-
url:
|
|
120
|
+
url: helper.localized_url_for(targetUrl, langKey),
|
|
120
121
|
};
|
|
121
122
|
});
|
|
122
123
|
}
|
|
@@ -139,11 +140,11 @@ module.exports = cacheComponent(Footer, "common.footer", (props) => {
|
|
|
139
140
|
|
|
140
141
|
archives = Object.keys(byYear)
|
|
141
142
|
.sort((a, b) => Number(b) - Number(a))
|
|
142
|
-
.map((year) => ({ year, url:
|
|
143
|
+
.map((year) => ({ year, url: helper.localized_url_for(`${archiveDir}/${year}/`, langKey) }));
|
|
143
144
|
}
|
|
144
145
|
|
|
145
146
|
return {
|
|
146
|
-
siteUrl:
|
|
147
|
+
siteUrl: helper.localized_url_for("/", langKey),
|
|
147
148
|
siteTitle: title,
|
|
148
149
|
siteYear: date(new Date(), "YYYY"),
|
|
149
150
|
author,
|
package/layout/common/head.jsx
CHANGED
|
@@ -3,6 +3,11 @@ const MetaTags = require("../../layout/misc/meta");
|
|
|
3
3
|
const OpenGraph = require("../../layout/misc/open_graph");
|
|
4
4
|
const StructuredData = require("../../layout/misc/structured_data");
|
|
5
5
|
const Plugins = require("./plugins");
|
|
6
|
+
const { getArticleFontInitScript } = require("../../include/util/article_font");
|
|
7
|
+
const { getThemeInitScript } = require("../../include/util/theme");
|
|
8
|
+
const { getDefaultLanguageKey, getI18nKey, getLanguage, getPageLanguageKey, getPageLocale, isI18nEnabled, normalizeLocale, toArray } = require("../../include/util/i18n");
|
|
9
|
+
const fs = require("node:fs");
|
|
10
|
+
const path = require("node:path");
|
|
6
11
|
|
|
7
12
|
function getPageTitle(page, siteTitle, helper) {
|
|
8
13
|
let title = page.title;
|
|
@@ -23,16 +28,111 @@ function getPageTitle(page, siteTitle, helper) {
|
|
|
23
28
|
return [title, siteTitle].filter((str) => typeof str !== "undefined" && str.trim() !== "").join(" - ");
|
|
24
29
|
}
|
|
25
30
|
|
|
31
|
+
function getTermName(term) {
|
|
32
|
+
if (!term) return undefined;
|
|
33
|
+
if (typeof term === "string") return term;
|
|
34
|
+
|
|
35
|
+
return term.name || term.slug || term.path;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getArticleSection(page) {
|
|
39
|
+
if (page.category) return getTermName(page.category);
|
|
40
|
+
|
|
41
|
+
const categories = page.categories;
|
|
42
|
+
if (!categories) return undefined;
|
|
43
|
+
if (typeof categories.first === "function") return getTermName(categories.first());
|
|
44
|
+
if (Array.isArray(categories.data)) return getTermName(categories.data[0]);
|
|
45
|
+
if (Array.isArray(categories)) return getTermName(categories[0]);
|
|
46
|
+
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function toAbsoluteUrl(href, helper, config) {
|
|
51
|
+
if (!href || typeof href !== "string") return null;
|
|
52
|
+
if (/^https?:\/\//i.test(href)) return href;
|
|
53
|
+
if (typeof helper.full_url_for === "function") return helper.full_url_for(href);
|
|
54
|
+
|
|
55
|
+
const localUrl = typeof helper.url_for === "function" ? helper.url_for(href) : href;
|
|
56
|
+
if (/^https?:\/\//i.test(localUrl)) return localUrl;
|
|
57
|
+
|
|
58
|
+
const siteUrl = String(config.url || "").replace(/\/+$/, "");
|
|
59
|
+
const path = localUrl.startsWith("/") ? localUrl : `/${localUrl}`;
|
|
60
|
+
return `${siteUrl}${path}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function addAlternateLink(links, hreflang, href, helper, config) {
|
|
64
|
+
const normalizedHreflang = hreflang === "x-default" ? "x-default" : normalizeLocale(hreflang);
|
|
65
|
+
const absoluteHref = toAbsoluteUrl(href, helper, config);
|
|
66
|
+
if (!normalizedHreflang || !absoluteHref) return;
|
|
67
|
+
links.set(normalizedHreflang.toLowerCase(), {
|
|
68
|
+
hreflang: normalizedHreflang,
|
|
69
|
+
href: absoluteHref,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function addExplicitAlternates(links, alternates, helper, config) {
|
|
74
|
+
if (!alternates || typeof alternates !== "object") return;
|
|
75
|
+
|
|
76
|
+
Object.keys(alternates).forEach((key) => {
|
|
77
|
+
const value = alternates[key];
|
|
78
|
+
const href = typeof value === "string" ? value : value?.href || value?.url;
|
|
79
|
+
const language = getLanguage(config, key);
|
|
80
|
+
const hreflang = key === "x-default" ? "x-default" : language?.key === key ? language.locale : key;
|
|
81
|
+
addAlternateLink(links, hreflang, href, helper, config);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function collectDocuments(site) {
|
|
86
|
+
return [...toArray(site?.posts), ...toArray(site?.pages)];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function getHreflangLinks(site, page, config, helper) {
|
|
90
|
+
if (!isI18nEnabled(config)) return [];
|
|
91
|
+
|
|
92
|
+
const links = new Map();
|
|
93
|
+
const pageKey = getI18nKey(page);
|
|
94
|
+
const langKey = getPageLanguageKey(page, config);
|
|
95
|
+
const locale = getPageLocale(page, config);
|
|
96
|
+
|
|
97
|
+
addAlternateLink(links, locale, page.permalink || page.path, helper, config);
|
|
98
|
+
addExplicitAlternates(links, page.i18n?.alternates || page.alternates || page.hreflang, helper, config);
|
|
99
|
+
|
|
100
|
+
if (pageKey) {
|
|
101
|
+
collectDocuments(site).forEach((item) => {
|
|
102
|
+
if (!item || getI18nKey(item) !== pageKey) return;
|
|
103
|
+
addAlternateLink(links, getPageLocale(item, config), item.permalink || item.path, helper, config);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (links.size > 1 && !links.has("x-default")) {
|
|
108
|
+
const defaultLanguage = getLanguage(config, getDefaultLanguageKey(config));
|
|
109
|
+
const defaultLink = Array.from(links.values()).find((link) => link.hreflang.toLowerCase() === defaultLanguage.locale.toLowerCase());
|
|
110
|
+
if (defaultLink) addAlternateLink(links, "x-default", defaultLink.href, helper, config);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return Array.from(links.values()).sort((a, b) => {
|
|
114
|
+
if (a.hreflang === "x-default") return 1;
|
|
115
|
+
if (b.hreflang === "x-default") return -1;
|
|
116
|
+
if (a.hreflang === getLanguage(config, langKey).locale) return -1;
|
|
117
|
+
if (b.hreflang === getLanguage(config, langKey).locale) return 1;
|
|
118
|
+
return a.hreflang.localeCompare(b.hreflang);
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
26
122
|
module.exports = class extends Component {
|
|
27
123
|
render() {
|
|
28
124
|
const { site, config, helper, page } = this.props;
|
|
29
125
|
const { url_for, is_post } = helper;
|
|
30
126
|
const { url, head = {}, article } = config;
|
|
31
|
-
const { meta = [], open_graph = {}, structured_data = {}, canonical_url = page.permalink, favicon } = head;
|
|
127
|
+
const { meta = [], open_graph = {}, structured_data = {}, canonical_url: headCanonicalUrl = page.permalink, favicon } = head;
|
|
128
|
+
const markdownSourceUrl = page.markdown_path ? url_for(page.markdown_path) : null;
|
|
129
|
+
const markdownSourceType = "text/markdown; charset=utf-8";
|
|
32
130
|
|
|
33
131
|
const noIndex = helper.is_archive() || helper.is_tag();
|
|
34
132
|
|
|
35
|
-
const language = page.lang || page.language || config.language;
|
|
133
|
+
const language = getPageLocale(page, config) || page.lang || page.language || config.language;
|
|
134
|
+
const canonicalUrl = toAbsoluteUrl(page.canonical_url || page.canonical || page.i18n?.canonical || headCanonicalUrl, helper, config);
|
|
135
|
+
const hreflangLinks = getHreflangLinks(site, page, config, helper);
|
|
36
136
|
|
|
37
137
|
let images;
|
|
38
138
|
if (typeof page.og_image === "string") {
|
|
@@ -68,31 +168,15 @@ module.exports = class extends Component {
|
|
|
68
168
|
structuredImages = page.photos;
|
|
69
169
|
}
|
|
70
170
|
|
|
71
|
-
const themeInitScript =
|
|
72
|
-
(
|
|
73
|
-
|
|
74
|
-
mocha: "night",
|
|
75
|
-
macchiato: "night",
|
|
76
|
-
nord: "light",
|
|
77
|
-
nord_night: "night",
|
|
78
|
-
rose_pine: "night",
|
|
79
|
-
tokyo_night: "night",
|
|
80
|
-
latte: "light"
|
|
81
|
-
};
|
|
82
|
-
var stored = localStorage.getItem("themePreference");
|
|
83
|
-
var theme = stored && stored in THEME_MAP ? stored : "system";
|
|
84
|
-
var html = document.documentElement;
|
|
85
|
-
var resolvedTheme = theme === "system"
|
|
86
|
-
? window.matchMedia("(prefers-color-scheme: dark)").matches ? "mocha" : "nord"
|
|
87
|
-
: theme;
|
|
88
|
-
html.setAttribute("data-theme", resolvedTheme);
|
|
89
|
-
html.classList.add(THEME_MAP[resolvedTheme]);
|
|
90
|
-
})();
|
|
91
|
-
`;
|
|
171
|
+
const themeInitScript = getThemeInitScript();
|
|
172
|
+
const articleFontInitScript = getArticleFontInitScript();
|
|
173
|
+
const articleFontUtilsScript = fs.readFileSync(path.join(__dirname, "../../source/js/article-font-utils.js"), "utf8");
|
|
92
174
|
|
|
93
175
|
return (
|
|
94
176
|
<head>
|
|
95
177
|
<script dangerouslySetInnerHTML={{ __html: themeInitScript }}></script>
|
|
178
|
+
<script dangerouslySetInnerHTML={{ __html: articleFontUtilsScript }}></script>
|
|
179
|
+
<script dangerouslySetInnerHTML={{ __html: articleFontInitScript }}></script>
|
|
96
180
|
<meta charset="utf-8" />
|
|
97
181
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
98
182
|
{noIndex ? <meta name="robots" content="noindex" /> : null}
|
|
@@ -109,7 +193,11 @@ module.exports = class extends Component {
|
|
|
109
193
|
keywords={(page.tags?.length ? page.tags : undefined) || config.keywords}
|
|
110
194
|
url={open_graph.url || page.permalink || url}
|
|
111
195
|
images={openGraphImages}
|
|
196
|
+
imageAlt={open_graph.image_alt || page.og_image_alt || page.cover_alt || page.title || config.title}
|
|
197
|
+
imageWidth={open_graph.image_width}
|
|
198
|
+
imageHeight={open_graph.image_height}
|
|
112
199
|
siteName={open_graph.site_name || config.title}
|
|
200
|
+
section={open_graph.section || getArticleSection(page)}
|
|
113
201
|
language={language}
|
|
114
202
|
twitterId={open_graph.twitter_id}
|
|
115
203
|
twitterCard={open_graph.twitter_card}
|
|
@@ -132,18 +220,19 @@ module.exports = class extends Component {
|
|
|
132
220
|
images={structuredImages}
|
|
133
221
|
/>
|
|
134
222
|
) : null}
|
|
135
|
-
{
|
|
136
|
-
{
|
|
223
|
+
{canonicalUrl ? <link rel="canonical" href={canonicalUrl} /> : null}
|
|
224
|
+
{hreflangLinks.map((link) => (
|
|
225
|
+
<link rel="alternate" hreflang={link.hreflang} href={link.href} />
|
|
226
|
+
))}
|
|
227
|
+
{is_post(page) && markdownSourceUrl ? <link rel="alternate" type={markdownSourceType} title={helper.__("article.markdown_source")} href={markdownSourceUrl} /> : null}
|
|
228
|
+
<link rel="icon" href={url_for(favicon || "/img/favicon.svg")} />
|
|
137
229
|
<link rel="stylesheet" href={url_for("/css/default.css")} />
|
|
138
|
-
<link rel="stylesheet" href={url_for("/css/responsive
|
|
139
|
-
<link rel="stylesheet" href={url_for("/css/
|
|
140
|
-
<link rel="stylesheet" href={url_for("/css/responsive/touch.css")} media="screen and (max-width:1023px)" />
|
|
141
|
-
<link rel="stylesheet" href={url_for("/css/responsive/desktop.css")} media="screen and (min-width:1024px)" />
|
|
142
|
-
<link rel="preload" as="style" href={url_for("/css/callout_blocks.css")} onload="this.onload=null;this.rel='stylesheet'" />
|
|
230
|
+
<link rel="stylesheet" href={url_for("/css/responsive.css")} />
|
|
231
|
+
<link rel="stylesheet" href={url_for("/css/callout_blocks.css")} media="print" onload="this.media='all'" />
|
|
143
232
|
<link rel="preload" href={url_for("/css/font/woff2/HomemadeApple.woff2")} as="font" type="font/woff2" crossorigin />
|
|
144
233
|
<link rel="preconnect" href="https://fontsapi.zeoseven.com" />
|
|
145
|
-
<link rel="
|
|
146
|
-
<link rel="
|
|
234
|
+
<link rel="stylesheet" href="https://fontsapi.zeoseven.com/5/main/result.css" media="print" onload="this.media='all'" />
|
|
235
|
+
<link rel="stylesheet" href="https://fontsapi.zeoseven.com/442/main/result.css" media="print" onload="this.media='all'" />
|
|
147
236
|
<link rel="preload" as="style" href="/css/shiki/shiki.css" onload="this.onload=null;this.rel='stylesheet'" />
|
|
148
237
|
{page.encrypt ? <link rel="stylesheet" href={url_for("/css/encrypt.css")} /> : null}
|
|
149
238
|
<Plugins site={site} config={config} helper={helper} page={page} head={true} />
|