hexo-theme-gnix 8.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.
Files changed (66) hide show
  1. package/README.md +4 -2
  2. package/include/hexo/feed.js +6 -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/util/article_font.js +132 -0
  13. package/include/util/i18n.js +280 -0
  14. package/include/util/theme.js +84 -0
  15. package/languages/en.yml +28 -0
  16. package/languages/zh-CN.yml +28 -0
  17. package/layout/archive.jsx +131 -127
  18. package/layout/common/article.jsx +316 -34
  19. package/layout/common/article_info.jsx +339 -0
  20. package/layout/common/article_media.jsx +11 -4
  21. package/layout/common/comment.jsx +15 -7
  22. package/layout/common/footer.jsx +6 -5
  23. package/layout/common/head.jsx +122 -33
  24. package/layout/common/navbar.jsx +195 -65
  25. package/layout/common/theme_selector.jsx +16 -14
  26. package/layout/layout.jsx +43 -5
  27. package/layout/misc/open_graph.jsx +162 -66
  28. package/layout/misc/paginator.jsx +2 -8
  29. package/layout/plugin/cookie_consent.jsx +252 -53
  30. package/layout/plugin/swup.jsx +1 -1
  31. package/layout/search/insight.jsx +1 -1
  32. package/layout/tag.jsx +3 -2
  33. package/layout/tags.jsx +81 -73
  34. package/package.json +5 -5
  35. package/scripts/index.js +1 -0
  36. package/source/css/archive.css +225 -180
  37. package/source/css/default.css +1223 -126
  38. package/source/css/responsive.css +426 -0
  39. package/source/css/shiki/shiki.css +12 -2081
  40. package/source/css/tags.css +183 -0
  41. package/source/css/twikoo.css +1053 -1049
  42. package/source/img/favicon.svg +1 -6
  43. package/source/img/og_image.webp +0 -0
  44. package/source/js/article-font-utils.js +99 -0
  45. package/source/js/busuanzi.js +91 -24
  46. package/source/js/components/chat.js +169 -50
  47. package/source/js/components/image-carousel.js +152 -108
  48. package/source/js/components/sidenote.js +210 -0
  49. package/source/js/components/text-image-section.js +78 -90
  50. package/source/js/components/theme-stacked.js +65 -33
  51. package/source/js/components/tree.js +30 -16
  52. package/source/js/decrypt.js +7 -2
  53. package/source/js/main.js +428 -5
  54. package/source/js/swup.js +39 -0
  55. package/source/js/theme-selector.js +26 -16
  56. package/include/hexo/generator.js +0 -53
  57. package/layout/misc/article_licensing.jsx +0 -99
  58. package/source/css/responsive/desktop.css +0 -36
  59. package/source/css/responsive/mobile.css +0 -38
  60. package/source/css/responsive/tablet.css +0 -43
  61. package/source/css/responsive/touch.css +0 -155
  62. package/source/img/logo.svg +0 -9
  63. package/source/js/archive-breadcrumb.js +0 -132
  64. package/source/js/host/cookieconsent/3.1.1/build/cookieconsent.min.css +0 -6
  65. package/source/js/host/cookieconsent/3.1.1/build/cookieconsent.min.js +0 -1
  66. package/source/js/swup.bundle.js +0 -1
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 %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"
@@ -1,160 +1,164 @@
1
1
  const { Component, Fragment, isValidDate, parseISO, dateFormatters } = require("../include/util/common");
2
+ const { filterByLanguage } = require("../include/util/i18n");
2
3
  const ArticleMedia = require("./common/article_media");
3
4
 
