hexo-theme-gnix 12.0.0 → 14.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 (45) hide show
  1. package/README.md +2 -0
  2. package/include/hexo/generator/archive.js +14 -1
  3. package/include/hexo/generator/index.js +0 -5
  4. package/include/hexo/generator/page.js +18 -4
  5. package/include/hexo/generator/tag.js +1 -1
  6. package/include/hexo/helper.js +0 -4
  7. package/include/hexo/i18n.js +31 -136
  8. package/include/hexo/obsidian-callouts.js +210 -0
  9. package/include/hexo/renderer.js +4 -14
  10. package/include/hexo/shiki.js +191 -0
  11. package/include/hexo/sitemap.js +184 -0
  12. package/include/util/i18n.js +92 -106
  13. package/languages/en.yml +4 -10
  14. package/languages/zh-CN.yml +4 -10
  15. package/layout/archive.jsx +155 -78
  16. package/layout/common/article.jsx +94 -108
  17. package/layout/common/article_cover.jsx +3 -3
  18. package/layout/common/article_info.jsx +11 -48
  19. package/layout/common/article_media.jsx +9 -2
  20. package/layout/common/footer.jsx +17 -106
  21. package/layout/common/head.jsx +3 -15
  22. package/layout/common/navbar.jsx +24 -87
  23. package/layout/common/scripts.jsx +1 -1
  24. package/layout/layout.jsx +37 -19
  25. package/layout/plugin/goatcounter.jsx +25 -0
  26. package/layout/tag.jsx +3 -70
  27. package/layout/tags.jsx +26 -23
  28. package/package.json +7 -13
  29. package/scripts/index.js +1 -0
  30. package/source/css/archive.css +287 -168
  31. package/source/css/callout_blocks.css +41 -21
  32. package/source/css/default.css +154 -132
  33. package/source/css/optional/mermaid.css +12 -6
  34. package/source/css/responsive.css +1 -45
  35. package/source/css/shiki/shiki.css +5 -4
  36. package/source/css/tags.css +53 -59
  37. package/source/js/components/archive-popup.js +313 -0
  38. package/source/js/components/friends-list.js +270 -0
  39. package/source/js/components/x-info-card.js +297 -0
  40. package/source/js/main.js +38 -34
  41. package/source/js/mdit/mermaid.js +10 -0
  42. package/include/hexo/generator/home.js +0 -64
  43. package/layout/index.jsx +0 -19
  44. package/layout/misc/paginator.jsx +0 -69
  45. package/source/js/host/iconify-icon/3.0.2/iconify-icon.min.js +0 -12
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"
@@ -1,5 +1,4 @@
1
1
  const { Component, Fragment, isValidDate, parseISO, dateFormatters } = require("../include/util/common");
2
- const { filterByLanguage } = require("../include/util/i18n");
3
2
  const ArticleMedia = require("./common/article_media");
4
3
 
5
4
  function collectPosts(collection) {
@@ -12,6 +11,14 @@ function collectPosts(collection) {
12
11
  return posts;
13
12
  }
14
13
 
14
+ function estimateReadMinutes(content) {
15
+ if (typeof content !== "string" || !content) return 0;
16
+ const stripped = content.replace(/<\/?[a-z][^>]*>/gi, "").trim();
17
+ if (!stripped) return 0;
18
+ const tokens = stripped.match(/[ÿ-￿]|[a-zA-Z]+/g);
19
+ return tokens ? Math.max(1, Math.ceil(tokens.length / 200)) : 0;
20
+ }
21
+
15
22
  function getSeason(month) {
16
23
  if (month >= 2 && month <= 4) return "Spring";
17
24
  if (month >= 5 && month <= 7) return "Summer";
@@ -44,6 +51,47 @@ function groupPostsBySeason(posts) {
44
51
  }, []);
45
52
  }
46
53
 
