hexo-theme-stellar 1.40.0 → 1.42.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/.claude/skills/stellar-theme-dev/SKILL.md +1 -2
- package/CHANGELOG.md +90 -0
- package/CONTRIBUTING.md +2 -1
- package/README.md +50 -21
- package/README_EN.md +113 -0
- package/_config.yml +40 -12
- package/_data/icons.yml +248 -37
- package/languages/en.yml +6 -0
- package/languages/zh-CN.yml +6 -0
- package/languages/zh-TW.yml +6 -0
- package/layout/_partial/comments/artalk/script.ejs +25 -8
- package/layout/_partial/comments/beaudar/script.ejs +1 -0
- package/layout/_partial/comments/twikoo/script.ejs +1 -0
- package/layout/_partial/comments/utterances/script.ejs +1 -0
- package/layout/_partial/comments/waline/script.ejs +1 -0
- package/layout/_partial/cover/index.ejs +0 -3
- package/layout/_partial/head.ejs +43 -7
- package/layout/_partial/main/article/article_tags.ejs +16 -0
- package/layout/_partial/main/navbar/article_banner.ejs +33 -20
- package/layout/_partial/main/notebook/note_card.ejs +1 -1
- package/layout/_partial/main/notebook/paginator.ejs +2 -2
- package/layout/_partial/main/pin_slider.ejs +61 -30
- package/layout/_partial/main/post_list/latest_post_card.ejs +25 -0
- package/layout/_partial/main/post_list/paginator.ejs +2 -2
- package/layout/_partial/main/post_list/post_card.ejs +16 -64
- package/layout/_partial/main/post_list/topic_card.ejs +26 -5
- package/layout/_partial/menubtn.ejs +2 -2
- package/layout/_partial/scripts/bootstrap.ejs +35 -0
- package/layout/_partial/scripts/defines.ejs +27 -2
- package/layout/_partial/scripts.ejs +39 -7
- package/layout/_partial/sidebar/logo.ejs +1 -1
- package/layout/_partial/sidebar/menu.ejs +1 -1
- package/layout/_partial/sidebar/search.ejs +1 -1
- package/layout/_partial/widgets/author.ejs +1 -1
- package/layout/_partial/widgets/components/link.ejs +2 -2
- package/layout/_partial/widgets/ghuser.ejs +1 -1
- package/layout/_partial/widgets/related.ejs +1 -1
- package/layout/_partial/widgets/toc.ejs +3 -3
- package/layout/_partial/widgets/tree.ejs +1 -1
- package/layout/_plugins/adaptive_text.ejs +11 -0
- package/layout/_plugins/copycode.ejs +2 -2
- package/layout/_plugins/fancybox.ejs +4 -2
- package/layout/_plugins/mermaid.ejs +3 -2
- package/layout/_plugins/scrollreveal.ejs +22 -16
- package/layout/_plugins/search/local_search.ejs +4 -3
- package/layout/_plugins/swiper.ejs +2 -1
- package/layout/categories.ejs +1 -1
- package/layout/index.ejs +10 -2
- package/layout/index_topic.ejs +4 -4
- package/layout/page.ejs +3 -0
- package/layout/tags.ejs +1 -0
- package/package.json +3 -3
- package/scripts/events/lib/doc_tree.js +12 -208
- package/scripts/events/lib/notebooks.js +8 -148
- package/scripts/events/lib/utils.js +10 -1
- package/scripts/filters/index.js +1 -0
- package/scripts/filters/lib/img_lazyload.js +4 -0
- package/scripts/filters/lib/img_onerror.js +4 -0
- package/scripts/filters/lib/md_table.js +46 -0
- package/scripts/generators/search.js +12 -18
- package/scripts/generators/stellar-icons.js +47 -0
- package/scripts/helpers/ai_label.js +29 -0
- package/scripts/helpers/icon.js +2 -2
- package/scripts/helpers/json_ld.js +10 -13
- package/scripts/helpers/related_posts.js +0 -7
- package/scripts/helpers/seo.js +9 -0
- package/scripts/helpers/subtitle.js +8 -0
- package/scripts/lib/ai_label.js +55 -0
- package/scripts/lib/doc_tree.js +267 -0
- package/scripts/lib/notebooks.js +181 -0
- package/scripts/lib/search_index.js +82 -0
- package/scripts/lib/seo.js +79 -0
- package/scripts/lib/subtitle.js +24 -0
- package/scripts/tags/lib/about.js +2 -2
- package/scripts/tags/lib/albums.js +1 -1
- package/scripts/tags/lib/banner.js +5 -4
- package/scripts/tags/lib/chat.js +49 -49
- package/scripts/tags/lib/copy.js +1 -1
- package/scripts/tags/lib/friends.js +1 -1
- package/scripts/tags/lib/gallery.js +1 -1
- package/scripts/tags/lib/hashtag.js +1 -1
- package/scripts/tags/lib/image.js +3 -3
- package/scripts/tags/lib/posters.js +1 -1
- package/scripts/tags/lib/sites.js +2 -2
- package/source/css/_common/base.styl +5 -9
- package/source/css/_common/cover-overlay.styl +111 -0
- package/source/css/_common/device.styl +3 -3
- package/source/css/_common/title.styl +17 -0
- package/source/css/_components/layout.styl +9 -13
- package/source/css/_components/list.styl +135 -64
- package/source/css/_components/main.styl +2 -2
- package/source/css/_components/md-table.styl +23 -0
- package/source/css/_components/pages/archives.styl +5 -22
- package/source/css/_components/pages/article-story.styl +5 -14
- package/source/css/_components/partial/article-banner.styl +61 -37
- package/source/css/_components/partial/article-tags.styl +13 -0
- package/source/css/_components/partial/bread-nav.styl +19 -12
- package/source/css/_components/partial/navbar.styl +11 -3
- package/source/css/_components/partial/post-panel.styl +76 -0
- package/source/css/_components/pin-slider.styl +38 -41
- package/source/css/_components/sidebar/footer.styl +1 -1
- package/source/css/_components/sidebar/logo.styl +2 -2
- package/source/css/_components/sidebar/search.styl +25 -5
- package/source/css/_components/sidebar/sidebar.styl +7 -7
- package/source/css/_components/tag-plugins/banner.styl +53 -11
- package/source/css/_components/tag-plugins/friends_posts.styl +5 -86
- package/source/css/_components/tag-plugins/gallery.styl +3 -0
- package/source/css/_components/tag-plugins/read/paper.styl +3 -3
- package/source/css/_components/tag-plugins/read/reel.styl +4 -4
- package/source/css/_components/tag-plugins/table.styl +16 -11
- package/source/css/_components/tag-plugins/timeline.styl +4 -0
- package/source/css/_components/widgets/components.styl +6 -0
- package/source/css/_components/widgets/list.styl +6 -0
- package/source/css/_components/widgets/timeline.styl +2 -2
- package/source/css/_components/widgets/toc.styl +13 -7
- package/source/css/_components/widgets/widgets.styl +8 -7
- package/source/css/_custom.styl +9 -21
- package/source/css/_defines/func.styl +28 -3
- package/source/css/_plugins/index.styl +0 -18
- package/source/css/_plugins/lazyload.styl +1 -0
- package/source/css/{_plugins/comments → comments}/artalk.styl +4 -0
- package/source/css/{_plugins/comments → comments}/twikoo.styl +4 -0
- package/source/css/{_plugins → plugins}/mermaid.styl +3 -0
- package/source/css/{_plugins → plugins}/swiper.styl +5 -1
- package/source/js/color.js +325 -0
- package/source/js/icons.js +60 -0
- package/source/js/main.js +115 -1
- package/source/js/plugins/adaptive-text.js +146 -0
- package/source/js/search/highlight.js +82 -0
- package/source/js/search/local-search.js +294 -103
- package/source/js/services/friends_and_posts.js +1 -1
- package/source/js/services/timeline.js +1 -1
- package/source/js/services/weibo.js +3 -3
- package/source/js/services.js +179 -0
- package/source/js/tagtree.js +29 -0
- package/source/js/theme.js +61 -0
- package/{layout/_partial/scripts/utils.ejs → source/js/utils.js} +27 -11
- package/layout/_partial/cover/post_cover.ejs +0 -37
- package/layout/_partial/scripts/services.ejs +0 -178
- package/layout/_partial/scripts/sidebar.ejs +0 -25
- package/layout/_partial/scripts/tagtree.ejs +0 -29
- package/layout/_partial/scripts/theme.ejs +0 -67
- /package/source/css/{_plugins/comments → comments}/beaudar.styl +0 -0
- /package/source/css/{_plugins/comments → comments}/utterances.styl +0 -0
- /package/source/css/{_plugins/comments → comments}/waline.styl +0 -0
- /package/source/css/{_plugins → plugins}/fancybox.styl +0 -0
package/languages/en.yml
CHANGED
|
@@ -39,6 +39,11 @@ meta:
|
|
|
39
39
|
contributors: Page Contributors
|
|
40
40
|
word_count: '%s words'
|
|
41
41
|
reading_time: '%s min read'
|
|
42
|
+
ai_label:
|
|
43
|
+
manual: Written entirely by a human
|
|
44
|
+
reviewed: AI-reviewed
|
|
45
|
+
polished: AI-polished
|
|
46
|
+
generated: AI-generated
|
|
42
47
|
date_suffix:
|
|
43
48
|
just: Just
|
|
44
49
|
min: minutes ago
|
|
@@ -70,6 +75,7 @@ symbol:
|
|
|
70
75
|
colon: ": "
|
|
71
76
|
brackets_l: "("
|
|
72
77
|
brackets_r: ")"
|
|
78
|
+
page: "Page %s"
|
|
73
79
|
|
|
74
80
|
tag_plugins:
|
|
75
81
|
mbti:
|
package/languages/zh-CN.yml
CHANGED
|
@@ -39,6 +39,11 @@ meta:
|
|
|
39
39
|
contributors: 本文贡献者
|
|
40
40
|
word_count: '%s 字'
|
|
41
41
|
reading_time: '预计阅读 %s 分钟'
|
|
42
|
+
ai_label:
|
|
43
|
+
manual: 本文完全由人类完成
|
|
44
|
+
reviewed: 已 AI 审核
|
|
45
|
+
polished: 已 AI 润色
|
|
46
|
+
generated: 由 AI 生成
|
|
42
47
|
date_suffix:
|
|
43
48
|
just: 刚刚
|
|
44
49
|
min: 分钟前
|
|
@@ -70,6 +75,7 @@ symbol:
|
|
|
70
75
|
colon: ":"
|
|
71
76
|
brackets_l: "("
|
|
72
77
|
brackets_r: ")"
|
|
78
|
+
page: "第 %s 页"
|
|
73
79
|
|
|
74
80
|
tag_plugins:
|
|
75
81
|
mbti:
|
package/languages/zh-TW.yml
CHANGED
|
@@ -39,6 +39,11 @@ meta:
|
|
|
39
39
|
contributors: 本文貢獻者
|
|
40
40
|
word_count: '%s 字'
|
|
41
41
|
reading_time: '預計閱讀 %s 分鐘'
|
|
42
|
+
ai_label:
|
|
43
|
+
manual: 本文完全由人類完成
|
|
44
|
+
reviewed: 已 AI 審核
|
|
45
|
+
polished: 已 AI 潤飾
|
|
46
|
+
generated: 由 AI 生成
|
|
42
47
|
date_suffix:
|
|
43
48
|
just: 剛剛
|
|
44
49
|
min: 分鐘前
|
|
@@ -70,6 +75,7 @@ symbol:
|
|
|
70
75
|
colon: ":"
|
|
71
76
|
brackets_l: "("
|
|
72
77
|
brackets_r: ")"
|
|
78
|
+
page: "第 %s 頁"
|
|
73
79
|
|
|
74
80
|
tag_plugins:
|
|
75
81
|
mbti:
|
|
@@ -4,8 +4,13 @@
|
|
|
4
4
|
function initArtalk() {
|
|
5
5
|
const el = document.getElementById('artalk_container');
|
|
6
6
|
if (!el) return;
|
|
7
|
+
|
|
8
|
+
// artalk 邮件通知链接携带 ?atk_comment=<id> / #atk-comment-<id> 定位目标,
|
|
9
|
+
// 命中时跳过视口懒加载立即初始化,让 Artalk 自身的 list-goto 逻辑完成定位。
|
|
10
|
+
const hasAtkTarget = /[?&]atk_comment=\d+/.test(window.location.search) || /#atk-comment-\d+/.test(window.location.hash);
|
|
7
11
|
|
|
8
12
|
function load_artalk() {
|
|
13
|
+
utils.css('<%- url_for('/css/comments/artalk.css') %>?v=<%- stellar_info('version') %>');
|
|
9
14
|
utils.css('<%- theme.comments.artalk.css %>');
|
|
10
15
|
utils.js('<%- theme.comments.artalk.js %>', {defer: true}).then(function () {
|
|
11
16
|
// 监听 Artalk 事件
|
|
@@ -13,6 +18,17 @@
|
|
|
13
18
|
// Artalk.use((ctx) => {
|
|
14
19
|
// ctx.on('list-loaded', () => window.lazyLoadInstance?.update())
|
|
15
20
|
// })
|
|
21
|
+
// 邮件通知链接以 ?atk_comment=<id> 定位,Artalk 在 list-loaded 时才重新读取 URL,
|
|
22
|
+
// 固定延时清理会与之竞态(#598)。先把评论 id 改写到 hash(Artalk 读取优先级高于 query),
|
|
23
|
+
// 同时立即移除 atk_comment 查询参数,避免其 hash 监听干扰目录定位;
|
|
24
|
+
// atk_notify_key 保留到 list-loaded 完成定位与已读回执后再清理。
|
|
25
|
+
const atkComment = window.location.search.match(/[?&]atk_comment=(\d+)/);
|
|
26
|
+
if (atkComment && !/#atk-comment-\d+/.test(window.location.hash)) {
|
|
27
|
+
const params = new URLSearchParams(window.location.search);
|
|
28
|
+
const notifyKey = params.get('atk_notify_key');
|
|
29
|
+
const query = notifyKey ? '?atk_notify_key=' + encodeURIComponent(notifyKey) : '';
|
|
30
|
+
window.history.replaceState(null, '', window.location.pathname + query + '#atk-comment-' + atkComment[1]);
|
|
31
|
+
}
|
|
16
32
|
const path = el.getAttribute('comment_id') ?? decodeURI(window.location.pathname);
|
|
17
33
|
const artalk = Artalk.init({
|
|
18
34
|
el: '#artalk_container',
|
|
@@ -39,18 +55,19 @@
|
|
|
39
55
|
},
|
|
40
56
|
<% } %>
|
|
41
57
|
});
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
58
|
+
// list-loaded 时 Artalk 已读取 atk_comment / atk_notify_key 并触发定位与已读回执,
|
|
59
|
+
// 此时再清理残留的 ?atk_* 参数,避免固定延时与异步加载竞态(#598)。
|
|
60
|
+
if (hasAtkTarget && window.location.search.indexOf('atk_') !== -1) {
|
|
61
|
+
artalk.on('list-loaded', function () {
|
|
62
|
+
window.setTimeout(function () {
|
|
63
|
+
window.history.replaceState(null, '', window.location.pathname + window.location.hash);
|
|
64
|
+
}, 0);
|
|
65
|
+
});
|
|
49
66
|
}
|
|
50
67
|
});
|
|
51
68
|
}
|
|
52
69
|
|
|
53
|
-
util.viewportLazyload(el, load_artalk);
|
|
70
|
+
util.viewportLazyload(el, load_artalk, !hasAtkTarget);
|
|
54
71
|
}
|
|
55
72
|
|
|
56
73
|
// Initialize on page load
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
if (!el) return;
|
|
7
7
|
|
|
8
8
|
function load_twikoo() {
|
|
9
|
+
utils.css('<%- url_for('/css/comments/twikoo.css') %>?v=<%- stellar_info('version') %>');
|
|
9
10
|
utils.js('<%- theme.comments.twikoo.js %>', {defer: true}).then(function () {
|
|
10
11
|
const path = el.getAttribute('comment_id') ?? decodeURI(window.location.pathname);
|
|
11
12
|
twikoo.init(Object.assign(<%- JSON.stringify(theme.comments.twikoo) %>, {
|
package/layout/_partial/head.ejs
CHANGED
|
@@ -1,13 +1,28 @@
|
|
|
1
1
|
<%
|
|
2
|
+
function strip_wiki_title(title, wiki) {
|
|
3
|
+
if (!wiki) { return title; }
|
|
4
|
+
var t = String(title || '');
|
|
5
|
+
if (t.toLowerCase() === String(wiki).toLowerCase()) { return ''; }
|
|
6
|
+
// wiki 名中的空格/连字符在标题中可能写为另一种形态(如 cloud shell / cloud-shell)
|
|
7
|
+
var flex = String(wiki).replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/[\s-]+/g, '[\\s-]*');
|
|
8
|
+
var m = t.match(new RegExp('^' + flex + '[\\s-]*[::\\-](.*)$', 'i'));
|
|
9
|
+
if (m && m[1]) {
|
|
10
|
+
return m[1].trim();
|
|
11
|
+
}
|
|
12
|
+
return t;
|
|
13
|
+
}
|
|
14
|
+
|
|
2
15
|
function generate_title() {
|
|
3
16
|
if (page.wiki) {
|
|
4
17
|
const proj = theme.wiki.tree[page.wiki]
|
|
5
18
|
const wiki = proj?.name || page.wiki
|
|
6
19
|
if (page.title) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
20
|
+
var stripped = strip_wiki_title(page.title, wiki)
|
|
21
|
+
if (stripped) {
|
|
22
|
+
return wiki + __('symbol.colon') + stripped + ' - ' + config.title;
|
|
23
|
+
}
|
|
10
24
|
}
|
|
25
|
+
return wiki + ' - ' + config.title;
|
|
11
26
|
} else {
|
|
12
27
|
if (page.title) {
|
|
13
28
|
return page.title + ' - ' + config.title;
|
|
@@ -17,6 +32,9 @@ function generate_title() {
|
|
|
17
32
|
return __('btn.tag') + __('symbol.colon') + page.tag + ' - ' + config.title;
|
|
18
33
|
}
|
|
19
34
|
}
|
|
35
|
+
if (is_home() && page.current && page.current > 1) {
|
|
36
|
+
return config.title + ' - ' + __('symbol.page', page.current);
|
|
37
|
+
}
|
|
20
38
|
return config.title;
|
|
21
39
|
}
|
|
22
40
|
function generate_description() {
|
|
@@ -75,7 +93,7 @@ function og_args() {
|
|
|
75
93
|
args.description = proj.description;
|
|
76
94
|
}
|
|
77
95
|
}
|
|
78
|
-
args.image = config.avatar || (config.email ? gravatar(config.email) : null);
|
|
96
|
+
args.image = page.cover || page.banner || first_content_image(page.content) || config.avatar || (config.email ? gravatar(config.email) : null);
|
|
79
97
|
return Object.assign(args, page.open_graph);
|
|
80
98
|
}
|
|
81
99
|
|
|
@@ -92,9 +110,6 @@ function generate_og_title() {
|
|
|
92
110
|
}
|
|
93
111
|
|
|
94
112
|
function generate_og_site_name() {
|
|
95
|
-
if (page.layout === 'post' && page.title) {
|
|
96
|
-
return generate_og_title();
|
|
97
|
-
}
|
|
98
113
|
return escape_meta_content(strip_html(config.title));
|
|
99
114
|
}
|
|
100
115
|
|
|
@@ -207,4 +222,25 @@ function generate_canonical() {
|
|
|
207
222
|
|
|
208
223
|
<%- json_ld() %>
|
|
209
224
|
<%- custom_inject() %>
|
|
225
|
+
|
|
226
|
+
<!-- icons.yml 驱动的 CSS 装饰图标 -->
|
|
227
|
+
<%
|
|
228
|
+
const iconCssVars = (() => {
|
|
229
|
+
const map = {
|
|
230
|
+
'--icon-h3-left': 'default:arrow-left',
|
|
231
|
+
'--icon-h3-right': 'default:arrow-right',
|
|
232
|
+
'--icon-quote-left': 'quot:quote-left',
|
|
233
|
+
'--icon-quote-right': 'quot:quote-right'
|
|
234
|
+
};
|
|
235
|
+
let vars = [];
|
|
236
|
+
for (const [name, key] of Object.entries(map)) {
|
|
237
|
+
const raw = theme.icons && theme.icons[key];
|
|
238
|
+
if (raw) vars.push(` ${name}: url("data:image/svg+xml,${encodeURIComponent(raw)}");`);
|
|
239
|
+
}
|
|
240
|
+
return vars.length > 0 ? `:root {\n${vars.join('\n')}\n}` : '';
|
|
241
|
+
})();
|
|
242
|
+
%>
|
|
243
|
+
<% if (iconCssVars) { %>
|
|
244
|
+
<style><%- iconCssVars %></style>
|
|
245
|
+
<% } %>
|
|
210
246
|
</head>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<%# 文章标签行(layout: post,位于 article-footer 之前) %>
|
|
2
|
+
<%
|
|
3
|
+
function layoutDiv() {
|
|
4
|
+
var el = '';
|
|
5
|
+
if (!page.tags || page.tags.length === 0) {
|
|
6
|
+
return el;
|
|
7
|
+
}
|
|
8
|
+
el += `<div class="article-tags${scrollreveal(' ')}">`
|
|
9
|
+
page.tags.forEach((tag, i) => {
|
|
10
|
+
el += `<a class="tag" href="${pretty_url(tag.path)}">${icon('default:hashtag')}${escape_html(tag.name)}</a>`
|
|
11
|
+
})
|
|
12
|
+
el += '</div>'
|
|
13
|
+
return el;
|
|
14
|
+
}
|
|
15
|
+
%>
|
|
16
|
+
<%- layoutDiv(); %>
|
|
@@ -19,17 +19,18 @@ if (banner.subtitle == null) {
|
|
|
19
19
|
banner.subtitle = page.subtitle
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
// 上块:第一行(左侧面包屑 + 右侧阅读时长 / AI 标签)+ 第二行(日期信息,靠左)
|
|
23
|
+
function layoutTop() {
|
|
23
24
|
if (page.breadcrumb === false) {
|
|
24
|
-
return
|
|
25
|
+
return ''
|
|
25
26
|
}
|
|
26
27
|
var el = ''
|
|
27
|
-
// 1
|
|
28
|
+
// 1.上块容器
|
|
28
29
|
el += `<div class="top bread-nav footnote">`
|
|
29
|
-
// 2
|
|
30
|
-
el += `<div class="
|
|
31
|
-
//
|
|
32
|
-
el += `<div class="
|
|
30
|
+
// 2.第一行
|
|
31
|
+
el += `<div class="top-row">`
|
|
32
|
+
// 2.1 左:面包屑导航
|
|
33
|
+
el += `<div class="left" id="breadcrumb">`
|
|
33
34
|
// 首页
|
|
34
35
|
el += `<a class="cap breadcrumb" href="${pretty_url(config.root)}">${__("btn.home")}</a>`
|
|
35
36
|
if (theme.wiki.tree[page.wiki]) {
|
|
@@ -41,20 +42,30 @@ function layoutBreadcrumb() {
|
|
|
41
42
|
} else {
|
|
42
43
|
el += partial('breadcrumb/page')
|
|
43
44
|
}
|
|
44
|
-
|
|
45
|
+
el += `</div>`
|
|
46
|
+
// 2.2 右:字数和预计阅读时长 + AI 成分标签(仅在有内容时渲染)
|
|
47
|
+
var rightHtml = ''
|
|
48
|
+
// 字数和预计阅读时长,置于第一行右侧(#302)
|
|
45
49
|
if (page.layout == 'post' && theme.article.reading_time) {
|
|
46
50
|
var _wc = word_count(page)
|
|
47
51
|
if (_wc > 0) {
|
|
48
|
-
|
|
52
|
+
rightHtml += `<span class="cap reading">${__("meta.word_count", _wc)} · ${__("meta.reading_time", reading_time(page))}</span>`
|
|
49
53
|
}
|
|
50
54
|
}
|
|
51
|
-
//
|
|
55
|
+
// AI 成分标签:置于第一行最右(阅读时长右侧);
|
|
56
|
+
// banner 含图片时不用配置色,继承默认文字色(--text-banner)
|
|
57
|
+
const bannerHasImage = banner.url && banner.url.length > 0
|
|
58
|
+
rightHtml += ai_label(page.ai_label, 'cap', bannerHasImage)
|
|
59
|
+
if (rightHtml.length > 0) {
|
|
60
|
+
el += `<div class="right">${rightHtml}</div>`
|
|
61
|
+
}
|
|
62
|
+
// end 2.第一行
|
|
52
63
|
el += `</div>`
|
|
53
|
-
// 3
|
|
64
|
+
// 3.第二行:日期信息,靠左
|
|
65
|
+
el += `<div class="meta-row">`
|
|
54
66
|
el += partial('dateinfo')
|
|
55
|
-
// end 2.left
|
|
56
67
|
el += `</div>`
|
|
57
|
-
// end 1
|
|
68
|
+
// end 1.上块
|
|
58
69
|
el += `</div>`
|
|
59
70
|
return el
|
|
60
71
|
}
|
|
@@ -108,26 +119,28 @@ function layoutBottom() {
|
|
|
108
119
|
|
|
109
120
|
function layoutDiv() {
|
|
110
121
|
const bottom = layoutBottom()
|
|
111
|
-
|
|
122
|
+
const top = layoutTop()
|
|
123
|
+
if (top.length == 0 && bottom.length == 0) {
|
|
112
124
|
return ``
|
|
113
125
|
}
|
|
114
|
-
const top = layoutBreadcrumb()
|
|
115
126
|
var bannerStyle = ``
|
|
116
|
-
var
|
|
127
|
+
var adaptiveAttr = ``
|
|
128
|
+
var maskHtml = ``
|
|
117
129
|
var el = ``
|
|
118
130
|
if (banner.url?.length > 0) {
|
|
119
131
|
bannerStyle += ` style="--bg-url:url('${banner.url.replace(/'/g, '%27')}')"`
|
|
132
|
+
// 背景图上方文字颜色自适应
|
|
133
|
+
adaptiveAttr = ` data-text-adaptive="split"`
|
|
120
134
|
}
|
|
121
135
|
el += `<div class="article-banner-wrap${scrollreveal(' ')}">`
|
|
122
136
|
el += `<div class="article banner top"${bannerStyle}>`
|
|
123
137
|
if (banner.url?.length > 0) {
|
|
124
138
|
el += `<img class="lazy bg" data-src="${banner.url}">`
|
|
125
|
-
|
|
126
|
-
contentStyle += ' style="--text-banner:' + banner.color + '"'
|
|
127
|
-
}
|
|
139
|
+
maskHtml = `<div class="banner-mask banner-mask-top" aria-hidden="true"></div><div class="banner-mask banner-mask-bottom" aria-hidden="true"></div>`
|
|
128
140
|
}
|
|
129
141
|
el += `
|
|
130
|
-
<div class="content"${
|
|
142
|
+
<div class="content"${adaptiveAttr}>
|
|
143
|
+
${maskHtml}
|
|
131
144
|
${top}
|
|
132
145
|
${bottom}
|
|
133
146
|
</div>
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<% if ((page.total || 0) > 1) { %>
|
|
2
2
|
<div class="paginator-wrap dis-select<%= scrollreveal(' ') %>">
|
|
3
3
|
<%- paginator({
|
|
4
|
-
prev_text: icon('
|
|
5
|
-
next_text: icon('
|
|
4
|
+
prev_text: icon('default:arrow-left'),
|
|
5
|
+
next_text: icon('default:arrow-right'),
|
|
6
6
|
force_prev_next: true,
|
|
7
7
|
escape: false,
|
|
8
8
|
}) %>
|
|
@@ -49,25 +49,13 @@ pinItems.sort(function(a, b) {
|
|
|
49
49
|
return pinWeight(b) - pinWeight(a)
|
|
50
50
|
})
|
|
51
51
|
|
|
52
|
-
// 文章幻灯片:固定「标题 +
|
|
53
|
-
// 标题:
|
|
54
|
-
// 样式与
|
|
55
|
-
function postSubtitle(post) {
|
|
56
|
-
if (post.poster != null && post.poster.caption != null && post.poster.caption.length > 0) {
|
|
57
|
-
return post.poster.caption
|
|
58
|
-
}
|
|
59
|
-
if (post.description != null && post.description.length > 0) {
|
|
60
|
-
return post.description
|
|
61
|
-
}
|
|
62
|
-
var text = post.excerpt || post.content || ''
|
|
63
|
-
text = strip_html(text).replace(/\s+/g, ' ').trim()
|
|
64
|
-
return text.length > 0 ? truncate(text, {length: 50}) : ''
|
|
65
|
-
}
|
|
66
|
-
|
|
52
|
+
// 文章幻灯片:固定「标题 + 一行小字」结构。
|
|
53
|
+
// 标题:post.title;小字由 subtitle() helper 统一取值(subtitle > description > excerpt 前 50 字)。
|
|
54
|
+
// 样式与 hero 卡片 cover-info 一致;无封面时为纯白卡片(文字按普通文章颜色)。
|
|
67
55
|
function postSlide(post) {
|
|
68
56
|
var noCover = !(post.cover != null && post.cover.length > 0)
|
|
69
|
-
var headline =
|
|
70
|
-
var
|
|
57
|
+
var headline = post.title
|
|
58
|
+
var sub = subtitle(post)
|
|
71
59
|
var el = ''
|
|
72
60
|
el += '<a class="pin-slide post-slide' + (noCover ? ' no-cover' : '') + '" href="' + escape_html(pretty_url(post.link || post.path)) + '"'
|
|
73
61
|
if (!noCover) {
|
|
@@ -77,10 +65,11 @@ function postSlide(post) {
|
|
|
77
65
|
if (!noCover) {
|
|
78
66
|
el += '<img class="pin-slide-bg" src="' + escape_html(post.cover) + '" alt=""/>'
|
|
79
67
|
}
|
|
80
|
-
|
|
68
|
+
// 有封面时文字颜色自适应(大字 contrast、小字 theme);无封面保持普通文章文字颜色
|
|
69
|
+
el += '<div class="pin-slide-text"' + (noCover ? '' : ' data-text-adaptive="split"') + '>'
|
|
81
70
|
el += '<div class="pin-slide-headline">' + escape_html(headline) + '</div>'
|
|
82
|
-
if (
|
|
83
|
-
el += '<div class="pin-slide-caption">' + escape_html(
|
|
71
|
+
if (sub.length > 0) {
|
|
72
|
+
el += '<div class="pin-slide-caption">' + escape_html(sub) + '</div>'
|
|
84
73
|
}
|
|
85
74
|
el += '</div>'
|
|
86
75
|
el += '</a>'
|
|
@@ -100,10 +89,9 @@ function wikiSlide(proj) {
|
|
|
100
89
|
var title = proj.title || proj.name || proj.id
|
|
101
90
|
var excerpt = proj.description || ''
|
|
102
91
|
var el = ''
|
|
103
|
-
el += '<a class="pin-slide" href="' + escape_html(pretty_url(proj.homepage?.path || '/')) + '">'
|
|
92
|
+
el += '<a class="pin-slide" href="' + escape_html(pretty_url(proj.homepage?.path || '/')) + '" style="--pin-cover-url:url(\'' + escape_html(img.replace(/'/g, '%27')) + '\')">'
|
|
104
93
|
el += '<img class="pin-slide-bg" src="' + escape_html(img) + '" alt=""/>'
|
|
105
|
-
el += '<div class="pin-slide-
|
|
106
|
-
el += '<div class="pin-slide-info">'
|
|
94
|
+
el += '<div class="pin-slide-info" data-text-adaptive="split">'
|
|
107
95
|
el += '<div class="pin-slide-caps">' + projectChips(proj.tags) + '</div>'
|
|
108
96
|
el += '<div class="pin-slide-title">' + escape_html(title) + '</div>'
|
|
109
97
|
if (excerpt.length > 0) {
|
|
@@ -137,20 +125,21 @@ if (pinItems.length > 0) {
|
|
|
137
125
|
el += '</div>'
|
|
138
126
|
if (dotsHtml.length > 0) {
|
|
139
127
|
el += '<div class="pin-slider-dots">' + dotsHtml + '</div>'
|
|
140
|
-
el += '<button type="button" class="pin-slider-nav prev">' + icon('
|
|
141
|
-
el += '<button type="button" class="pin-slider-nav next">' + icon('
|
|
128
|
+
el += '<button type="button" class="pin-slider-nav prev">' + icon('default:arrow-left') + '</button>'
|
|
129
|
+
el += '<button type="button" class="pin-slider-nav next">' + icon('default:arrow-right') + '</button>'
|
|
142
130
|
}
|
|
143
131
|
el += '</div>'
|
|
144
132
|
%>
|
|
145
133
|
<%- el %>
|
|
146
134
|
<script>
|
|
147
135
|
function initWhenReady() {
|
|
148
|
-
if (typeof
|
|
149
|
-
|
|
136
|
+
if (typeof stellar === 'object' && typeof stellar.initPlugin === 'function') {
|
|
137
|
+
stellar.initPlugin(initPinSlider, 'pin-slider');
|
|
150
138
|
} else {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
139
|
+
// pin_slider 内联脚本渲染在 scripts/bootstrap 之前,等待 DOMContentLoaded 后注册
|
|
140
|
+
document.addEventListener('DOMContentLoaded', function () {
|
|
141
|
+
if (typeof stellar === 'object' && typeof stellar.initPlugin === 'function') {
|
|
142
|
+
stellar.initPlugin(initPinSlider, 'pin-slider');
|
|
154
143
|
}
|
|
155
144
|
});
|
|
156
145
|
}
|
|
@@ -163,6 +152,10 @@ function initPinSlider() {
|
|
|
163
152
|
var slides = slider.querySelectorAll('.pin-slide');
|
|
164
153
|
var count = slides.length;
|
|
165
154
|
if (!track || count <= 1) {
|
|
155
|
+
// 单张幻灯片:直接把封面同步到 .pin-slider 自身背景(跟随 corner-shape)
|
|
156
|
+
if (slides[0]) {
|
|
157
|
+
slider.style.setProperty('--pin-cover-url', slides[0].style.getPropertyValue('--pin-cover-url') || '');
|
|
158
|
+
}
|
|
166
159
|
slider.style.visibility = 'visible';
|
|
167
160
|
return;
|
|
168
161
|
}
|
|
@@ -185,6 +178,11 @@ function initPinSlider() {
|
|
|
185
178
|
function goTo(n) {
|
|
186
179
|
index = (n + count) % count;
|
|
187
180
|
track.style.transform = 'translateX(-' + (index * 100) + '%)';
|
|
181
|
+
// 背景图由 .pin-slider 自身背景承载(跟随 corner-shape),随当前 slide 同步
|
|
182
|
+
var active = slides[index];
|
|
183
|
+
if (active) {
|
|
184
|
+
slider.style.setProperty('--pin-cover-url', active.style.getPropertyValue('--pin-cover-url') || '');
|
|
185
|
+
}
|
|
188
186
|
var dots = slider.querySelector('.pin-slider-dots');
|
|
189
187
|
var items = dots ? dots.children : [];
|
|
190
188
|
for (var i = 0; i < count; i++) {
|
|
@@ -198,9 +196,39 @@ function initPinSlider() {
|
|
|
198
196
|
}
|
|
199
197
|
}
|
|
200
198
|
savePinIndex(group, contentKey, index, count);
|
|
199
|
+
updateNavColor();
|
|
201
200
|
}
|
|
202
201
|
function next() { goTo(index + 1); }
|
|
203
202
|
function prev() { goTo(index - 1); }
|
|
203
|
+
|
|
204
|
+
// 左右箭头颜色随当前幻灯片封面自适应(contrast:深色背景白箭头、浅色背景深箭头)
|
|
205
|
+
var navColorRetries = 0;
|
|
206
|
+
function updateNavColor() {
|
|
207
|
+
var navs = slider.querySelectorAll('.pin-slider-nav');
|
|
208
|
+
if (navs.length === 0) return;
|
|
209
|
+
if (!window.stellar || !window.stellar.color || !window.resolveAdaptiveBackdrop) {
|
|
210
|
+
// 懒加载的 color.js / 插件尚未就绪时重试
|
|
211
|
+
if (navColorRetries < 25) {
|
|
212
|
+
navColorRetries++;
|
|
213
|
+
setTimeout(updateNavColor, 200);
|
|
214
|
+
}
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
var slide = slides[index];
|
|
218
|
+
if (!slide) return;
|
|
219
|
+
var cover = slide.style.getPropertyValue('--pin-cover-url');
|
|
220
|
+
if (!cover) return;
|
|
221
|
+
var m = cover.match(/url\((['"]?)(.*?)\1\)/i);
|
|
222
|
+
if (!m) return;
|
|
223
|
+
stellar.color.getAverageColor(m[2], { background: window.resolveAdaptiveBackdrop(slide) }).then(function (rgb) {
|
|
224
|
+
if (!rgb) return;
|
|
225
|
+
var color = stellar.color.adaptiveTextColor(rgb, { style: 'contrast' });
|
|
226
|
+
for (var i = 0; i < navs.length; i++) {
|
|
227
|
+
navs[i].style.setProperty('--text-banner', color);
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
window.refreshPinNavColor = updateNavColor;
|
|
204
232
|
function start() {
|
|
205
233
|
if (reduced) return;
|
|
206
234
|
slider.classList.remove('paused');
|
|
@@ -294,6 +322,9 @@ function initPinSlider() {
|
|
|
294
322
|
navNext.removeEventListener('click', next);
|
|
295
323
|
}
|
|
296
324
|
document.removeEventListener('visibilitychange', onVis);
|
|
325
|
+
if (window.refreshPinNavColor === updateNavColor) {
|
|
326
|
+
window.refreshPinNavColor = null;
|
|
327
|
+
}
|
|
297
328
|
};
|
|
298
329
|
}
|
|
299
330
|
function restorePinIndex(group, contentKey, count) {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<%
|
|
2
|
+
// 最新文章封面卡片(公共组件):整卡跳转到最新文章,背景为封面图,底部渐变模糊文字区。
|
|
3
|
+
// 入参:href(跳转链接)、background(背景图 URL)、label(专栏名/分类名)、post({title, date})
|
|
4
|
+
// 专栏列表页与未来首页等场景复用;无 post 时仅渲染 label。
|
|
5
|
+
function layoutDiv() {
|
|
6
|
+
var el = '';
|
|
7
|
+
var bg = background || theme.default.topic;
|
|
8
|
+
// 注意:不能用 `var href` 承接,var 提升会遮蔽 partial 传入的 href 参数
|
|
9
|
+
var link = pretty_url(href || '/');
|
|
10
|
+
el += '<a class="cover" position="bottom" style="--cover-url:url(\'' + escape_html(bg.replace(/'/g, '%27')) + '\')" href="' + link + '">';
|
|
11
|
+
el += '<img src="' + escape_html(bg) + '" alt=""/>';
|
|
12
|
+
el += '<div class="cover-info" position="bottom" data-text-adaptive="split">';
|
|
13
|
+
if (typeof label !== 'undefined' && label) {
|
|
14
|
+
el += '<div class="text topic">' + escape_html(label) + '</div>';
|
|
15
|
+
}
|
|
16
|
+
if (post) {
|
|
17
|
+
el += '<div class="text headline">' + escape_html(post.title) + '</div>';
|
|
18
|
+
el += '<div class="text caption">' + escape_html(date(post.date, config.date_format)) + '</div>';
|
|
19
|
+
}
|
|
20
|
+
el += '</div>';
|
|
21
|
+
el += '</a>';
|
|
22
|
+
return el;
|
|
23
|
+
}
|
|
24
|
+
%>
|
|
25
|
+
<%- layoutDiv() %>
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<% if ((is_home() || is_category() || is_tag()) && page.total > 1) { %>
|
|
2
2
|
<div class='paginator-wrap dis-select<%- scrollreveal(' ') %>'>
|
|
3
3
|
<%- paginator({
|
|
4
|
-
prev_text: icon('
|
|
5
|
-
next_text: icon('
|
|
4
|
+
prev_text: icon('default:arrow-left'),
|
|
5
|
+
next_text: icon('default:arrow-right'),
|
|
6
6
|
force_prev_next: true,
|
|
7
7
|
escape: false
|
|
8
8
|
}) %>
|