5
+ function collectPosts(collection) {
6
+ const posts = [];
7
+ if (collection?.each) {
8
+ collection.each((post) => posts.push(post));
9
+ } else if (Array.isArray(collection)) {
10
+ posts.push(...collection);
11
+ }
12
+ return posts;
13
+ }
14
+
15
+ function getSeason(month) {
16
+ if (month >= 2 && month <= 4) return "Spring";
17
+ if (month >= 5 && month <= 7) return "Summer";
18
+ if (month >= 8 && month <= 10) return "Autumn";
19
+ return "Winter";
20
+ }
21
+
22
+ function getArchiveRangeLabel(year, month = null, season = null) {
23
+ if (!year) return "All Posts";
24
+ if (month) {
25
+ const time = parseISO(`${year}-${String(month).padStart(2, "0")}-01T00:00:00.000Z`);
26
+ return isValidDate(time) ? `${dateFormatters.longMonth.format(time)} ${year}` : String(year);
27
+ }
28
+ return season ? `${season} ${year}` : String(year);
29
+ }
30
+
31
+ function groupPostsBySeason(posts) {
32
+ return posts.reduce((groups, post) => {
33
+ const year = post.date.year();
34
+ const season = getSeason(post.date.month());
35
+ const lastGroup = groups[groups.length - 1];
36
+
37
+ if (lastGroup && lastGroup.year === year && lastGroup.season === season) {
38
+ lastGroup.posts.push(post);
39
+ } else {
40
+ groups.push({ year, season, posts: [post] });
41
+ }
42
+
43
+ return groups;
44
+ }, []);
45
+ }
46
+
47
+ function collectArchiveYears(posts) {
48
+ return Array.from(new Set(posts.map((post) => post.date.year()))).sort((a, b) => b - a);
49
+ }
50
+
51
+ function getPostDateParts(postDate, dateXml, date) {
52
+ const xml = dateXml(postDate);
53
+ const parsed = parseISO(xml);
54
+
55
+ return {
56
+ label: isValidDate(parsed) ? dateFormatters.shortDay.format(parsed) : date(postDate),
57
+ xml,
58
+ };
59
+ }
60
+
4
61
  module.exports = class extends Component {
5
62
  render() {
6
63
  const { page, helper, site, config } = this.props;
7
64
  const { url_for, date_xml, date } = helper;
8
-
9
- function getSeason(month) {
10
- if (month >= 2 && month <= 4) return "Spring";
11
- if (month >= 5 && month <= 7) return "Summer";
12
- if (month >= 8 && month <= 10) return "Autumn";
13
- return "Winter";
14
- }
65
+ const langKey = helper.language_key(page);
15
66
 
16
67
  function renderArticleList(posts, year, month = null, sectionTitle = null, season = null) {
17
- let time = null;
18
- if (page.year) {
19
- const mm = page.month ? String(page.month).padStart(2, "0") : "01";
20
- time = parseISO(`${page.year}-${mm}-01T00:00:00.000Z`);
21
- }
22
-
23
- let title = `'${String(year).slice(-2)}`;
24
- if (sectionTitle) {
25
- title = sectionTitle;
26
- } else if (month !== null && isValidDate(time)) {
27
- title = dateFormatters.longMonth.format(time);
28
- }
68
+ const title = sectionTitle || getArchiveRangeLabel(year, month, season);
69
+ const kicker = year ? String(year) : "Archive";
70
+ const marker = season ? season.toLowerCase() : "all";
71
+ const countLabel = posts.length === 1 ? "1 entry" : `${posts.length} entries`;
72
+ const sectionId = `archive-${kicker}-${marker}-${month || "all"}`;
29
73
 
30
74
  return (
31
- <div class="card">
32
- <div class={["card-content", season ? season.toLowerCase() : null].filter(Boolean).join(" ")}>
33
- <span class="year">{title}</span>
34
- <div class="timeline">
35
- {posts.map((post) => {
36
- return <ArticleMedia url={url_for(post.link || post.path)} title={post.title} date={date(post.date)} dateXml={date_xml(post.date)} />;
37
- })}
75
+ <section class={["archive-group", marker].filter(Boolean).join(" ")} aria-labelledby={sectionId}>
76
+ <header class="archive-group__header">
77
+ <div>
78
+ <h2 id={sectionId} class="archive-group__title">
79
+ {title}
80
+ </h2>
38
81
  </div>
82
+ <span class="archive-group__count">{countLabel}</span>
83
+ </header>
84
+ <div class="timeline">
85
+ {posts.map((post) => {
86
+ const postDate = getPostDateParts(post.date, date_xml, date);
87
+ return <ArticleMedia key={post.path} url={url_for(post.link || post.path)} title={post.title} date={postDate.label} dateXml={postDate.xml} />;
88
+ })}
39
89
  </div>
40
- </div>
90
+ </section>
41
91
  );
42
92
  }
43
93
 
94
+ const visiblePosts = collectPosts(page.posts);
95
+ const totalVisiblePosts = visiblePosts.length;
96
+ const latestPost = visiblePosts[0];
97
+
98
+ const allPostsSource = site?.posts ? filterByLanguage(site.posts.sort("date", -1), langKey, config) : page.posts;
99
+ const allPosts = collectPosts(allPostsSource);
100
+ const years = collectArchiveYears(allPosts);
44
101
  let articleList;
45
102
  if (!page.year) {
46
- const groups = [];
47
- page.posts.each((p) => {
48
- const year = p.date.year();
49
- const month = p.date.month(); // 0-11
50
- const season = getSeason(month);
51
-
52
- const lastGroup = groups[groups.length - 1];
53
- if (lastGroup && lastGroup.year === year && lastGroup.season === season) {
54
- lastGroup.posts.push(p);
55
- } else {
56
- groups.push({
57
- year,
58
- season,
59
- posts: [p],
60
- });
61
- }
62
- });
63
-
64
- articleList = groups.map((group) => {
65
- const title = `'${String(group.year).slice(-2)} ${group.season}`;
66
- return renderArticleList(group.posts, group.year, null, title, group.season);
103
+ articleList = groupPostsBySeason(visiblePosts).map((group) => {
104
+ const title = getArchiveRangeLabel(group.year, null, group.season);
105
+ return <Fragment key={`${group.year}-${group.season}`}>{renderArticleList(group.posts, group.year, null, title, group.season)}</Fragment>;
67
106
  });
68
107
  } else {
69
108
  const season = page.month ? getSeason(page.month - 1) : null;
70
- articleList = renderArticleList(page.posts, page.year, page.month, null, season);
109
+ articleList = renderArticleList(visiblePosts, page.year, page.month, null, season);
71
110
  }
72
111
 
73
112
  const archiveDir = config?.archive_dir || "archives";
74
- const archiveBasePath = url_for(`/${archiveDir}/`);
113
+ const archiveBasePath = helper.localized_url_for(`/${archiveDir}/`, langKey);
75
114
  const currentYear = page.year ? Number(page.year) : null;
76
115
  const currentMonth = page.month ? Number(page.month) : null;
77
-
78
- const yearToMonths = new Map();
79
- const allPosts = site?.posts ? site.posts.sort("date", -1) : page.posts;
80
- if (allPosts?.each) {
81
- allPosts.each((p) => {
82
- const y = p.date.year();
83
- const m = p.date.month() + 1;
84
- const existing = yearToMonths.get(y);
85
- if (existing) {
86
- existing.add(m);
87
- } else {
88
- yearToMonths.set(y, new Set([m]));
89
- }
90
- });
91
- }
92
- const years = Array.from(yearToMonths.keys()).sort((a, b) => b - a);
93
- const monthsForCurrentYear = currentYear && yearToMonths.get(currentYear) ? Array.from(yearToMonths.get(currentYear)).sort((a, b) => a - b) : [];
116
+ const activeScope = getArchiveRangeLabel(currentYear, currentMonth);
117
+ const yearCountLabel = years.length === 1 ? "1 year" : `${years.length} years`;
118
+ const scopeSummaryLabel = currentYear ? `from ${activeScope}` : `across ${yearCountLabel}`;
119
+ const latestLabel = latestPost ? date(latestPost.date) : "No posts yet";
94
120
 
95
121
  return (
96
122
  <Fragment>
97
- <script data-swup-reload-script defer src="/js/archive-breadcrumb.js"></script>
98
- <link rel="stylesheet" href="/css/archive.css" />
99
- <nav class="archive-breadcrumb" aria-label="archive breadcrumb" data-archive-breadcrumb data-archive-dir={archiveDir}>
100
- <span class="prompt">$</span>{" "}
101
- <span class="archive-breadcrumb__cmd" style="color: var(--blue)">
102
- ls
103
- </span>{" "}
104
- <span class="archive-breadcrumb__cmd" style="color: var(--yellow)">
105
- posts
106
- </span>
107
- <span class="archive-breadcrumb__cmd" style="padding: 0">
108
- /
109
- </span>
110
- <span class="archive-breadcrumb__picker" data-picker="year">
111
- <button type="button" class="archive-breadcrumb__trigger" aria-haspopup="listbox" aria-expanded="false" aria-label="Select year">
112
- <span data-label="year">{currentYear ? String(currentYear) : "*"}</span>
113
- </button>
114
- <div class="archive-breadcrumb__menu" role="listbox" tabindex="-1" data-menu="year">
115
- <button type="button" class="archive-breadcrumb__option" role="option" data-href={archiveBasePath} aria-selected={!currentYear ? "true" : "false"}>
116
- *
117
- </button>
118
- {years.map((y) => (
119
- <button type="button" class="archive-breadcrumb__option" role="option" data-href={url_for(`/${archiveDir}/${y}/`)} aria-selected={currentYear === y ? "true" : "false"}>
120
- {y}
121
- </button>
122
- ))}
123
+ <link rel="stylesheet" href={url_for("/css/archive.css")} data-page-head />
124
+ <main class="archive-page">
125
+ <header class="archive-hero">
126
+ <div class="archive-hero__copy">
127
+ <p class="archive-eyebrow">Archive Index</p>
128
+ <h1>{activeScope}</h1>
129
+ <p class="archive-hero__summary">
130
+ {totalVisiblePosts ? `Browsing ${totalVisiblePosts} ${totalVisiblePosts === 1 ? "post" : "posts"} ${scopeSummaryLabel}.` : "No posts are available in this archive yet."}
131
+ </p>
123
132
  </div>
124
- </span>
125
- <span class="archive-breadcrumb__cmd" style="padding: 0">
126
- /
127
- </span>
128
- <span class="archive-breadcrumb__picker" data-picker="month">
129
- <button type="button" class="archive-breadcrumb__trigger" aria-haspopup="listbox" aria-expanded="false" aria-label="Select month" disabled={!currentYear}>
130
- <span data-label="month">{currentMonth ? String(String(currentMonth).padStart(2, "0")) : "*"}</span>
131
- </button>
132
- <div class="archive-breadcrumb__menu" role="listbox" tabindex="-1" data-menu="month">
133
- <button
134
- type="button"
135
- class="archive-breadcrumb__option"
136
- role="option"
137
- data-href={currentYear ? url_for(`/${archiveDir}/${currentYear}/`) : archiveBasePath}
138
- aria-selected={!currentMonth ? "true" : "false"}
139
- >
140
- *
141
- </button>
142
- {monthsForCurrentYear.map((m) => (
143
- <button
144
- type="button"
145
- class="archive-breadcrumb__option"
146
- role="option"
147
- data-href={url_for(`/${archiveDir}/${currentYear}/${String(m).padStart(2, "0")}/`)}
148
- aria-selected={currentMonth === m ? "true" : "false"}
149
- >
150
- {String(m).padStart(2, "0")}
151
- </button>
152
- ))}
153
- </div>
154
- </span>
155
- <span class="cursor">_</span>
156
- </nav>
157
- {articleList}
133
+ <dl class="archive-stats" aria-label="Archive summary">
134
+ <div>
135
+ <dt>Posts</dt>
136
+ <dd>{totalVisiblePosts}</dd>
137
+ </div>
138
+ <div>
139
+ <dt>Years</dt>
140
+ <dd>{years.length}</dd>
141
+ </div>
142
+ <div>
143
+ <dt>Latest</dt>
144
+ <dd>{latestLabel}</dd>
145
+ </div>
146
+ </dl>
147
+ </header>
148
+
149
+ <nav class="archive-years" aria-label="Archive years">
150
+ <a href={archiveBasePath} class={!currentYear ? "is-active" : null} aria-current={!currentYear ? "page" : null}>
151
+ All
152
+ </a>
153
+ {years.map((year) => (
154
+ <a key={year} href={helper.localized_url_for(`/${archiveDir}/${year}/`, langKey)} class={currentYear === year ? "is-active" : null} aria-current={currentYear === year ? "page" : null}>
155
+ {year}
156
+ </a>
157
+ ))}
158
+ </nav>
159
+
160
+ <div class="archive-stack">{articleList}</div>
161
+ </main>
158
162
  </Fragment>
159
163
  );
160
164
  }