hexo-theme-stellar 1.41.0 → 1.42.1
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 +2 -2
- package/CHANGELOG.md +60 -0
- package/CONTRIBUTING.md +2 -1
- package/README.md +50 -21
- package/README_EN.md +113 -0
- package/_config.yml +11 -6
- package/_data/icons.yml +0 -2
- package/layout/_partial/cover/index.ejs +0 -3
- package/layout/_partial/main/navbar/article_banner.ejs +30 -27
- package/layout/_partial/main/pin_slider.ejs +59 -28
- package/layout/_partial/main/post_list/latest_post_card.ejs +25 -0
- 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/scripts/bootstrap.ejs +37 -0
- package/layout/_partial/scripts/defines.ejs +2 -1
- package/layout/_partial/scripts.ejs +9 -0
- package/layout/_partial/widgets/toc.ejs +2 -2
- package/layout/_plugins/adaptive_text.ejs +11 -0
- package/layout/_plugins/copycode.ejs +2 -2
- package/layout/_plugins/index.ejs +25 -0
- package/layout/_plugins/mermaid.ejs +1 -1
- package/layout/_plugins/scrollreveal.ejs +22 -16
- package/layout/_plugins/swiper.ejs +1 -1
- package/layout/index.ejs +10 -2
- package/layout/index_topic.ejs +4 -4
- package/package.json +3 -3
- package/scripts/filters/lib/img_lazyload.js +184 -40
- package/scripts/generators/stellar-icons.js +1 -1
- package/scripts/helpers/subtitle.js +8 -0
- package/scripts/lib/subtitle.js +24 -0
- package/scripts/tags/lib/banner.js +4 -3
- package/source/css/_common/cover-overlay.styl +111 -0
- 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/pages/archives.styl +4 -1
- package/source/css/_components/pages/article-story.styl +1 -10
- package/source/css/_components/partial/article-banner.styl +61 -37
- package/source/css/_components/partial/article-tags.styl +4 -1
- package/source/css/_components/partial/bread-nav.styl +7 -14
- package/source/css/_components/partial/navbar.styl +1 -1
- 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 +1 -1
- 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/timeline.styl +4 -0
- package/source/css/_components/widgets/ghrepo.styl +4 -0
- package/source/css/_components/widgets/timeline.styl +2 -2
- package/source/css/_components/widgets/toc.styl +12 -6
- package/source/css/_components/widgets/widgets.styl +8 -7
- package/source/css/_custom.styl +8 -9
- package/source/css/_plugins/lazyload.styl +1 -0
- package/source/css/comments/waline.styl +24 -0
- package/source/js/color.js +325 -0
- package/source/js/main.js +2 -2
- package/source/js/plugins/adaptive-text.js +146 -0
- package/source/js/services/friends_and_posts.js +1 -1
- package/source/js/services/timeline.js +1 -1
- package/source/js/services/weibo.js +1 -1
- package/source/js/services.js +1 -1
- package/source/js/utils.js +14 -1
- package/layout/_partial/cover/post_cover.ejs +0 -37
|
@@ -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) {
|
|
@@ -145,12 +133,13 @@ if (pinItems.length > 0) {
|
|
|
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,47 +1,16 @@
|
|
|
1
1
|
<%
|
|
2
|
-
const poster = post.poster;
|
|
3
2
|
var obj = {
|
|
4
3
|
image: post.cover
|
|
5
4
|
};
|
|
6
|
-
if (poster) {
|
|
7
|
-
obj.headline = poster.headline;
|
|
8
|
-
obj.topic = poster.topic;
|
|
9
|
-
obj.caption = poster.caption;
|
|
10
|
-
obj.color = poster.color;
|
|
11
|
-
}
|
|
12
5
|
function div_default() {
|
|
13
6
|
var el = '';
|
|
14
7
|
el += '<article class="md-text">';
|
|
15
8
|
|
|
16
|
-
//
|
|
17
|
-
if (obj.image
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
cover_url = obj.image;
|
|
22
|
-
} else {
|
|
23
|
-
cover_url = 'https://source.unsplash.com/1280x640/?' + obj.image;
|
|
24
|
-
}
|
|
25
|
-
} else {
|
|
26
|
-
// 自动以 tags 作为关键词搜索封面
|
|
27
|
-
if (post.tags) {
|
|
28
|
-
var params = '';
|
|
29
|
-
post.tags.reverse().forEach((tag, i) => {
|
|
30
|
-
if (i > 0) {
|
|
31
|
-
params += ',';
|
|
32
|
-
}
|
|
33
|
-
params += tag.name;
|
|
34
|
-
});
|
|
35
|
-
cover_url = 'https://source.unsplash.com/1280x640/?' + params;
|
|
36
|
-
} else {
|
|
37
|
-
cover_url = 'https://source.unsplash.com/random/1280x640';
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
if (cover_url) {
|
|
41
|
-
el += '<div class="post-cover">';
|
|
42
|
-
el += '<img src="' + escape_html(cover_url) + '"/>';
|
|
43
|
-
el += '</div>';
|
|
44
|
-
}
|
|
9
|
+
// 封面:仅当显式 cover 为完整 URL 时渲染(Unsplash 自动封面接口已失效,不再生成)
|
|
10
|
+
if (obj.image && obj.image.includes('/')) {
|
|
11
|
+
el += '<div class="post-cover">';
|
|
12
|
+
el += '<img src="' + escape_html(obj.image) + '"/>';
|
|
13
|
+
el += '</div>';
|
|
45
14
|
}
|
|
46
15
|
|
|
47
16
|
// 标题
|
|
@@ -100,7 +69,7 @@ function div_default() {
|
|
|
100
69
|
if (i >= 5) {
|
|
101
70
|
return;
|
|
102
71
|
}
|
|
103
|
-
el += `<span class="cap tag-chip"
|
|
72
|
+
el += `<span class="cap tag-chip">${icon('default:hashtag')}${escape_html(tag.name)}</span>`;
|
|
104
73
|
});
|
|
105
74
|
el += '</span>';
|
|
106
75
|
}
|
|
@@ -110,39 +79,22 @@ function div_default() {
|
|
|
110
79
|
}
|
|
111
80
|
function div_photo() {
|
|
112
81
|
var el = '';
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
} else {
|
|
118
|
-
position = 'bottom';
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
el += '<div class="cover" position="' + position + '" style="--cover-url:url(\'' + escape_html(obj.image.replace(/'/g, '%27')) + '\')">';
|
|
82
|
+
// 小字取值统一由 subtitle() helper 提供:subtitle > description > excerpt 前 50 字
|
|
83
|
+
var caption = subtitle(post);
|
|
84
|
+
// hero 卡片文字区固定 bottom:标题 + 一行小字,不再有 top 布局
|
|
85
|
+
el += '<div class="cover" position="bottom" style="--cover-url:url(\'' + escape_html(obj.image.replace(/'/g, '%27')) + '\')">';
|
|
122
86
|
el += '<img src="' + escape_html(obj.image) + '"/>';
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
128
|
-
el += '>';
|
|
129
|
-
if (obj.topic) {
|
|
130
|
-
el += '<div class="text topic">' + escape_html(obj.topic) + '</div>';
|
|
131
|
-
}
|
|
132
|
-
if (obj.headline) {
|
|
133
|
-
el += '<div class="text headline">' + escape_html(obj.headline) + '</div>';
|
|
134
|
-
}
|
|
135
|
-
if (obj.caption) {
|
|
136
|
-
el += '<div class="text caption">' + escape_html(obj.caption) + '</div>';
|
|
137
|
-
}
|
|
138
|
-
el += '</div>';
|
|
139
|
-
|
|
87
|
+
el += '<div class="cover-info" position="bottom" data-text-adaptive="split">';
|
|
88
|
+
el += '<div class="text headline">' + escape_html(post.title ? post.title : date(post.date, config.date_format)) + '</div>';
|
|
89
|
+
if (caption.length > 0) {
|
|
90
|
+
el += '<div class="text caption">' + escape_html(caption) + '</div>';
|
|
140
91
|
}
|
|
141
92
|
el += '</div>';
|
|
93
|
+
el += '</div>';
|
|
142
94
|
return el;
|
|
143
95
|
}
|
|
144
96
|
function div() {
|
|
145
|
-
if (obj.image && obj.image.length > 0
|
|
97
|
+
if (theme.article.card_style == 'hero' && obj.image && obj.image.length > 0) {
|
|
146
98
|
return div_photo();
|
|
147
99
|
}
|
|
148
100
|
return div_default();
|
|
@@ -1,14 +1,35 @@
|
|
|
1
1
|
<%
|
|
2
2
|
function layoutDiv() {
|
|
3
3
|
var el = '';
|
|
4
|
+
var bg = topic.cover || topic.icon || theme.default.topic;
|
|
5
|
+
var label = topic.title || topic.name;
|
|
6
|
+
var latest = topic.homepage;
|
|
4
7
|
el += '<article class="md-text">';
|
|
5
|
-
|
|
6
|
-
el += '<
|
|
7
|
-
|
|
8
|
+
// 专栏标题(story 文章 h2 样式,置于卡片外)
|
|
9
|
+
el += '<h2 class="topic-title">' + escape_html(label) + '</h2>';
|
|
10
|
+
// 专栏描述(标题下方)
|
|
8
11
|
if (topic.description) {
|
|
9
|
-
el += '<p>' + topic.description + '</p>';
|
|
12
|
+
el += '<p class="topic-desc">' + escape_html(topic.description) + '</p>';
|
|
13
|
+
}
|
|
14
|
+
// 最新文章卡片(整卡跳转最新文章)
|
|
15
|
+
el += partial('_partial/main/post_list/latest_post_card', {
|
|
16
|
+
href: pretty_url(latest ? (latest.path || '/') : '/'),
|
|
17
|
+
background: bg,
|
|
18
|
+
post: latest
|
|
19
|
+
});
|
|
20
|
+
// 其他文章(排除最新,最多 3 条,按发布时间倒序)
|
|
21
|
+
if (topic.pages && topic.pages.length > 1) {
|
|
22
|
+
el += '<div class="post-panel">';
|
|
23
|
+
el += '<div class="posts">';
|
|
24
|
+
topic.pages.slice(1, 4).forEach(function(post) {
|
|
25
|
+
el += '<a class="post-link" href="' + pretty_url(post.path) + '">';
|
|
26
|
+
el += '<span class="title">' + escape_html(post.title) + '</span>';
|
|
27
|
+
el += '<span class="date">' + escape_html(date(post.date, config.date_format)) + '</span>';
|
|
28
|
+
el += '</a>';
|
|
29
|
+
});
|
|
30
|
+
el += '</div>';
|
|
31
|
+
el += '</div>';
|
|
10
32
|
}
|
|
11
|
-
el += '</div>';
|
|
12
33
|
el += '</article>';
|
|
13
34
|
return el;
|
|
14
35
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
// 插件注册队列:utils.js 尚未就绪时先缓存注册请求,就绪后统一补跑,
|
|
3
|
+
// 保证解析期注册的插件不因加载时序丢失。
|
|
4
|
+
window.stellar = window.stellar || {};
|
|
5
|
+
window.stellar._pluginQueue = window.stellar._pluginQueue || [];
|
|
6
|
+
window.stellar.initPlugin = function (fn, name, options) {
|
|
7
|
+
if (typeof utils === 'object' && typeof utils.initPlugin === 'function') {
|
|
8
|
+
return utils.initPlugin(fn, name, options);
|
|
9
|
+
}
|
|
10
|
+
window.stellar._pluginQueue.push({ fn: fn, name: name, options: options || {} });
|
|
11
|
+
};
|
|
12
|
+
window.stellar._flushPlugins = function () {
|
|
13
|
+
var pending = window.stellar._pluginQueue.splice(0);
|
|
14
|
+
if (typeof utils !== 'object' || typeof utils.initPlugin !== 'function') return;
|
|
15
|
+
for (var i = 0; i < pending.length; i++) {
|
|
16
|
+
try {
|
|
17
|
+
utils.initPlugin(pending[i].fn, pending[i].name, pending[i].options);
|
|
18
|
+
} catch (e) {
|
|
19
|
+
console.error('[Stellar] 插件补注册失败:', e);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
document.addEventListener('DOMContentLoaded', function () {
|
|
24
|
+
if (typeof utils === 'object') return;
|
|
25
|
+
// utils 未就绪时补载;加载成功后由 utils.js 内部补跑插件队列
|
|
26
|
+
var s = document.createElement('script');
|
|
27
|
+
// 用 setAttribute 赋值 src,避免被基于 `s.src = "..."` 的正则
|
|
28
|
+
// (图片懒加载 / 脚本延迟优化器)误改写
|
|
29
|
+
s.setAttribute('src', '<%- url_for('/js/utils.js') %>?v=<%- stellar_info('version') %>');
|
|
30
|
+
s.async = false;
|
|
31
|
+
s.onerror = function () {
|
|
32
|
+
console.error('[Stellar] 无法加载 /js/utils.js,已启用内容兜底显示(sr-fallback)');
|
|
33
|
+
document.documentElement.classList.add('sr-fallback');
|
|
34
|
+
};
|
|
35
|
+
document.head.appendChild(s);
|
|
36
|
+
});
|
|
37
|
+
</script>
|
|
@@ -195,7 +195,8 @@
|
|
|
195
195
|
if (!window.location.search) return;
|
|
196
196
|
if (!new URLSearchParams(window.location.search).has('kw')) return;
|
|
197
197
|
document.addEventListener('DOMContentLoaded', function () {
|
|
198
|
-
// utils
|
|
198
|
+
// utils 由 /js/utils.js 定义(同时暴露 window.utils);
|
|
199
|
+
// 未就绪时由 bootstrap 队列兜底,此处按就绪状态判断
|
|
199
200
|
if (typeof utils === 'object' && typeof utils.js === 'function') {
|
|
200
201
|
utils.js('<%- url_for('/js/search/highlight.js') %>', { defer: true }).catch(function () {});
|
|
201
202
|
}
|
|
@@ -34,8 +34,17 @@ function tagtreeWidgetOnPage() {
|
|
|
34
34
|
%>
|
|
35
35
|
<%- partial('scripts/defines') %>
|
|
36
36
|
|
|
37
|
+
<%- partial('scripts/bootstrap') %>
|
|
38
|
+
|
|
37
39
|
<!-- core: utils 必须同步加载(页尾内联插件片段在解析期即调用 utils.initPlugin) -->
|
|
38
40
|
<script src="<%- url_for('/js/utils.js') %>?v=<%- stellar_info('version') %>"></script>
|
|
41
|
+
<script>
|
|
42
|
+
// 解析期检测 utils 是否已就绪;未就绪时同步补载,
|
|
43
|
+
// 保证「utils 先于页尾插件片段定义」的不变量。
|
|
44
|
+
if (typeof utils === 'undefined') {
|
|
45
|
+
document.write('<script src="<%- url_for('/js/utils.js') %>?v=<%- stellar_info('version') %>"><\/script>');
|
|
46
|
+
}
|
|
47
|
+
</script>
|
|
39
48
|
<script defer src="<%- url_for('/js/stellar-icons.js') %>?v=<%- stellar_info('version') %>"></script>
|
|
40
49
|
<!-- 非首屏图标异步加载:占位符替换依赖构建期生成的 js/icons/{ns}.json -->
|
|
41
50
|
<script defer src="<%- url_for('/js/icons.js') %>?v=<%- stellar_info('version') %>" data-version="<%- stellar_info('version') %>"></script>
|
|
@@ -60,12 +60,12 @@ function layoutDiv(fallback) {
|
|
|
60
60
|
}
|
|
61
61
|
el += `<div class="widget-footer">`
|
|
62
62
|
el += `<a class="top" onclick="util.scrollTop()">`
|
|
63
|
-
el += icon('default:upup')
|
|
63
|
+
el += icon('default:upup', '', true)
|
|
64
64
|
el += `<span>${__('btn.top')}</span>`
|
|
65
65
|
el += `</a>`
|
|
66
66
|
if (hasComments) {
|
|
67
67
|
el += `<a class="buttom" onclick="util.scrollComment()">`
|
|
68
|
-
el += icon('default:tocomment')
|
|
68
|
+
el += icon('default:tocomment', '', true)
|
|
69
69
|
el += `<span>${__('btn.comments')}</span>`
|
|
70
70
|
el += `</a>`
|
|
71
71
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
stellar.initPlugin(() => {
|
|
3
|
+
const els = Array.prototype.slice.call(document.querySelectorAll('[data-text-adaptive]'));
|
|
4
|
+
if (els.length === 0) return;
|
|
5
|
+
utils.js('/js/color.js').then(() => utils.js('/js/plugins/adaptive-text.js')).then(() => {
|
|
6
|
+
if (typeof applyAdaptiveText === 'function') {
|
|
7
|
+
applyAdaptiveText(els);
|
|
8
|
+
}
|
|
9
|
+
});
|
|
10
|
+
}, 'adaptive-text');
|
|
11
|
+
</script>
|
|
@@ -1,3 +1,28 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
// 防御:bootstrap 被第三方优化器改写/移除导致 window.stellar.initPlugin 缺失时,
|
|
3
|
+
// 在此补一个等价注册点:utils 就绪时直接委托,未就绪时入队,utils 加载后自动补跑。
|
|
4
|
+
if (typeof window.stellar === 'undefined' || typeof window.stellar.initPlugin !== 'function') {
|
|
5
|
+
window.stellar = window.stellar || {};
|
|
6
|
+
window.stellar._pluginQueue = window.stellar._pluginQueue || [];
|
|
7
|
+
window.stellar.initPlugin = function (fn, name, options) {
|
|
8
|
+
if (typeof utils === 'object' && typeof utils.initPlugin === 'function') {
|
|
9
|
+
return utils.initPlugin(fn, name, options);
|
|
10
|
+
}
|
|
11
|
+
window.stellar._pluginQueue.push({ fn: fn, name: name, options: options || {} });
|
|
12
|
+
};
|
|
13
|
+
window.stellar._flushPlugins = function () {
|
|
14
|
+
var pending = window.stellar._pluginQueue.splice(0);
|
|
15
|
+
if (typeof utils !== 'object' || typeof utils.initPlugin !== 'function') return;
|
|
16
|
+
for (var i = 0; i < pending.length; i++) {
|
|
17
|
+
try {
|
|
18
|
+
utils.initPlugin(pending[i].fn, pending[i].name, pending[i].options);
|
|
19
|
+
} catch (e) {
|
|
20
|
+
console.error('[Stellar] 插件补注册失败:', e);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
</script>
|
|
1
26
|
<%
|
|
2
27
|
function loadPlugins() {
|
|
3
28
|
var el = ''
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script defer type="text/javascript" src="<%- conf.js %>"></script>
|
|
2
2
|
<script>
|
|
3
|
-
|
|
3
|
+
stellar.initPlugin(() => {
|
|
4
4
|
const els = document.querySelectorAll('.mermaid');
|
|
5
5
|
if (els.length > 0) {
|
|
6
6
|
utils.css(`<%- url_for('/css/plugins/mermaid.css') %>?v=<%- stellar_info('version') %>`);
|
|
@@ -1,7 +1,20 @@
|
|
|
1
1
|
<script>
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
(function () {
|
|
3
|
+
// 兜底看门狗独立于插件初始化:3 秒内未完成 reveal 时强制显示 .slide-up 内容
|
|
4
|
+
let fallbackApplied = false;
|
|
5
|
+
const revealFallback = () => {
|
|
6
|
+
if (fallbackApplied) return;
|
|
7
|
+
fallbackApplied = true;
|
|
8
|
+
document.documentElement.classList.add('sr-fallback');
|
|
9
|
+
};
|
|
10
|
+
const watchdog = setTimeout(() => revealFallback(), 3000);
|
|
11
|
+
|
|
12
|
+
stellar.initPlugin(() => {
|
|
13
|
+
const els = Array.prototype.slice.call(document.querySelectorAll('.slide-up'));
|
|
14
|
+
if (els.length === 0) {
|
|
15
|
+
clearTimeout(watchdog);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
5
18
|
|
|
6
19
|
// 吸顶/固定定位容器内的元素始终在视口内(或由容器控制显隐),
|
|
7
20
|
// 但 ScrollReveal 按文档坐标判断可见性,会误判这类元素为不可见,
|
|
@@ -45,15 +58,10 @@
|
|
|
45
58
|
targets.push(el);
|
|
46
59
|
}
|
|
47
60
|
});
|
|
48
|
-
if (pinnedGroups.size === 0 && targets.length === 0)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const revealFallback = () => {
|
|
53
|
-
if (fallbackApplied) return;
|
|
54
|
-
fallbackApplied = true;
|
|
55
|
-
document.documentElement.classList.add('sr-fallback');
|
|
56
|
-
};
|
|
61
|
+
if (pinnedGroups.size === 0 && targets.length === 0) {
|
|
62
|
+
clearTimeout(watchdog);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
57
65
|
|
|
58
66
|
const slideUp = {
|
|
59
67
|
distance: `<%- conf.distance %>`,
|
|
@@ -64,9 +72,6 @@
|
|
|
64
72
|
easing: "ease-out"
|
|
65
73
|
};
|
|
66
74
|
|
|
67
|
-
// 看门狗:CDN 长时间无响应时强制显示内容
|
|
68
|
-
const watchdog = setTimeout(() => revealFallback(), 3000);
|
|
69
|
-
|
|
70
75
|
utils.js(`<%- conf.js %>`, { defer: true })
|
|
71
76
|
.then(() => {
|
|
72
77
|
if (fallbackApplied) return;
|
|
@@ -92,5 +97,6 @@
|
|
|
92
97
|
console.error('[Plugin scrollreveal] 加载失败:', err);
|
|
93
98
|
revealFallback();
|
|
94
99
|
});
|
|
95
|
-
|
|
100
|
+
}, 'scrollreveal');
|
|
101
|
+
})();
|
|
96
102
|
</script>
|
package/layout/index.ejs
CHANGED
|
@@ -76,11 +76,19 @@ if (pinFlat && is_home_first_page()) {
|
|
|
76
76
|
function layout_post_card(layout, post, content) {
|
|
77
77
|
var el = '';
|
|
78
78
|
var layout = layout;
|
|
79
|
-
|
|
79
|
+
var hero = false;
|
|
80
|
+
if (layout == 'post' && post.cover != undefined && theme.article.card_style == 'hero') {
|
|
80
81
|
layout += ' photo';
|
|
82
|
+
hero = true;
|
|
81
83
|
}
|
|
82
84
|
el += `<div class="post-card-wrap${scrollreveal(' ')}">`;
|
|
83
|
-
|
|
85
|
+
if (hero) {
|
|
86
|
+
// hero 封面卡片:背景图由 .post-card 自身背景承载(跟随 corner-shape 圆角裁剪),
|
|
87
|
+
// 子图保留用于 hover 变暗/放大滤镜
|
|
88
|
+
el += `<a class="post-card ${layout}" style="--cover-url:url('${escape_html(post.cover.replace(/'/g, '%27'))}')" href="${pretty_url(post.link || post.path)}">`
|
|
89
|
+
} else {
|
|
90
|
+
el += `<a class="post-card ${layout}" href="${pretty_url(post.link || post.path)}">`
|
|
91
|
+
}
|
|
84
92
|
el += content;
|
|
85
93
|
el += '</a>';
|
|
86
94
|
el += '</div>';
|
package/layout/index_topic.ejs
CHANGED
|
@@ -8,9 +8,9 @@ function layout_topic_list(partial) {
|
|
|
8
8
|
continue
|
|
9
9
|
}
|
|
10
10
|
el += `<div class="post-card-wrap${scrollreveal(' ')}">`
|
|
11
|
-
el += `<
|
|
11
|
+
el += `<div class="post-card topic">`
|
|
12
12
|
el += partial(topic)
|
|
13
|
-
el += `</
|
|
13
|
+
el += `</div>`
|
|
14
14
|
el += `</div>`
|
|
15
15
|
}
|
|
16
16
|
return el
|
|
@@ -18,7 +18,7 @@ function layout_topic_list(partial) {
|
|
|
18
18
|
function layoutDiv() {
|
|
19
19
|
var el = ''
|
|
20
20
|
el += partial('_partial/main/navbar/nav_tabs_blog')
|
|
21
|
-
el += `<div class="post-list
|
|
21
|
+
el += `<div class="post-list topic">`;
|
|
22
22
|
el += layout_topic_list(function(topic) {
|
|
23
23
|
return partial('_partial/main/post_list/topic_card', {topic: topic})
|
|
24
24
|
})
|
|
@@ -27,4 +27,4 @@ function layoutDiv() {
|
|
|
27
27
|
}
|
|
28
28
|
%>
|
|
29
29
|
|
|
30
|
-
<%- layoutDiv() %>
|
|
30
|
+
<%- layoutDiv() %>
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hexo-theme-stellar",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.42.1",
|
|
4
4
|
"description": "Elegant and powerful theme for Hexo.",
|
|
5
5
|
"main": "package.json",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"lint": "eslint .",
|
|
8
|
-
"test": "node ci/check-require-decls.js && node --test",
|
|
9
|
-
"check": "npm run lint && npm test && python3 docs/knowledge/tools/verify.py",
|
|
8
|
+
"test": "node ci/check-require-decls.js && node --test && node ci/check-spec-refs.js",
|
|
9
|
+
"check": "npm run lint && npm test && python3 docs/knowledge/tools/verify.py && node ci/check-release-docs.js",
|
|
10
10
|
"release": "node release.js",
|
|
11
11
|
"release:dry": "node release.js --dry-run"
|
|
12
12
|
},
|