54
+ function groupSeasonGroupsByYear(seasonGroups) {
55
+ const years = [];
56
+ for (const group of seasonGroups) {
57
+ const last = years[years.length - 1];
58
+ if (last && last.year === group.year) {
59
+ last.groups.push(group);
60
+ last.total += group.posts.length;
61
+ } else {
62
+ years.push({ year: group.year, total: group.posts.length, groups: [group] });
63
+ }
64
+ }
65
+ return years;
66
+ }
67
+
68
+ function toRoman(num) {
69
+ const map = [
70
+ [1000, "M"],
71
+ [900, "CM"],
72
+ [500, "D"],
73
+ [400, "CD"],
74
+ [100, "C"],
75
+ [90, "XC"],
76
+ [50, "L"],
77
+ [40, "XL"],
78
+ [10, "X"],
79
+ [9, "IX"],
80
+ [5, "V"],
81
+ [4, "IV"],
82
+ [1, "I"],
83
+ ];
84
+ let n = num;
85
+ let out = "";
86
+ for (const [value, sym] of map) {
87
+ while (n >= value) {
88
+ out += sym;
89
+ n -= value;
90
+ }
91
+ }
92
+ return out;
93
+ }
94
+
47
95
  function collectArchiveYears(posts) {
48
96
  return Array.from(new Set(posts.map((post) => post.date.year()))).sort((a, b) => b - a);
49
97
  }
@@ -58,104 +106,133 @@ function getPostDateParts(postDate, dateXml, date) {
58
106
  };
59
107
  }
60
108
 
109
+ function renderSeasonGroup({ posts, year, season, month, sectionTitle, url_for, date_xml, date }) {
110
+ const title = sectionTitle || getArchiveRangeLabel(year, month, season);
111
+ const kicker = year ? String(year) : "Archive";
112
+ const marker = season ? season.toLowerCase() : "all";
113
+ const sectionId = `archive-${kicker}-${marker}-${month || "all"}`;
114
+
115
+ return (
116
+ <section class={["archive-group", marker].filter(Boolean).join(" ")} aria-labelledby={sectionId}>
117
+ <header class="archive-group__header">
118
+ <h2 id={sectionId} class="archive-group__title">
119
+ {title}
120
+ </h2>
121
+ <span class="archive-group__count">{String(posts.length).padStart(2, "0")}</span>
122
+ </header>
123
+ <div class="timeline">
124
+ {posts.map((post) => {
125
+ const postDate = getPostDateParts(post.date, date_xml, date);
126
+ const readMinutes = estimateReadMinutes(post._content);
127
+ return (
128
+ <ArticleMedia
129
+ key={post.path}
130
+ url={url_for(post.link || post.path)}
131
+ title={post.title}
132
+ date={postDate.label}
133
+ dateXml={postDate.xml}
134
+ excerpt={post.excerpt || null}
135
+ readTime={readMinutes ? `${readMinutes} min` : null}
136
+ />
137
+ );
138
+ })}
139
+ </div>
140
+ </section>
141
+ );
142
+ }
143
+
61
144
  module.exports = class extends Component {
62
145
  render() {
63
- const { page, helper, site, config } = this.props;
146
+ const { page, helper } = this.props;
64
147
  const { url_for, date_xml, date } = helper;
65
- const langKey = helper.language_key(page);
66
-
67
- function renderArticleList(posts, year, month = null, sectionTitle = null, season = null) {
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"}`;
73
-
74
- return (
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>
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
- })}
89
- </div>
90
- </section>
91
- );
92
- }
93
148
 
94
149
  const visiblePosts = collectPosts(page.posts);
95
150
  const totalVisiblePosts = visiblePosts.length;
96
- const latestPost = visiblePosts[0];
97
151
 
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);
152
+ const years = collectArchiveYears(visiblePosts);
153
+
154
+ const currentYear = page.year ? Number(page.year) : null;
155
+ const currentMonth = page.month ? Number(page.month) : null;
156
+ const isTagPage = Boolean(page.tag);
157
+ const entriesLabel = `${totalVisiblePosts} ${totalVisiblePosts === 1 ? "entry" : "entries"}`;
158
+
101
159
  let articleList;
102
160
  if (!page.year) {
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>;
106
- });
161
+ const seasonGroups = groupPostsBySeason(visiblePosts);
162
+ const yearBlocks = groupSeasonGroupsByYear(seasonGroups);
163
+ articleList = yearBlocks.map((block) => (
164
+ <Fragment key={block.year}>
165
+ <div class="archive-era" id={`archive-year-${block.year}`}>
166
+ <span class="archive-era__roman">{toRoman(block.year)}</span>
167
+ <span class="archive-era__year" aria-hidden="true">
168
+ {" "}
169
+ {block.year}{" "}
170
+ </span>
171
+ </div>
172
+ {block.groups.map((group) => (
173
+ <Fragment key={`${group.year}-${group.season}`}>
174
+ {renderSeasonGroup({
175
+ posts: group.posts,
176
+ year: group.year,
177
+ season: group.season,
178
+ sectionTitle: group.season,
179
+ url_for,
180
+ date_xml,
181
+ date,
182
+ })}
183
+ </Fragment>
184
+ ))}
185
+ </Fragment>
186
+ ));
107
187
  } else {
108
188
  const season = page.month ? getSeason(page.month - 1) : null;
109
- articleList = renderArticleList(visiblePosts, page.year, page.month, null, season);
189
+ articleList = renderSeasonGroup({
190
+ posts: visiblePosts,
191
+ year: page.year,
192
+ season,
193
+ month: page.month,
194
+ url_for,
195
+ date_xml,
196
+ date,
197
+ });
110
198
  }
