hexo-theme-gnix 9.0.0 → 11.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -6
- 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/hexo/renderer.js +131 -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 +18 -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
package/layout/archive.jsx
CHANGED
|
@@ -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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
<
|
|
32
|
-
<
|
|
33
|
-
<
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
</
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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(
|
|
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 =
|
|
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
|
|
79
|
-
const
|
|
80
|
-
|
|
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
|
-
<
|
|
98
|
-
<
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
<
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const { Component, Fragment, dateFormatters } = require("../../include/util/common");
|
|
2
2
|
const Comment = require("./comment");
|
|
3
|
-
const ArticleLicensing = require("../misc/article_licensing");
|
|
4
3
|
const ArticleCover = require("./article_cover");
|
|
4
|
+
const ArticleInfo = require("./article_info");
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Get the word count of text.
|
|
@@ -20,12 +20,14 @@ module.exports = class extends Component {
|
|
|
20
20
|
// index: true if in article list, false if in article page
|
|
21
21
|
const { config, helper, page, index } = this.props;
|
|
22
22
|
|
|
23
|
-
const { article } = config;
|
|
24
23
|
const { url_for } = helper;
|
|
25
24
|
|
|
26
25
|
const cover = page.cover ? url_for(page.cover) : null;
|
|
27
26
|
const wordCount = getWordCount(page._content);
|
|
28
27
|
const readTime = Math.ceil(wordCount / 200); // 假设每分钟阅读200字
|
|
28
|
+
const hasComment = !index && config.comment && typeof config.comment.type === "string";
|
|
29
|
+
const translatedCommentsLabel = helper.__("article.comments");
|
|
30
|
+
const commentsLabel = translatedCommentsLabel === "article.comments" ? "Comments" : translatedCommentsLabel;
|
|
29
31
|
|
|
30
32
|
return (
|
|
31
33
|
<Fragment>
|
|
@@ -69,45 +71,310 @@ module.exports = class extends Component {
|
|
|
69
71
|
) : null}
|
|
70
72
|
{page.title !== "" && !index ? <h1 class="article-title">{page.title}</h1> : null}
|
|
71
73
|
|
|
72
|
-
{!index && page.excerpt && <div class="
|
|
74
|
+
{!index && page.excerpt && <div class="article-excerpt" dangerouslySetInnerHTML={{ __html: page.excerpt }}></div>}
|
|
73
75
|
|
|
74
76
|
{(index || !page.excerpt) && (
|
|
75
77
|
<div
|
|
76
|
-
class={index && page.excerpt ? "
|
|
78
|
+
class={index && page.excerpt ? "article-excerpt" : "content"}
|
|
77
79
|
dangerouslySetInnerHTML={{
|
|
78
80
|
__html: index && page.excerpt ? page.excerpt : page.content,
|
|
79
81
|
}}
|
|
80
82
|
></div>
|
|
81
83
|
)}
|
|
82
84
|
|
|
83
|
-
{
|
|
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>
|
|
136
|
+
</svg>
|
|
137
|
+
</button>
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
)}
|
|
141
|
+
{index && page.tags?.length && (
|
|
84
142
|
<div class="article-footer">
|
|
85
143
|
<div class="article-tags">
|
|
86
144
|
{page.tags.map((tag, i) => (
|
|
87
145
|
<Fragment>
|
|
88
146
|
{i > 0 && <span class="meta-separator">·</span>}
|
|
89
|
-
<a class="article-tag" rel="tag" href={
|
|
147
|
+
<a class="article-tag" rel="tag" href={helper.localized_tag_url(tag, helper.language_key(page))}>
|
|
90
148
|
{tag.name}
|
|
91
149
|
</a>
|
|
92
150
|
</Fragment>
|
|
93
151
|
))}
|
|
94
152
|
</div>
|
|
95
|
-
{
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
</a>
|
|
99
|
-
)}
|
|
153
|
+
<a class="article-read-more" href={url_for(page.link || page.path)}>
|
|
154
|
+
Read More →
|
|
155
|
+
</a>
|
|
100
156
|
</div>
|
|
101
157
|
)}
|
|
102
158
|
|
|
103
159
|
{!index && page.excerpt && <div class="content" dangerouslySetInnerHTML={{ __html: page.content }}></div>}
|
|
104
|
-
|
|
105
|
-
{/* Licensing block */}
|
|
106
|
-
{!index && article && article.licenses && Object.keys(article.licenses) ? <ArticleLicensing.Cacheable page={page} config={config} helper={helper} /> : null}
|
|
107
160
|
</article>
|
|
161
|
+
|
|
162
|
+
{!index && (
|
|
163
|
+
<div id="article-font-settings" popover="auto" class="article-popover article-font-popover">
|
|
164
|
+
<div class="article-popover-header">
|
|
165
|
+
<h3>Display Settings</h3>
|
|
166
|
+
<button type="button" class="article-popover-close" popovertarget="article-font-settings" popovertargetaction="hide" aria-label="Close">
|
|
167
|
+
<svg
|
|
168
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
169
|
+
width="18"
|
|
170
|
+
height="18"
|
|
171
|
+
viewBox="0 0 24 24"
|
|
172
|
+
fill="none"
|
|
173
|
+
stroke="currentColor"
|
|
174
|
+
stroke-width="2"
|
|
175
|
+
stroke-linecap="round"
|
|
176
|
+
stroke-linejoin="round"
|
|
177
|
+
role="img"
|
|
178
|
+
aria-label="Close"
|
|
179
|
+
>
|
|
180
|
+
<title>Close</title>
|
|
181
|
+
<path d="M18 6 6 18" />
|
|
182
|
+
<path d="m6 6 12 12" />
|
|
183
|
+
</svg>
|
|
184
|
+
</button>
|
|
185
|
+
</div>
|
|
186
|
+
<div class="article-popover-body">
|
|
187
|
+
<div class="font-settings-column">
|
|
188
|
+
<div class="font-setting-group font-size-group">
|
|
189
|
+
<div class="font-size-selector">
|
|
190
|
+
<button type="button" class="font-size-btn" data-size="small" aria-label="Small">
|
|
191
|
+
<span class="font-size-preview">A</span>
|
|
192
|
+
</button>
|
|
193
|
+
<button type="button" class="font-size-btn" data-size="medium-small" aria-label="Medium Small">
|
|
194
|
+
<span class="font-size-preview">A</span>
|
|
195
|
+
</button>
|
|
196
|
+
<button type="button" class="font-size-btn is-active" data-size="medium" aria-label="Medium">
|
|
197
|
+
<span class="font-size-preview">A</span>
|
|
198
|
+
</button>
|
|
199
|
+
<button type="button" class="font-size-btn" data-size="medium-large" aria-label="Medium Large">
|
|
200
|
+
<span class="font-size-preview">A</span>
|
|
201
|
+
</button>
|
|
202
|
+
<button type="button" class="font-size-btn" data-size="large" aria-label="Large">
|
|
203
|
+
<span class="font-size-preview">A</span>
|
|
204
|
+
</button>
|
|
205
|
+
</div>
|
|
206
|
+
</div>
|
|
207
|
+
<div class="font-setting-group font-line-height-group">
|
|
208
|
+
<div class="font-line-height-control">
|
|
209
|
+
<span class="font-line-height-label">Compact</span>
|
|
210
|
+
<input id="article-line-height-slider" class="font-line-height-slider" type="range" min="1.45" max="1.9" step="0.05" value="1.7" aria-label="Line Height" />
|
|
211
|
+
<span class="font-line-height-label">Relaxed</span>
|
|
212
|
+
</div>
|
|
213
|
+
<div class="font-line-height-meta">
|
|
214
|
+
<span>Normal</span>
|
|
215
|
+
<output class="font-line-height-value" for="article-line-height-slider">
|
|
216
|
+
1.70
|
|
217
|
+
</output>
|
|
218
|
+
</div>
|
|
219
|
+
</div>
|
|
220
|
+
<div class="font-setting-group font-type-group">
|
|
221
|
+
<div class="font-type-selector">
|
|
222
|
+
<button type="button" class="font-type-btn is-active" data-font="serif">
|
|
223
|
+
<span class="font-type-preview">Aa</span>
|
|
224
|
+
<span class="font-type-name">Serif</span>
|
|
225
|
+
</button>
|
|
226
|
+
<button type="button" class="font-type-btn" data-font="sans-serif">
|
|
227
|
+
<span class="font-type-preview">Aa</span>
|
|
228
|
+
<span class="font-type-name">Sans Serif</span>
|
|
229
|
+
</button>
|
|
230
|
+
<button type="button" class="font-type-btn" data-font="mono">
|
|
231
|
+
<span class="font-type-preview">Aa</span>
|
|
232
|
+
<span class="font-type-name">Monospace</span>
|
|
233
|
+
</button>
|
|
234
|
+
<button type="button" class="font-type-btn" data-font="handwriting">
|
|
235
|
+
<span class="font-type-preview">Aa</span>
|
|
236
|
+
<span class="font-type-name">Handwriting</span>
|
|
237
|
+
</button>
|
|
238
|
+
</div>
|
|
239
|
+
</div>
|
|
240
|
+
<div class="font-setting-group font-weight-group">
|
|
241
|
+
<div class="font-weight-selector">
|
|
242
|
+
<button type="button" class="font-weight-btn" data-weight="light" aria-label="Light">
|
|
243
|
+
<span class="font-option-name">Light</span>
|
|
244
|
+
</button>
|
|
245
|
+
<button type="button" class="font-weight-btn is-active" data-weight="regular" aria-label="Regular">
|
|
246
|
+
<span class="font-option-name">Regular</span>
|
|
247
|
+
</button>
|
|
248
|
+
<button type="button" class="font-weight-btn" data-weight="medium" aria-label="Medium">
|
|
249
|
+
<span class="font-option-name">Medium</span>
|
|
250
|
+
</button>
|
|
251
|
+
</div>
|
|
252
|
+
</div>
|
|
253
|
+
<div class="font-setting-group font-custom-group">
|
|
254
|
+
<button type="button" class="font-custom-toggle" aria-expanded="false" aria-controls="font-custom-panel" aria-label="Custom Fonts">
|
|
255
|
+
<span>Custom Fonts</span>
|
|
256
|
+
<span class="font-custom-toggle-icon" aria-hidden="true">
|
|
257
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" fill="none" aria-hidden="true" focusable="false">
|
|
258
|
+
<path d="M7 2.25v9.5" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" />
|
|
259
|
+
<path d="M2.25 7h9.5" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" />
|
|
260
|
+
</svg>
|
|
261
|
+
</span>
|
|
262
|
+
</button>
|
|
263
|
+
<div id="font-custom-panel" class="font-custom-panel" data-expanded="false" aria-hidden="true">
|
|
264
|
+
<div class="font-custom-panel-inner">
|
|
265
|
+
<form class="font-custom-form">
|
|
266
|
+
<label class="font-custom-field font-custom-import-field">
|
|
267
|
+
<span class="font-custom-label-row">
|
|
268
|
+
<span>Web Font CSS URL</span>
|
|
269
|
+
<button type="button" class="font-custom-help-btn" popovertarget="font-custom-help-popover" aria-label="Font CSS help">
|
|
270
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" role="img" aria-hidden="true">
|
|
271
|
+
<circle cx="12" cy="12" r="9" stroke="currentColor" stroke-width="1.8" />
|
|
272
|
+
<path d="M12 10.25v5.75" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" />
|
|
273
|
+
<circle cx="12" cy="7.9" r="1" fill="currentColor" />
|
|
274
|
+
</svg>
|
|
275
|
+
</button>
|
|
276
|
+
</span>
|
|
277
|
+
<textarea
|
|
278
|
+
class="font-custom-imports"
|
|
279
|
+
name="font-custom-imports"
|
|
280
|
+
rows="3"
|
|
281
|
+
placeholder="https://fonts.googleapis.com/css2?family=..."
|
|
282
|
+
aria-label="Web Font CSS URL"
|
|
283
|
+
></textarea>
|
|
284
|
+
</label>
|
|
285
|
+
<div class="font-custom-family-grid">
|
|
286
|
+
<label class="font-custom-field">
|
|
287
|
+
<span>Serif</span>
|
|
288
|
+
<input class="font-custom-family-input" name="font-custom-family-serif" type="text" data-font-family="serif" placeholder={'"Noto Serif SC", serif'} autocomplete="off" />
|
|
289
|
+
</label>
|
|
290
|
+
<label class="font-custom-field">
|
|
291
|
+
<span>Sans Serif</span>
|
|
292
|
+
<input
|
|
293
|
+
class="font-custom-family-input"
|
|
294
|
+
name="font-custom-family-sans-serif"
|
|
295
|
+
type="text"
|
|
296
|
+
data-font-family="sans-serif"
|
|
297
|
+
placeholder={'"Inter", sans-serif'}
|
|
298
|
+
autocomplete="off"
|
|
299
|
+
/>
|
|
300
|
+
</label>
|
|
301
|
+
<label class="font-custom-field">
|
|
302
|
+
<span>Monospace</span>
|
|
303
|
+
<input class="font-custom-family-input" name="font-custom-family-mono" type="text" data-font-family="mono" placeholder={'"Fira Code", monospace'} autocomplete="off" />
|
|
304
|
+
</label>
|
|
305
|
+
<label class="font-custom-field">
|
|
306
|
+
<span>Handwriting</span>
|
|
307
|
+
<input
|
|
308
|
+
class="font-custom-family-input"
|
|
309
|
+
name="font-custom-family-handwriting"
|
|
310
|
+
type="text"
|
|
311
|
+
data-font-family="handwriting"
|
|
312
|
+
placeholder={'"LXGW WenKai", cursive'}
|
|
313
|
+
autocomplete="off"
|
|
314
|
+
/>
|
|
315
|
+
</label>
|
|
316
|
+
</div>
|
|
317
|
+
<div class="font-custom-actions">
|
|
318
|
+
<button type="submit" class="font-custom-apply">
|
|
319
|
+
Apply
|
|
320
|
+
</button>
|
|
321
|
+
<button type="button" class="font-custom-reset">
|
|
322
|
+
Reset
|
|
323
|
+
</button>
|
|
324
|
+
</div>
|
|
325
|
+
</form>
|
|
326
|
+
</div>
|
|
327
|
+
</div>
|
|
328
|
+
</div>
|
|
329
|
+
<div id="font-custom-help-popover" popover="manual" class="font-custom-help-popover" role="tooltip">
|
|
330
|
+
Paste one web font CSS URL per line. Each URL can load one or more font families.
|
|
331
|
+
</div>
|
|
332
|
+
</div>
|
|
333
|
+
<aside class="font-preview-column">
|
|
334
|
+
<div class="font-setting-group font-preview-group">
|
|
335
|
+
<span class="font-setting-label">Preview</span>
|
|
336
|
+
<div class="font-preview-box">
|
|
337
|
+
<p class="font-preview-title">{page.title}</p>
|
|
338
|
+
{page.excerpt && <p class="font-preview-excerpt" dangerouslySetInnerHTML={{ __html: `${page.excerpt.substring(0, 80)}...` }}></p>}
|
|
339
|
+
</div>
|
|
340
|
+
</div>
|
|
341
|
+
</aside>
|
|
342
|
+
</div>
|
|
343
|
+
</div>
|
|
344
|
+
)}
|
|
345
|
+
|
|
346
|
+
{hasComment && (
|
|
347
|
+
<div id="article-comment-popover" popover="auto" class="article-popover article-comment-popover">
|
|
348
|
+
<div class="article-popover-header">
|
|
349
|
+
<h3>{commentsLabel}</h3>
|
|
350
|
+
<button type="button" class="article-popover-close" popovertarget="article-comment-popover" popovertargetaction="hide" aria-label={helper.__("article.close")}>
|
|
351
|
+
<svg
|
|
352
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
353
|
+
width="18"
|
|
354
|
+
height="18"
|
|
355
|
+
viewBox="0 0 24 24"
|
|
356
|
+
fill="none"
|
|
357
|
+
stroke="currentColor"
|
|
358
|
+
stroke-width="2"
|
|
359
|
+
stroke-linecap="round"
|
|
360
|
+
stroke-linejoin="round"
|
|
361
|
+
role="img"
|
|
362
|
+
aria-label="Close"
|
|
363
|
+
>
|
|
364
|
+
<title>Close</title>
|
|
365
|
+
<path d="M18 6 6 18" />
|
|
366
|
+
<path d="m6 6 12 12" />
|
|
367
|
+
</svg>
|
|
368
|
+
</button>
|
|
369
|
+
</div>
|
|
370
|
+
<div class="article-popover-body article-comment-popover-body">
|
|
371
|
+
<Comment config={config} page={page} helper={helper} embedded />
|
|
372
|
+
</div>
|
|
373
|
+
</div>
|
|
374
|
+
)}
|
|
375
|
+
|
|
376
|
+
{!index && <ArticleInfo page={page} config={config} helper={helper} />}
|
|
108
377
|
</div>
|
|
109
|
-
{/* Comment */}
|
|
110
|
-
{!index ? <Comment config={config} page={page} helper={helper} /> : null}
|
|
111
378
|
</Fragment>
|
|
112
379
|
);
|
|
113
380
|
}
|