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.
Files changed (146) hide show
  1. package/.claude/skills/stellar-theme-dev/SKILL.md +1 -2
  2. package/CHANGELOG.md +90 -0
  3. package/CONTRIBUTING.md +2 -1
  4. package/README.md +50 -21
  5. package/README_EN.md +113 -0
  6. package/_config.yml +40 -12
  7. package/_data/icons.yml +248 -37
  8. package/languages/en.yml +6 -0
  9. package/languages/zh-CN.yml +6 -0
  10. package/languages/zh-TW.yml +6 -0
  11. package/layout/_partial/comments/artalk/script.ejs +25 -8
  12. package/layout/_partial/comments/beaudar/script.ejs +1 -0
  13. package/layout/_partial/comments/twikoo/script.ejs +1 -0
  14. package/layout/_partial/comments/utterances/script.ejs +1 -0
  15. package/layout/_partial/comments/waline/script.ejs +1 -0
  16. package/layout/_partial/cover/index.ejs +0 -3
  17. package/layout/_partial/head.ejs +43 -7
  18. package/layout/_partial/main/article/article_tags.ejs +16 -0
  19. package/layout/_partial/main/navbar/article_banner.ejs +33 -20
  20. package/layout/_partial/main/notebook/note_card.ejs +1 -1
  21. package/layout/_partial/main/notebook/paginator.ejs +2 -2
  22. package/layout/_partial/main/pin_slider.ejs +61 -30
  23. package/layout/_partial/main/post_list/latest_post_card.ejs +25 -0
  24. package/layout/_partial/main/post_list/paginator.ejs +2 -2
  25. package/layout/_partial/main/post_list/post_card.ejs +16 -64
  26. package/layout/_partial/main/post_list/topic_card.ejs +26 -5
  27. package/layout/_partial/menubtn.ejs +2 -2
  28. package/layout/_partial/scripts/bootstrap.ejs +35 -0
  29. package/layout/_partial/scripts/defines.ejs +27 -2
  30. package/layout/_partial/scripts.ejs +39 -7
  31. package/layout/_partial/sidebar/logo.ejs +1 -1
  32. package/layout/_partial/sidebar/menu.ejs +1 -1
  33. package/layout/_partial/sidebar/search.ejs +1 -1
  34. package/layout/_partial/widgets/author.ejs +1 -1
  35. package/layout/_partial/widgets/components/link.ejs +2 -2
  36. package/layout/_partial/widgets/ghuser.ejs +1 -1
  37. package/layout/_partial/widgets/related.ejs +1 -1
  38. package/layout/_partial/widgets/toc.ejs +3 -3
  39. package/layout/_partial/widgets/tree.ejs +1 -1
  40. package/layout/_plugins/adaptive_text.ejs +11 -0
  41. package/layout/_plugins/copycode.ejs +2 -2
  42. package/layout/_plugins/fancybox.ejs +4 -2
  43. package/layout/_plugins/mermaid.ejs +3 -2
  44. package/layout/_plugins/scrollreveal.ejs +22 -16
  45. package/layout/_plugins/search/local_search.ejs +4 -3
  46. package/layout/_plugins/swiper.ejs +2 -1
  47. package/layout/categories.ejs +1 -1
  48. package/layout/index.ejs +10 -2
  49. package/layout/index_topic.ejs +4 -4
  50. package/layout/page.ejs +3 -0
  51. package/layout/tags.ejs +1 -0
  52. package/package.json +3 -3
  53. package/scripts/events/lib/doc_tree.js +12 -208
  54. package/scripts/events/lib/notebooks.js +8 -148
  55. package/scripts/events/lib/utils.js +10 -1
  56. package/scripts/filters/index.js +1 -0
  57. package/scripts/filters/lib/img_lazyload.js +4 -0
  58. package/scripts/filters/lib/img_onerror.js +4 -0
  59. package/scripts/filters/lib/md_table.js +46 -0
  60. package/scripts/generators/search.js +12 -18
  61. package/scripts/generators/stellar-icons.js +47 -0
  62. package/scripts/helpers/ai_label.js +29 -0
  63. package/scripts/helpers/icon.js +2 -2
  64. package/scripts/helpers/json_ld.js +10 -13
  65. package/scripts/helpers/related_posts.js +0 -7
  66. package/scripts/helpers/seo.js +9 -0
  67. package/scripts/helpers/subtitle.js +8 -0
  68. package/scripts/lib/ai_label.js +55 -0
  69. package/scripts/lib/doc_tree.js +267 -0
  70. package/scripts/lib/notebooks.js +181 -0
  71. package/scripts/lib/search_index.js +82 -0
  72. package/scripts/lib/seo.js +79 -0
  73. package/scripts/lib/subtitle.js +24 -0
  74. package/scripts/tags/lib/about.js +2 -2
  75. package/scripts/tags/lib/albums.js +1 -1
  76. package/scripts/tags/lib/banner.js +5 -4
  77. package/scripts/tags/lib/chat.js +49 -49
  78. package/scripts/tags/lib/copy.js +1 -1
  79. package/scripts/tags/lib/friends.js +1 -1
  80. package/scripts/tags/lib/gallery.js +1 -1
  81. package/scripts/tags/lib/hashtag.js +1 -1
  82. package/scripts/tags/lib/image.js +3 -3
  83. package/scripts/tags/lib/posters.js +1 -1
  84. package/scripts/tags/lib/sites.js +2 -2
  85. package/source/css/_common/base.styl +5 -9
  86. package/source/css/_common/cover-overlay.styl +111 -0
  87. package/source/css/_common/device.styl +3 -3
  88. package/source/css/_common/title.styl +17 -0
  89. package/source/css/_components/layout.styl +9 -13
  90. package/source/css/_components/list.styl +135 -64
  91. package/source/css/_components/main.styl +2 -2
  92. package/source/css/_components/md-table.styl +23 -0
  93. package/source/css/_components/pages/archives.styl +5 -22
  94. package/source/css/_components/pages/article-story.styl +5 -14
  95. package/source/css/_components/partial/article-banner.styl +61 -37
  96. package/source/css/_components/partial/article-tags.styl +13 -0
  97. package/source/css/_components/partial/bread-nav.styl +19 -12
  98. package/source/css/_components/partial/navbar.styl +11 -3
  99. package/source/css/_components/partial/post-panel.styl +76 -0
  100. package/source/css/_components/pin-slider.styl +38 -41
  101. package/source/css/_components/sidebar/footer.styl +1 -1
  102. package/source/css/_components/sidebar/logo.styl +2 -2
  103. package/source/css/_components/sidebar/search.styl +25 -5
  104. package/source/css/_components/sidebar/sidebar.styl +7 -7
  105. package/source/css/_components/tag-plugins/banner.styl +53 -11
  106. package/source/css/_components/tag-plugins/friends_posts.styl +5 -86
  107. package/source/css/_components/tag-plugins/gallery.styl +3 -0
  108. package/source/css/_components/tag-plugins/read/paper.styl +3 -3
  109. package/source/css/_components/tag-plugins/read/reel.styl +4 -4
  110. package/source/css/_components/tag-plugins/table.styl +16 -11
  111. package/source/css/_components/tag-plugins/timeline.styl +4 -0
  112. package/source/css/_components/widgets/components.styl +6 -0
  113. package/source/css/_components/widgets/list.styl +6 -0
  114. package/source/css/_components/widgets/timeline.styl +2 -2
  115. package/source/css/_components/widgets/toc.styl +13 -7
  116. package/source/css/_components/widgets/widgets.styl +8 -7
  117. package/source/css/_custom.styl +9 -21
  118. package/source/css/_defines/func.styl +28 -3
  119. package/source/css/_plugins/index.styl +0 -18
  120. package/source/css/_plugins/lazyload.styl +1 -0
  121. package/source/css/{_plugins/comments → comments}/artalk.styl +4 -0
  122. package/source/css/{_plugins/comments → comments}/twikoo.styl +4 -0
  123. package/source/css/{_plugins → plugins}/mermaid.styl +3 -0
  124. package/source/css/{_plugins → plugins}/swiper.styl +5 -1
  125. package/source/js/color.js +325 -0
  126. package/source/js/icons.js +60 -0
  127. package/source/js/main.js +115 -1
  128. package/source/js/plugins/adaptive-text.js +146 -0
  129. package/source/js/search/highlight.js +82 -0
  130. package/source/js/search/local-search.js +294 -103
  131. package/source/js/services/friends_and_posts.js +1 -1
  132. package/source/js/services/timeline.js +1 -1
  133. package/source/js/services/weibo.js +3 -3
  134. package/source/js/services.js +179 -0
  135. package/source/js/tagtree.js +29 -0
  136. package/source/js/theme.js +61 -0
  137. package/{layout/_partial/scripts/utils.ejs → source/js/utils.js} +27 -11
  138. package/layout/_partial/cover/post_cover.ejs +0 -37
  139. package/layout/_partial/scripts/services.ejs +0 -178
  140. package/layout/_partial/scripts/sidebar.ejs +0 -25
  141. package/layout/_partial/scripts/tagtree.ejs +0 -29
  142. package/layout/_partial/scripts/theme.ejs +0 -67
  143. /package/source/css/{_plugins/comments → comments}/beaudar.styl +0 -0
  144. /package/source/css/{_plugins/comments → comments}/utterances.styl +0 -0
  145. /package/source/css/{_plugins/comments → comments}/waline.styl +0 -0
  146. /package/source/css/{_plugins → plugins}/fancybox.styl +0 -0