111
199
 
112
- const archiveDir = config?.archive_dir || "archives";
113
- const archiveBasePath = helper.localized_url_for(`/${archiveDir}/`, langKey);
114
- const currentYear = page.year ? Number(page.year) : null;
115
- const currentMonth = page.month ? Number(page.month) : null;
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";
200
+ const heroTitle = isTagPage ? page.tag : currentYear ? getArchiveRangeLabel(currentYear, currentMonth) : "Index";
201
+ const heroKind = isTagPage ? "Tag" : currentYear ? "Archive" : "Volume";
120
202
 
121
203
  return (
122
204
  <Fragment>
123
205
  <link rel="stylesheet" href={url_for("/css/archive.css")} data-page-head />
124
206
  <main class="archive-page">
125
207
  <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>
132
- </div>
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>
208
+ <p class="archive-hero__eyebrow">
209
+ <span>{heroKind}</span>
210
+ <span class="archive-hero__sep" aria-hidden="true">
211
+ ·
212
+ </span>
213
+ <span class="archive-hero__count">{entriesLabel}</span>
214
+ </p>
215
+ <h1 class="archive-hero__title">{heroTitle}</h1>
216
+ {years.length > 0 && (
217
+ <span class="archive-hero__roman" aria-hidden="true">
218
+ {years.length === 1 ? toRoman(years[0]) : `${toRoman(years[years.length - 1])}–${toRoman(years[0])}`}
219
+ </span>
220
+ )}
147
221
  </header>
148
222
 
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>
223
+ {!page.year && years.length > 1 && (
224
+ <aside class="archive-rail" aria-label="Jump to year">
225
+ <ol class="archive-rail__list">
226
+ {years.map((year) => (
227
+ <li key={year} class="archive-rail__item">
228
+ <a href={`#archive-year-${year}`} class="archive-rail__link">
229
+ <span class="archive-rail__year">{year}</span>
230
+ </a>
231
+ </li>
232
+ ))}
233
+ </ol>
234
+ </aside>
235
+ )}
159
236
 
160
237
  <div class="archive-stack">{articleList}</div>
161
238
  </main>
@@ -17,15 +17,14 @@ function getWordCount(content) {
17
17
 
18
18
  module.exports = class extends Component {
19
19
  render() {
20
- // index: true if in article list, false if in article page
21
- const { config, helper, page, index } = this.props;
20
+ const { config, helper, page } = this.props;
22
21
 
23
22
  const { url_for } = helper;
24
23
 
25
24
  const cover = page.cover ? url_for(page.cover) : null;
26
25
  const wordCount = getWordCount(page._content);
27
26
  const readTime = Math.ceil(wordCount / 200); // 假设每分钟阅读200字
28
- const hasComment = !index && config.comment && typeof config.comment.type === "string";
27
+ const hasComment = config.comment && typeof config.comment.type === "string";
29
28
  const translatedCommentsLabel = helper.__("article.comments");
30
29
  const commentsLabel = translatedCommentsLabel === "article.comments" ? "Comments" : translatedCommentsLabel;
31
30
 
@@ -34,7 +33,7 @@ module.exports = class extends Component {
34
33
  {/* Main content */}
35
34
  <div class="card">
36
35
  {/* Cover image */}
37
- {cover ? <ArticleCover page={page} cover={cover} index={index} helper={helper} /> : null}
36
+ {cover ? <ArticleCover page={page} cover={cover} helper={helper} /> : null}
38
37
  <article class={`card-content article${"direction" in page ? ` ${page.direction}` : ""}`}>
39
38
  {/* Metadata - Medium style */}
40
39
  {page.layout !== "page" ? (
@@ -45,122 +44,110 @@ module.exports = class extends Component {
45
44
  {dateFormatters.shortDay.format(page.date)}
46
45
  </time>
47
46
  )}
48
- {page.date && (wordCount > 0 || !index) && <span class="meta-separator">·</span>}
47
+ {page.date && <span class="meta-separator">·</span>}
49
48
  {wordCount > 0 && <span class="article-reading-time">{readTime} min</span>}
50
- {!index && (
51
- <Fragment>
52
- <span class="meta-separator">·</span>
53
- <span
54
- class="article-visit-count"
55
- data-flag-title={page.title}
56
- dangerouslySetInnerHTML={{
57
- __html: '<span id="busuanzi_page_pv"></span> PV',
58
- }}
59
- ></span>
60
- </Fragment>
61
- )}
49
+ <span class="meta-separator">·</span>
50
+ <span
51
+ class="article-visit-count"
52
+ data-flag-title={page.title}
53
+ dangerouslySetInnerHTML={{
54
+ __html: '<span id="busuanzi_page_pv"></span> PV',
55
+ }}
56
+ ></span>
62
57
  </div>
63
58
  </div>
64
59
  ) : null}
65
60
 
66
61
  {/* Title */}
67
- {page.title !== "" && index ? (
68
- <h2 class="article-title">
69
- <a href={url_for(page.link || page.path)}>{page.title}</a>
70
- </h2>
71
- ) : null}
72
- {page.title !== "" && !index ? <h1 class="article-title">{page.title}</h1> : null}
73
-
74
- {!index && page.excerpt && <div class="article-excerpt" dangerouslySetInnerHTML={{ __html: page.excerpt }}></div>}
62
+ {page.title !== "" ? <h1 class="article-title">{page.title}</h1> : null}
75
63
 
76
- {(index || !page.excerpt) && (
77
- <div
78
- class={index && page.excerpt ? "article-excerpt" : "content"}
79
- dangerouslySetInnerHTML={{
80
- __html: index && page.excerpt ? page.excerpt : page.content,
81
- }}
82
- ></div>
83
- )}
64
+ {page.excerpt && <div class="article-excerpt" dangerouslySetInnerHTML={{ __html: page.excerpt }}></div>}
84
65
 
85
- {!index && (
86
- <div class="article-footer article-meta-bar">
87
- <div class="article-tags">
88
- {page.tags?.length
89
- ? page.tags.map((tag, i) => (
90
- <Fragment>
91
- {i > 0 && <span class="meta-separator">·</span>}
92
- <a class="article-tag" rel="tag" href={helper.localized_tag_url(tag, helper.language_key(page))}>
93
- {tag.name}
94
- </a>
95
- </Fragment>
96
- ))
97
- : null}
98
- </div>
99
- <div class="article-title-actions">
100
- {hasComment && (
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}>
103
- <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>
111
- </svg>
112
- </button>
113
- )}
114
- <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")}>
116
- <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>
124
- </svg>
125
- </button>
126
- <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")}>
128
- <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>
66
+ <div class="article-meta-bar">
67
+ <div class="article-tags">
68
+ {page.tags?.length
69
+ ? page.tags.map((tag, i) => (
70
+ <Fragment>
71
+ {i > 0 && <span class="meta-separator">·</span>}
72
+ <a class="article-tag" rel="tag" href={helper.localized_tag_url(tag, helper.language_key(page))}>
73
+ {tag.name}
74
+ </a>
75
+ </Fragment>
76
+ ))
77
+ : null}
78
+ </div>
79
+ <div class="article-title-actions">
80
+ {hasComment && (
81
+ <button type="button" class="article-action-btn" popovertarget="article-comment-popover" aria-label={commentsLabel} title={commentsLabel}>
82
+ <svg
83
+ xmlns="http://www.w3.org/2000/svg"
84
+ width="24"
85
+ height="24"
86
+ viewBox="0 0 24 24"
87
+ fill="none"
88
+ stroke="currentColor"
89
+ stroke-width="2"
90
+ stroke-linecap="round"
91
+ stroke-linejoin="round"
92
+ role="img"
93
+ aria-label={commentsLabel}
94
+ >
95
+ <title>{commentsLabel}</title>
96
+ <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" />
97
+ <path d="M8 12h.01" />
98
+ <path d="M12 12h.01" />
99
+ <path d="M16 12h.01" />
136
100
  </svg>
137
101
  </button>
138
- </div>
139
- </div>
140
- )}
141
- {index && page.tags?.length && (
142
- <div class="article-footer">
143
- <div class="article-tags">
144
- {page.tags.map((tag, i) => (
145
- <Fragment>
146
- {i > 0 && <span class="meta-separator">·</span>}
147
- <a class="article-tag" rel="tag" href={helper.localized_tag_url(tag, helper.language_key(page))}>
148
- {tag.name}
149
- </a>
150
- </Fragment>
151
- ))}
152
- </div>
153
- <a class="article-read-more" href={url_for(page.link || page.path)}>
154
- Read More
155
- </a>
102
+ )}
103
+ <button type="button" class="article-action-btn" popovertarget="article-font-settings" aria-label={helper.__("article.font_settings")} title={helper.__("article.font_settings")}>
104
+ <svg
105
+ xmlns="http://www.w3.org/2000/svg"
106
+ width="24"
107
+ height="24"
108
+ viewBox="0 0 24 24"
109
+ fill="none"
110
+ stroke="currentColor"
111
+ stroke-width="2"
112
+ stroke-linecap="round"
113
+ stroke-linejoin="round"
114
+ role="img"
115
+ aria-label={helper.__("article.font_settings")}
116
+ >
117
+ <title>{helper.__("article.font_settings")}</title>
118
+ <path d="M12 4v16" />
119
+ <path d="M4 7V5a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v2" />
120
+ <path d="M9 20h6" />
121
+ </svg>
122
+ </button>
123
+ <button type="button" class="article-action-btn" popovertarget="article-info-popover" aria-label={helper.__("article.article_info")} title={helper.__("article.article_info")}>
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.article_info")}
136
+ >
137
+ <title>{helper.__("article.article_info")}</title>
138
+ <circle cx="12" cy="12" r="10" />
139
+ <path d="M12 16v-4" />
140
+ <path d="M12 8h.01" />
141
+ </svg>
142
+ </button>
156
143
  </div>
157
- )}
144
+ </div>
145
+
146
+ <div class="content" dangerouslySetInnerHTML={{ __html: page.content }}></div>
158
147
 
159
- {!index && page.excerpt && <div class="content" dangerouslySetInnerHTML={{ __html: page.content }}></div>}
160
148
  </article>
161
149
 
162
- {!index && (
163
- <div id="article-font-settings" popover="auto" class="article-popover article-font-popover">
150
+ <div id="article-font-settings" popover="auto" class="article-popover article-font-popover">
164
151
  <div class="article-popover-header">
165
152
  <h3>Display Settings</h3>
166
153
  <button type="button" class="article-popover-close" popovertarget="article-font-settings" popovertargetaction="hide" aria-label="Close">
@@ -341,7 +328,6 @@ module.exports = class extends Component {
341
328
  </aside>
342
329
  </div>
343
330
  </div>
344
- )}
345
331
 
346
332
  {hasComment && (
347
333
  <div id="article-comment-popover" popover="auto" class="article-popover article-comment-popover">
@@ -373,7 +359,7 @@ module.exports = class extends Component {
373
359
  </div>
374
360
  )}
375
361
 
376
- {!index && <ArticleInfo page={page} config={config} helper={helper} />}
362
+ <ArticleInfo page={page} config={config} helper={helper} />
377
363
  </div>
378
364
  </Fragment>
379
365
  );
@@ -2,7 +2,7 @@ const { Component } = require("inferno");
2
2
 
3
3
  module.exports = class extends Component {
4
4
  render() {
5
- const { page, cover, helper, index } = this.props;
5
+ const { page, cover, helper } = this.props;
6
6
  const { url_for } = helper;
7
7
 
8
8
  const imageSrcset = `${cover}?w=800 800w, ${cover}?w=1500 1500w, ${cover}?w=2000 2000w, ${cover} 6144w`;
@@ -19,8 +19,8 @@ module.exports = class extends Component {
19
19
  sizes="(max-width: 768px) 100vw, (max-width: 1024px) 100vw, 960px"
20
20
  referrerpolicy="no-referrer"
21
21
  decoding="async"
22
- loading={index ? "lazy" : "eager"}
23
- fetchpriority={index ? undefined : "high"}
22
+ loading="eager"
23
+ fetchpriority="high"
24
24
  />
25
25
  </a>
26
26
  );