@@ -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 || theme.article.auto_cover) {
18
- var cover_url;
19
- if (obj.image != undefined) {
20
- if (obj.image.includes('/')) {
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">#${escape_html(tag.name)}</span>`;
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
- var position = '';
114
- if (obj.headline || obj.topic || obj.caption) {
115
- if (obj.topic) {
116
- position = 'top';
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
- if (position.length > 0) {
124
- el += '<div class="cover-info" position="' + position + '"';
125
- if (obj.color) {
126
- el += 'style="color:' + escape_html(obj.color) + '"';
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 && obj.headline != undefined) {
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
- el += `<div class="preview"><img src="${topic.icon ? topic.icon : theme.default.topic}"/></div>`
6
- el += '<div class="excerpt">';
7
- el += '<h2 class="post-title">' + (topic.title || topic.name) + '</h2>';
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
  }
@@ -1,8 +1,8 @@
1
1
  <div class='float-panel'>
2
2
  <button type='button' style='display:none' class='laptop-only rightbar-toggle mobile' onclick='sidebar.rightbar()'>
3
- <%- icon('default:rightbar') %>
3
+ <%- icon('default:rightbar', '', true) %>
4
4
  </button>
5
5
  <button type='button' style='display:none' class='mobile-only leftbar-toggle mobile' onclick='sidebar.leftbar()'>
6
- <%- icon('default:leftbar') %>
6
+ <%- icon('default:leftbar', '', true) %>
7
7
  </button>
8
8
  </div>
@@ -0,0 +1,35 @@
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
+ s.src = '<%- url_for('/js/utils.js') %>?v=<%- stellar_info('version') %>';
28
+ s.async = false;
29
+ s.onerror = function () {
30
+ console.error('[Stellar] 无法加载 /js/utils.js,已启用内容兜底显示(sr-fallback)');
31
+ document.documentElement.classList.add('sr-fallback');
32
+ };
33
+ document.head.appendChild(s);
34
+ });
35
+ </script>
@@ -156,7 +156,10 @@
156
156
  root : `<%- config.root %>`,
157
157
  tag_plugins: {
158
158
  chat: Object.assign(<%- JSON.stringify(theme.tag_plugins.chat) %>),
159
- }
159
+ },
160
+ // 图标由构建期生成的 /js/stellar-icons.js 填充(defer,早于 DOMContentLoaded 就绪)
161
+ icons: {},
162
+ services: <%- JSON.stringify(theme.data_services || {}) %>,
160
163
  };
161
164
 
162
165
  // required plugins (only load if needs)
@@ -171,11 +174,33 @@
171
174
  const def = {
172
175
  avatar: `<%- theme.default.avatar %>`,
173
176
  cover: `<%- theme.default.cover %>`,
174
- loading: `<%- theme.default.loading %>`,
177
+ loading: `<%- theme.default.loading || (theme.icons && theme.icons['default:loading-placeholder']) %>`,
175
178
  data_cache: <%- JSON.stringify(theme.data_cache || {}) %>,
176
179
  };
177
180
  const deps = {
178
181
  marked: `<%- url_for(theme.dependencies.marked) %>`,
179
182
  lazyload: `<%- url_for(theme.dependencies.lazyload.js) %>`
180
183
  }
184
+
185
+ // 主题切换提示文案(供 /js/theme.js 使用)
186
+ window.__STELLAR_I18N__ = {
187
+ light: `<%- __('message.theme_switched.light') %>`,
188
+ dark: `<%- __('message.theme_switched.dark') %>`,
189
+ auto: `<%- __('message.theme_switched.auto') %>`,
190
+ };
191
+
192
+ // 搜索跳转高亮:URL 带 ?kw= 时按需加载,定位正文中的匹配词
193
+ (function () {
194
+ try {
195
+ if (!window.location.search) return;
196
+ if (!new URLSearchParams(window.location.search).has('kw')) return;
197
+ document.addEventListener('DOMContentLoaded', function () {
198
+ // utils 由 /js/utils.js 定义(同时暴露 window.utils);
199
+ // 未就绪时由 bootstrap 队列兜底,此处按就绪状态判断
200
+ if (typeof utils === 'object' && typeof utils.js === 'function') {
201
+ utils.js('<%- url_for('/js/search/highlight.js') %>', { defer: true }).catch(function () {});
202
+ }
203
+ });
204
+ } catch (e) {}
205
+ })();
181
206
  </script>
@@ -14,22 +14,54 @@ function custom_inject() {
14
14
  return el;
15
15
  }
16
16
 
17
+ // tagtree 脚本仅在与 tagtree 小部件渲染相同的条件下加载(见 _partial/sidebar/index_leftbar.ejs)
18
+ function tagtreeWidgetOnPage() {
19
+ const leftbar = page && page.leftbar;
20
+ if (!Array.isArray(leftbar)) {
21
+ return false;
22
+ }
23
+ return leftbar.some(function (w) {
24
+ if (typeof w === 'string') {
25
+ return w === 'tagtree';
26
+ }
27
+ if (w && typeof w === 'object') {
28
+ return w.override === 'tagtree' || w.layout === 'tagtree';
29
+ }
30
+ return false;
31
+ });
32
+ }
33
+
17
34
  %>
18
35
  <%- partial('scripts/defines') %>
19
- <%- partial('scripts/utils') %>
20
- <%- partial('scripts/sidebar') %>
21
- <%- partial('scripts/tagtree') %>
22
- <%- partial('scripts/lazyload') %>
23
36
 
24
- <!-- required -->
37
+ <%- partial('scripts/bootstrap') %>
38
+
39
+ <!-- core: utils 必须同步加载(页尾内联插件片段在解析期即调用 utils.initPlugin) -->
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>
48
+ <script defer src="<%- url_for('/js/stellar-icons.js') %>?v=<%- stellar_info('version') %>"></script>
49
+ <!-- 非首屏图标异步加载:占位符替换依赖构建期生成的 js/icons/{ns}.json -->
50
+ <script defer src="<%- url_for('/js/icons.js') %>?v=<%- stellar_info('version') %>" data-version="<%- stellar_info('version') %>"></script>
25
51
  <script src="<%- url_for(`${theme.stellar.main_js}?v=${stellar_info('version')}`) %>" defer></script>
52
+ <script defer src="<%- url_for('/js/theme.js') %>?v=<%- stellar_info('version') %>"></script>
26
53
 
27
- <%- partial('scripts/theme') %>
54
+ <% if (tagtreeWidgetOnPage()) { %>
55
+ <script>window.__STELLAR_TAGTREE__ = { expand_active: <%- JSON.stringify(!!(theme.widgets.tagtree && theme.widgets.tagtree.expand_active)) %> };</script>
56
+ <script defer src="<%- url_for('/js/tagtree.js') %>?v=<%- stellar_info('version') %>"></script>
57
+ <% } %>
58
+
59
+ <%- partial('scripts/lazyload') %>
28
60
 
29
61
  <!-- optional -->
30
62
  <%- partial('comments/script') %>
31
63
 
32
- <%- partial('scripts/services') %>
64
+ <script defer src="<%- url_for('/js/services.js') %>?v=<%- stellar_info('version') %>"></script>
33
65
  <%- partial('../_plugins/index') %>
34
66
 
35
67
  <!-- inject -->
@@ -50,7 +50,7 @@ function layoutBack() {
50
50
  var el = ''
51
51
  el += `<div class="wiki-home-wrap">`
52
52
  el += `<a class="wiki-home cap" href="${pretty_url(theme.site_tree.index_wiki.base_dir)}">`
53
- el += icon('default:goback')
53
+ el += icon('default:goback', '', true)
54
54
  el += `<span>${__('btn.all_wiki')}</span>`
55
55
  el += `</a>`
56
56
  el += `</div>`
@@ -15,7 +15,7 @@ function layoutDiv() {
15
15
  el += ` style="${style}"`
16
16
  el += ` title="${item.title}" href="${pretty_url(item.url)}">`
17
17
  if (item.icon?.length > 0) {
18
- el += icon(item.icon, 'no-lazy')
18
+ el += icon(item.icon, 'no-lazy', true)
19
19
  }
20
20
  el += `<span>${__(item.title)}</span>`
21
21
  if (active) {
@@ -32,7 +32,7 @@ function layoutDiv() {
32
32
  }
33
33
  }
34
34
  el += `<a class="search-button" onclick="document.getElementById(&quot;search-input&quot;).focus();">`
35
- el += icon('default:search')
35
+ el += icon('default:search', '', true)
36
36
  el += `</a>`
37
37
  el += `<input type="text" class="search-input" id="search-input"`
38
38
  if (filter.length > 0) {
@@ -26,7 +26,7 @@ function layoutDiv() {
26
26
 
27
27
  // homepage
28
28
  el += `<a class="follow" href="${url_for(author.path)}">`
29
- el += icon('solar:documents-bold-duotone')
29
+ el += icon('default:documents')
30
30
  el += __('btn.all_posts')
31
31
  el += `</a>`
32
32
  el += `</div>`
@@ -12,10 +12,10 @@ function layoutDiv() {
12
12
  }
13
13
  el += `</div>`
14
14
  if (columns == 1 && isActive) {
15
- el += icon('default:bookmark.active')
15
+ el += '<span class="active-dot"></span>'
16
16
  }
17
17
  el += `</a>`
18
18
  return el
19
19
  }
20
20
  %>
21
- <%- layoutDiv() %>
21
+ <%- layoutDiv() %>
@@ -30,7 +30,7 @@ function layoutDiv() {
30
30
  if (item.header) {
31
31
  el += `<div class="widget-header">`
32
32
  el += `<a class="gh-url" href="https://github.com/${item.username}">
33
- <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from TDesign Icons by TDesign - https://github.com/Tencent/tdesign-icons/blob/main/LICENSE --><path fill="currentColor" d="M12 .999c-6.074 0-11 5.05-11 11.278c0 4.983 3.152 9.21 7.523 10.702c.55.104.727-.246.727-.543v-2.1c-3.06.683-3.697-1.33-3.697-1.33c-.5-1.304-1.222-1.65-1.222-1.65c-.998-.7.076-.686.076-.686c1.105.08 1.686 1.163 1.686 1.163c.98 1.724 2.573 1.226 3.201.937c.098-.728.383-1.226.698-1.508c-2.442-.286-5.01-1.253-5.01-5.574c0-1.232.429-2.237 1.132-3.027c-.114-.285-.49-1.432.107-2.985c0 0 .924-.303 3.026 1.156c.877-.25 1.818-.375 2.753-.38c.935.005 1.876.13 2.755.38c2.1-1.459 3.023-1.156 3.023-1.156c.598 1.554.222 2.701.108 2.985c.706.79 1.132 1.796 1.132 3.027c0 4.332-2.573 5.286-5.022 5.565c.394.35.754 1.036.754 2.088v3.095c0 .3.176.652.734.542C19.852 21.484 23 17.258 23 12.277C23 6.048 18.075.999 12 .999"/></svg>
33
+ ${icon('github:logo-alt')}
34
34
  <span>github/${item.username}</span>
35
35
  </a>`
36
36
  el += `</div>`
@@ -18,7 +18,7 @@ function relatedPostsInTopic() {
18
18
  el += `<a class="item${isActive ? ' active' : ''}" href="${pretty_url(post.path)}">`
19
19
  el += `<span class="title">${post.title}</span>`
20
20
  if (isActive) {
21
- el += icon('default:bookmark.active')
21
+ el += '<span class="active-dot"></span>'
22
22
  }
23
23
  el += `</a>`
24
24
  }
@@ -15,7 +15,7 @@ function layoutTocHeader(title) {
15
15
  var el = ''
16
16
  el += `<div class="widget-header dis-select">`
17
17
  el += `<span class="name">${title || __("meta.toc")}</span>`
18
- el += `<a class="cap-action" onclick="sidebar.toggleTOC()" >${icon('default:rightbar')}</a>`
18
+ el += `<a class="cap-action" onclick="sidebar.toggleTOC()" >${icon('default:rightbar', '', true)}</a>`
19
19
  el += `</div>`
20
20
  return el
21
21
  }
@@ -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
  }
@@ -27,7 +27,7 @@ function layoutDocTree(pages) {
27
27
  el += `<a class="link${isActive}" href="${href}">`
28
28
  el += `<span class="toc-text">${p.title}</span>`
29
29
  if (isActive.length > 0) {
30
- el += icon('default:bookmark.active')
30
+ el += '<span class="active-dot"></span>'
31
31
  }
32
32
  el += `</a>`
33
33
  }
@@ -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,5 +1,5 @@
1
1
  <script>
2
- utils.initPlugin(() => {
2
+ stellar.initPlugin(() => {
3
3
  const codeElements = document.querySelectorAll('.code');
4
4
  if (codeElements.length > 0) {
5
5
  ctx.copycode = {
@@ -14,4 +14,4 @@
14
14
  });
15
15
  }
16
16
  }, 'copycode');
17
- </script>
17
+ </script>
@@ -7,8 +7,9 @@
7
7
  };
8
8
  // auto=按需:页面存在可弹窗图片才加载;global=全局:所有页面加载
9
9
  const isGlobal = ctx.fancybox.mode === 'global';
10
- // 评论区图片(artalk/twikoo/waline)显示较小,自动支持放大
11
- var selector = '[data-fancybox]:not(.error), .with-fancybox img:not([atk-emoticon]):not([class*="emo"])';
10
+ // 评论区图片(artalk/twikoo/waline)显示较小,自动支持放大;
11
+ // 仅绑定各评论服务的评论内容区,编辑器、表情面板等非内容区图片不弹窗
12
+ var selector = '[data-fancybox]:not(.error), .with-fancybox .atk-content img:not([atk-emoticon]):not([class*="emo"]), .with-fancybox .tk-content img:not([atk-emoticon]):not([class*="emo"]), .with-fancybox .wl-content img:not([atk-emoticon]):not([class*="emo"])';
12
13
  if (ctx.fancybox.selector) {
13
14
  selector += `, ${ctx.fancybox.selector}`
14
15
  }
@@ -31,6 +32,7 @@
31
32
  }
32
33
  }
33
34
  if (needFancybox) {
35
+ utils.css(`<%- url_for('/css/plugins/fancybox.css') %>?v=<%- stellar_info('version') %>`);
34
36
  utils.css(ctx.fancybox.css);
35
37
  utils.js(ctx.fancybox.js, { defer: true }).then(function () {
36
38
  if (isGlobal) {
@@ -1,8 +1,9 @@
1
1
  <script defer type="text/javascript" src="<%- conf.js %>"></script>
2
2
  <script>
3
- utils.initPlugin(() => {
3
+ stellar.initPlugin(() => {
4
4
  const els = document.querySelectorAll('.mermaid');
5
5
  if (els.length > 0) {
6
+ utils.css(`<%- url_for('/css/plugins/mermaid.css') %>?v=<%- stellar_info('version') %>`);
6
7
  const mermaid_config = {
7
8
  startOnLoad: false,
8
9
  theme:
@@ -30,4 +31,4 @@
30
31
  mermaid.run();
31
32
  }
32
33
  }, 'mermaid');
33
- </script>
34
+ </script>
@@ -1,7 +1,20 @@
1
1
  <script>
2
- utils.initPlugin(() => {
3
- const els = Array.prototype.slice.call(document.querySelectorAll('.slide-up'));
4
- if (els.length === 0) return;
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) return;
49
-
50
- // 加载/初始化失败时兜底显示内容,避免整页空白
51
- let fallbackApplied = false;
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
- }, 'scrollreveal');
100
+ }, 'scrollreveal');
101
+ })();
96
102
  </script>
@@ -1,8 +1,9 @@
1
1
  <script>
2
2
  window.addEventListener('DOMContentLoaded', (event) => {
3
- ctx.search = {
3
+ ctx.search = Object.assign({}, ctx.search, {
4
4
  path: `<%- conf.path || '/search.json' %>`,
5
- }
5
+ local_search: <%- JSON.stringify(conf) %>,
6
+ });
6
7
  utils.js('/js/search/local-search.js', { defer: true });
7
8
  });
8
- </script>
9
+ </script>
@@ -1,7 +1,8 @@
1
1
  <script>
2
- utils.initPlugin(() => {
2
+ stellar.initPlugin(() => {
3
3
  const swiper_api = document.getElementById('swiper-api');
4
4
  if (swiper_api != undefined) {
5
+ utils.css(`<%- url_for('/css/plugins/swiper.css') %>?v=<%- stellar_info('version') %>`);
5
6
  utils.css(`<%- conf.css %>`);
6
7
  utils.js(`<%- conf.js %>`, { defer: true }).then(function () {
7
8
  const effect = swiper_api.getAttribute('effect') || '';
@@ -16,7 +16,7 @@ if (page.menu_id == undefined) {
16
16
  nodes.forEach(function(node) {
17
17
  el += `<div class="cat-item">`
18
18
  el += `<a class="cat${node.parentId ? ' child' : ''}" href="${pretty_url(node.path)}">`
19
- el += `<span class="name">${icon('default:category')}${escape_html(node.name)}</span>`
19
+ el += `<span class="name">${icon(node.children && node.children.length > 0 ? 'default:category-open' : 'default:category')}${escape_html(node.name)}</span>`
20
20
  el += `<span class="badge">(${node.count})</span>`
21
21
  el += `</a>`
22
22
  if (node.children && node.children.length > 0) {
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
- if (layout == 'post' && post.cover != undefined && post.poster != undefined) {
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
- el += `<a class="post-card ${layout}" href="${pretty_url(post.link || post.path)}">`
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>';
@@ -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 += `<a class="post-card wiki topic" href="${pretty_url(topic.homepage?.path || '/')}">`
11
+ el += `<div class="post-card topic">`
12
12
  el += partial(topic)
13
- el += `</a>`
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 wiki topic">`;
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/layout/page.ejs CHANGED
@@ -56,6 +56,9 @@ function layoutDiv() {
56
56
  }
57
57
  el += `</article>`
58
58
  if (layout === 'post' || page.wiki || notebook) {
59
+ if (layout === 'post' && theme.article.tags) {
60
+ el += partial('_partial/main/article/article_tags')
61
+ }
59
62
  el += partial('_partial/main/article/article_footer')
60
63
  }
61
64
  if (layout === 'post' || page.wiki) {