hexo-theme-stellar 1.33.0 → 1.34.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 (83) hide show
  1. package/CLAUDE.md +217 -0
  2. package/README.md +2 -0
  3. package/_config.yml +35 -26
  4. package/docs/audits/2026-08-08-stellar-analysis.md +267 -0
  5. package/docs/release-process.md +59 -0
  6. package/layout/_partial/comments/artalk/script.ejs +56 -41
  7. package/layout/_partial/comments/beaudar/script.ejs +35 -20
  8. package/layout/_partial/comments/giscus/script.ejs +34 -19
  9. package/layout/_partial/comments/twikoo/script.ejs +29 -14
  10. package/layout/_partial/comments/utterances/script.ejs +35 -20
  11. package/layout/_partial/comments/waline/script.ejs +40 -28
  12. package/layout/_partial/cover/wiki_cover.ejs +1 -1
  13. package/layout/_partial/head.ejs +53 -4
  14. package/layout/_partial/scripts/defines.ejs +21 -3
  15. package/layout/_partial/scripts/lazyload.ejs +2 -2
  16. package/layout/_partial/scripts/services.ejs +101 -41
  17. package/layout/_partial/scripts/utils.ejs +144 -5
  18. package/layout/_partial/sidebar/index_leftbar.ejs +3 -0
  19. package/layout/_partial/sidebar/search.ejs +3 -0
  20. package/layout/_partial/widgets/ghuser.ejs +18 -4
  21. package/layout/_partial/widgets/linklist.ejs +1 -1
  22. package/layout/_partial/widgets/timeline.ejs +36 -33
  23. package/layout/_plugins/copycode.ejs +9 -5
  24. package/layout/_plugins/mathjax.ejs +47 -18
  25. package/layout/_plugins/mermaid.ejs +28 -24
  26. package/layout/_plugins/pjax.ejs +8 -0
  27. package/layout/_plugins/scrollreveal.ejs +14 -13
  28. package/layout/_plugins/swiper.ejs +2 -2
  29. package/layout/index.ejs +4 -2
  30. package/layout/index_topic.ejs +4 -2
  31. package/layout/index_wiki.ejs +6 -4
  32. package/layout/layout.ejs +3 -2
  33. package/package.json +2 -1
  34. package/scripts/commands/new-note.js +49 -0
  35. package/scripts/filters/lib/img_onerror.js +0 -2
  36. package/scripts/helpers/json_ld.js +11 -8
  37. package/scripts/helpers/pretty_url.js +5 -0
  38. package/scripts/tags/lib/chat.js +16 -9
  39. package/scripts/tags/lib/frame.js +2 -1
  40. package/scripts/tags/lib/image.js +6 -5
  41. package/scripts/tags/lib/link.js +6 -4
  42. package/scripts/tags/lib/timeline.js +101 -79
  43. package/source/css/_common/canonical.styl +11 -6
  44. package/source/css/_common/highlight.styl +4 -1
  45. package/source/css/_common/html.styl +4 -0
  46. package/source/css/_common/loading.styl +5 -1
  47. package/source/css/_components/md.styl +3 -0
  48. package/source/css/_components/pages/article-story.styl +2 -0
  49. package/source/css/_components/partial/article-footer.styl +1 -0
  50. package/source/css/_components/partial/navbar.styl +4 -2
  51. package/source/css/_components/sidebar/menu.styl +1 -0
  52. package/source/css/_components/sidebar/search.styl +4 -0
  53. package/source/css/_components/sidebar/sidebar.styl +12 -5
  54. package/source/css/_components/tag-plugins/chat.styl +6 -8
  55. package/source/css/_components/tag-plugins/folding.styl +1 -0
  56. package/source/css/_components/tag-plugins/gallery.styl +2 -0
  57. package/source/css/_components/tag-plugins/grid.styl +1 -0
  58. package/source/css/_components/tag-plugins/image.styl +3 -0
  59. package/source/css/_components/tag-plugins/link.styl +1 -0
  60. package/source/css/_components/tag-plugins/navbar.styl +15 -13
  61. package/source/css/_components/tag-plugins/note.styl +1 -0
  62. package/source/css/_components/tag-plugins/okr.styl +1 -0
  63. package/source/css/_components/tag-plugins/sites.styl +2 -0
  64. package/source/css/_components/tag-plugins/timeline.styl +9 -0
  65. package/source/css/_components/tag-plugins/vote.styl +2 -0
  66. package/source/css/_components/widgets/components.styl +1 -0
  67. package/source/css/_components/widgets/ghuser.styl +19 -3
  68. package/source/css/_components/widgets/list.styl +1 -0
  69. package/source/css/_components/widgets/toc.styl +3 -0
  70. package/source/css/_components/widgets/widgets.styl +4 -0
  71. package/source/css/_custom.styl +6 -3
  72. package/source/css/_defines/func.styl +24 -8
  73. package/source/css/_defines/theme_base.styl +1 -1
  74. package/source/css/_plugins/comments/artalk.styl +6 -2
  75. package/source/css/_plugins/comments/twikoo.styl +2 -0
  76. package/source/css/_plugins/index.styl +2 -0
  77. package/source/css/_plugins/pjax.styl +61 -0
  78. package/source/js/main.js +80 -26
  79. package/source/js/plugins/copycode.js +34 -27
  80. package/source/js/plugins/pjax.js +646 -0
  81. package/source/js/search/local-search.js +5 -2
  82. package/source/js/services/memos.js +42 -4
  83. package/source/js/services/rss.js +200 -0
@@ -1,86 +1,114 @@
1
1
  <script defer>
2
- window.addEventListener('DOMContentLoaded', (event) => {
2
+ const initServices = () => {
3
+ // 用于存储需要清理的资源
4
+ let timers = [];
5
+
3
6
  ctx.services = Object.assign({}, JSON.parse(`<%- JSON.stringify(theme.data_services) %>`));
4
7
  for (let id of Object.keys(ctx.services)) {
5
8
  const js = ctx.services[id].js;
6
9
  if (id == 'siteinfo') {
7
- ctx.cardlinks = document.querySelectorAll('a.link-card[cardlink]');
8
- if (ctx.cardlinks?.length > 0) {
10
+ const cardlinks = document.querySelectorAll('a.link-card[cardlink]');
11
+ if (cardlinks?.length > 0) {
9
12
  utils.js(js, { defer: true }).then(function () {
10
- setCardLink(ctx.cardlinks);
13
+ setCardLink(cardlinks);
11
14
  });
12
15
  }
16
+ } else if (id == 'ghinfo') {
17
+ const els = document.querySelectorAll('.ds-ghinfo');
18
+ if (els.length > 0) {
19
+ utils.js(js, { defer: true });
20
+ }
13
21
  } else if (id == 'voice') {
14
- ctx.voiceAudios = document.querySelectorAll('.voice>audio');
15
- if (ctx.voiceAudios?.length > 0) {
22
+ const voiceAudios = document.querySelectorAll('.voice>audio');
23
+ if (voiceAudios?.length > 0) {
16
24
  utils.js(js, { defer: true }).then(function () {
17
- createVoiceDom(ctx.voiceAudios);
25
+ createVoiceDom(voiceAudios);
18
26
  });
19
27
  }
20
28
  } else if (id == 'video') {
21
- ctx.videos = document.querySelectorAll('.video>video');
22
- if (ctx.videos?.length > 0) {
29
+ const videos = document.querySelectorAll('.video>video');
30
+ if (videos?.length > 0) {
23
31
  utils.js(js, { defer: true }).then(function () {
24
- videoEvents(ctx.videos);
32
+ videoEvents(videos);
25
33
  });
26
34
  }
27
35
  } else if (id == 'download-file') {
28
- ctx.files = document.querySelectorAll('.file');
29
- if (ctx.files?.length > 0) {
36
+ const files = document.querySelectorAll('.chat-file');
37
+ if (files?.length > 0) {
30
38
  utils.js(js, { defer: true }).then(function () {
31
- downloadFileEvent(ctx.files);
39
+ downloadFileEvent(files);
32
40
  });
33
41
  }
34
42
  } else {
35
43
  const els = document.getElementsByClassName(`ds-${id}`);
36
44
  if (els?.length > 0) {
37
45
  utils.jq(() => {
38
- if (id == 'timeline' || 'memos' || 'marked') {
46
+ if (id == 'timeline' || id == 'memos' || id == 'marked') {
39
47
  utils.js(deps.marked).then(function () {
40
48
  utils.js(js, { defer: true });
41
49
  });
42
50
  } else {
43
51
  utils.js(js, { defer: true });
44
52
  }
45
- });
53
+ }, false);
46
54
  }
47
55
  }
48
56
  }
49
57
 
58
+ // search
59
+ if (ctx.search && typeof searchFunc === 'function') {
60
+ const $inputArea = $("input#search-input");
61
+ if ($inputArea.length > 0 && !$inputArea[0]._searchInitialized) {
62
+ const path = ctx.search.path.startsWith('/') ? ctx.root + ctx.search.path.substring(1) : ctx.root + ctx.search.path;
63
+ const filter = $inputArea.attr('data-filter') || '';
64
+ searchFunc(path, filter, 'search-wrapper', 'search-input', 'search-result');
65
+ // 标记为已初始化,防止重复绑定
66
+ $inputArea[0]._searchInitialized = true;
67
+ }
68
+ }
69
+
50
70
  // chat iphone time
51
71
  let phoneTimes = document.querySelectorAll('.chat .status-bar .time');
72
+ let firstAdjustInterval = null;
73
+ let mainInterval = null;
52
74
 
53
75
  if (phoneTimes.length > 0) {
54
76
  NowTime();
55
- var date = new Date();
56
- var sec = date.getSeconds();
57
- var firstAdjustInterval = setInterval(firstAdjustTime, 1000 * (60 - sec));
58
- }
59
-
60
- function firstAdjustTime() {
61
- NowTime();
62
- clearInterval(firstAdjustInterval);
63
- setInterval(NowTime, 1000 * 60);
64
- }
77
+ const date = new Date();
78
+ const sec = date.getSeconds();
79
+ firstAdjustInterval = setInterval(firstAdjustTime, 1000 * (60 - sec));
80
+ timers.push(firstAdjustInterval);
65
81
 
66
- function NowTime() {
67
- for (let i = 0; i < phoneTimes.length; ++i) {
68
- var timeSpan = phoneTimes[i];
69
- var date = new Date();
70
- var hour = date.getHours();
71
- var min = date.getMinutes();
72
- timeSpan.innerHTML = check(hour) + ":" + check(min);
82
+ function firstAdjustTime() {
83
+ NowTime();
84
+ if (firstAdjustInterval) {
85
+ clearInterval(firstAdjustInterval);
86
+ firstAdjustInterval = null;
87
+ }
88
+ mainInterval = setInterval(NowTime, 1000 * 60);
89
+ timers.push(mainInterval);
73
90
  }
74
- };
75
91
 
76
- function check(val) {
77
- if (val < 10) {
78
- return ("0" + val);
92
+ function NowTime() {
93
+ for (let i = 0; i < phoneTimes.length; ++i) {
94
+ const timeSpan = phoneTimes[i];
95
+ const date = new Date();
96
+ const hour = date.getHours();
97
+ const min = date.getMinutes();
98
+ timeSpan.innerHTML = check(hour) + ":" + check(min);
99
+ }
100
+ };
101
+
102
+ function check(val) {
103
+ if (val < 10) {
104
+ return ("0" + val);
105
+ }
106
+ return (val);
79
107
  }
80
- return (val);
81
108
  }
82
109
 
83
- // chat quote
110
+ // chat quote - 存储事件监听器以便清理
111
+ const quoteClickHandlers = new Map();
84
112
  const chat_quote_obverser = new IntersectionObserver((entries, observer) => {
85
113
  entries.filter((entry) => { return entry.isIntersecting }).sort((a, b) => a.intersectionRect.y !== b.intersectionRect.y ? a.intersectionRect.y - b.intersectionRect.y : a.intersectionRect.x - b.intersectionRect.x).forEach((entry, index) => {
86
114
  observer.unobserve(entry.target);
@@ -95,7 +123,7 @@
95
123
 
96
124
  var chatQuotes = document.querySelectorAll(".chat .talk .quote");
97
125
  chatQuotes.forEach((quote) => {
98
- quote.addEventListener('click', function () {
126
+ const handler = function () {
99
127
  var chatCellDom = document.getElementById("quote-" + quote.getAttribute("quotedCellTag"));
100
128
  if (chatCellDom) {
101
129
  var chatDiv = chatCellDom.parentElement;
@@ -114,7 +142,39 @@
114
142
  }
115
143
  chat_quote_obverser.observe(chatCellDom);
116
144
  }
117
- });
145
+ };
146
+ quote.addEventListener('click', handler);
147
+ quoteClickHandlers.set(quote, handler); // 保存处理器引用
118
148
  });
119
- });
149
+
150
+ // 返回清理函数,用于清理定时器和观察器
151
+ return () => {
152
+ // 清理所有定时器(包括可能未完成的 firstAdjustInterval)
153
+ timers.forEach(timer => {
154
+ if (timer) clearInterval(timer);
155
+ });
156
+ timers = [];
157
+ if (firstAdjustInterval) {
158
+ clearInterval(firstAdjustInterval);
159
+ firstAdjustInterval = null;
160
+ }
161
+ if (mainInterval) {
162
+ clearInterval(mainInterval);
163
+ mainInterval = null;
164
+ }
165
+
166
+ // 断开观察器
167
+ if (chat_quote_obverser) {
168
+ chat_quote_obverser.disconnect();
169
+ }
170
+
171
+ // 移除所有 click 事件监听器
172
+ quoteClickHandlers.forEach((handler, quote) => {
173
+ quote.removeEventListener('click', handler);
174
+ });
175
+ quoteClickHandlers.clear();
176
+ };
177
+ };
178
+
179
+ utils.initPlugin(initServices, 'services');
120
180
  </script>
@@ -18,9 +18,10 @@
18
18
  this.list.push(f);
19
19
  };
20
20
  this.remove = (name) => {
21
- for (let index = 0; index < this.list.length; index++) {
21
+ // 倒序遍历避免 splice 后的索引问题
22
+ for (let index = this.list.length - 1; index >= 0; index--) {
22
23
  const e = this.list[index];
23
- if (e.name == name) {
24
+ if (e.name === name) {
24
25
  this.list.splice(index, 1);
25
26
  }
26
27
  }
@@ -41,8 +42,15 @@
41
42
  }
42
43
 
43
44
  const utils = {
45
+ // 已加载样式缓存
46
+ _loadedStyles: new Set(),
47
+
44
48
  // 懒加载 css https://github.com/filamentgroup/loadCSS
45
49
  css: (href, before, media, attributes) => {
50
+ // 如果样式已加载,直接返回 null
51
+ if (utils._loadedStyles.has(href)) {
52
+ return null;
53
+ }
46
54
  var doc = window.document;
47
55
  var ss = doc.createElement("link");
48
56
  var ref;
@@ -63,6 +71,8 @@
63
71
  ss.rel = "stylesheet";
64
72
  ss.href = href;
65
73
  ss.media = "only x";
74
+ // 标记样式为已加载 (在创建元素后立即标记,防止重复创建)
75
+ utils._loadedStyles.add(href);
66
76
  function ready(cb) {
67
77
  if (doc.body) {
68
78
  return cb();
@@ -100,11 +110,22 @@
100
110
  return ss;
101
111
  },
102
112
 
113
+ // 已加载脚本缓存
114
+ _loadedScripts: new Set(),
115
+
116
+ // 已加载元素缓存 (使用 WeakSet 追踪元素实例,避免 PJAX 导航时的状态混淆)
117
+ _loadedElements: new WeakSet(),
118
+
103
119
  js: (src, opt) => new Promise((resolve, reject) => {
104
- var script = document.createElement('script');
105
120
  if (src.startsWith('/')) {
106
121
  src = ctx.root + src.substring(1);
107
122
  }
123
+ // 如果脚本已加载,直接返回
124
+ if (utils._loadedScripts.has(src)) {
125
+ resolve();
126
+ return;
127
+ }
128
+ var script = document.createElement('script');
108
129
  script.src = src;
109
130
  if (opt) {
110
131
  for (let key of Object.keys(opt)) {
@@ -119,22 +140,42 @@
119
140
  const loadState = this.readyState
120
141
  if (loadState && loadState !== 'loaded' && loadState !== 'complete') return
121
142
  script.onload = script.onreadystatechange = null
143
+ utils._loadedScripts.add(src);
122
144
  resolve()
123
145
  }
124
146
  document.head.appendChild(script)
125
147
  }),
126
148
 
127
- jq: (fn) => {
149
+ // PJAX 事件监听器管理
150
+ _pjaxListeners: new Set(),
151
+
152
+ jq: (fn, pjax = true) => {
128
153
  if (typeof jQuery === 'undefined') {
129
154
  utils.js(deps.jquery).then(fn)
130
155
  } else {
131
156
  fn()
132
157
  }
158
+ if (pjax && !fn._pjax_registered) {
159
+ document.addEventListener('pjax:complete', fn);
160
+ fn._pjax_registered = true;
161
+ utils._pjaxListeners.add(fn); // 追踪监听器以便清理
162
+ }
163
+ },
164
+
165
+ // 清理所有 PJAX 事件监听器
166
+ cleanupPjaxListeners: () => {
167
+ utils._pjaxListeners.forEach(fn => {
168
+ document.removeEventListener('pjax:complete', fn);
169
+ fn._pjax_registered = false;
170
+ });
171
+ utils._pjaxListeners.clear();
133
172
  },
134
173
 
135
174
  onLoading: (el) => {
136
175
  if (el) {
137
- $(el).append('<div class="loading-wrap"><svg xmlns="http://www.w3.org/2000/svg" width="2em" height="2em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-width="2"><path stroke-dasharray="60" stroke-dashoffset="60" stroke-opacity=".3" d="M12 3C16.9706 3 21 7.02944 21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3Z"><animate fill="freeze" attributeName="stroke-dashoffset" dur="1.3s" values="60;0"/></path><path stroke-dasharray="15" stroke-dashoffset="15" d="M12 3C16.9706 3 21 7.02944 21 12"><animate fill="freeze" attributeName="stroke-dashoffset" dur="0.3s" values="15;0"/><animateTransform attributeName="transform" dur="1.5s" repeatCount="indefinite" type="rotate" values="0 12 12;360 12 12"/></path></g></svg></div>');
176
+ if ($(el).find('.loading-wrap').length === 0){
177
+ $(el).append('<div class="loading-wrap"><svg xmlns="http://www.w3.org/2000/svg" width="2em" height="2em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-width="2"><path stroke-dasharray="60" stroke-dashoffset="60" stroke-opacity=".3" d="M12 3C16.9706 3 21 7.02944 21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3Z"><animate fill="freeze" attributeName="stroke-dashoffset" dur="1.3s" values="60;0"/></path><path stroke-dasharray="15" stroke-dashoffset="15" d="M12 3C16.9706 3 21 7.02944 21 12"><animate fill="freeze" attributeName="stroke-dashoffset" dur="0.3s" values="15;0"/><animateTransform attributeName="transform" dur="1.5s" repeatCount="indefinite" type="rotate" values="0 12 12;360 12 12"/></path></g></svg></div>');
178
+ }
138
179
  }
139
180
  },
140
181
  onLoadSuccess: (el) => {
@@ -150,6 +191,10 @@
150
191
  }
151
192
  },
152
193
  request: (el, url, callback, onFailure) => {
194
+ // 检查元素实例是否已加载 (而不是检查属性)
195
+ if (el && utils._loadedElements.has(el)) {
196
+ return;
197
+ }
153
198
  const maxRetry = 3;
154
199
  let retryCount = 0;
155
200
 
@@ -179,6 +224,8 @@
179
224
  return resp;
180
225
  }).then(data => {
181
226
  if (timedOut) return;
227
+ // 标记元素实例为已加载
228
+ if (el) utils._loadedElements.add(el);
182
229
  utils.onLoadSuccess?.(el);
183
230
  callback(data);
184
231
  resolve(data);
@@ -237,8 +284,100 @@
237
284
  window.requestAnimationFrame(fn)
238
285
  },
239
286
  dark: {},
287
+
288
+ // 插件初始化管理器 - 统一处理 DOMContentLoaded 和 pjax:complete 事件
289
+ _pluginInitializers: [],
290
+ _pluginCleanups: new Map(), // 存储每个插件的清理函数
291
+
292
+ initPlugin: (initFn, name, options = {}) => {
293
+ if (!initFn || typeof initFn !== 'function') return;
294
+
295
+ // 避免重复注册
296
+ const pluginName = name || initFn.name;
297
+ if (utils._pluginInitializers.some(p => p.name === pluginName)) return;
298
+
299
+ // 包装初始化函数,添加防重复执行的保护
300
+ const wrappedInit = () => {
301
+ try {
302
+ // 在执行前先清理旧的资源
303
+ if (utils._pluginCleanups.has(pluginName)) {
304
+ const cleanup = utils._pluginCleanups.get(pluginName);
305
+ if (typeof cleanup === 'function') {
306
+ cleanup();
307
+ }
308
+ }
309
+
310
+ // 执行初始化,可能返回清理函数
311
+ const cleanup = initFn();
312
+
313
+ // 如果初始化函数返回了清理函数,保存它
314
+ if (typeof cleanup === 'function') {
315
+ utils._pluginCleanups.set(pluginName, cleanup);
316
+ }
317
+ } catch (error) {
318
+ console.error(`[Plugin ${pluginName}] 初始化失败:`, error);
319
+ }
320
+ };
321
+
322
+ utils._pluginInitializers.push({
323
+ fn: wrappedInit,
324
+ name: pluginName,
325
+ options: options
326
+ });
327
+
328
+ // 如果 DOM 已经加载完成,立即执行
329
+ if (document.readyState === 'complete' || document.readyState === 'interactive') {
330
+ wrappedInit();
331
+ } else {
332
+ // 否则等待 DOMContentLoaded
333
+ window.addEventListener('DOMContentLoaded', wrappedInit, { once: true });
334
+ }
335
+ },
336
+
337
+ // 重新初始化所有插件(用于 PJAX 等场景)
338
+ reinitPlugins: () => {
339
+ utils._pluginInitializers.forEach(plugin => {
340
+ // 跳过标记为不需要重新初始化的插件
341
+ if (plugin.options.skipReinit) return;
342
+
343
+ try {
344
+ plugin.fn();
345
+ } catch (error) {
346
+ console.error(`[Plugin ${plugin.name}] 重新初始化失败:`, error);
347
+ }
348
+ });
349
+ },
350
+
351
+ // 清理所有插件资源
352
+ cleanupPlugins: () => {
353
+ utils._pluginCleanups.forEach((cleanup, name) => {
354
+ try {
355
+ cleanup();
356
+ } catch (error) {
357
+ console.error(`[Plugin ${name}] 清理失败:`, error);
358
+ }
359
+ });
360
+ utils._pluginCleanups.clear();
361
+ },
362
+
363
+ // 清理所有资源(用于页面卸载或重置)
364
+ cleanupAll: () => {
365
+ utils.cleanupPlugins();
366
+ utils.cleanupPjaxListeners();
367
+ },
240
368
  };
241
369
 
370
+ // 自动处理 PJAX 事件
371
+ document.addEventListener('pjax:before', () => {
372
+ // 在导航前清理旧的 PJAX 监听器(如果需要重新注册)
373
+ // 注意:这里不清理,因为监听器应该是持久的
374
+ // utils.cleanupPjaxListeners();
375
+ });
376
+
377
+ document.addEventListener('pjax:complete', () => {
378
+ utils.reinitPlugins();
379
+ });
380
+
242
381
  // utils.dark.mode 当前模式 dark or light
243
382
  // utils.dark.toggle() 暗黑模式触发器
244
383
  // utils.dark.push(callBack[,"callBackName"]) 传入触发器回调函数
@@ -151,6 +151,9 @@ function layoutLogo() {
151
151
 
152
152
  function layoutNavArea() {
153
153
  var el = ''
154
+ if (page.menu == false) {
155
+ return el
156
+ }
154
157
  el += `<div class="nav-area">`
155
158
  el += partial('menu', {where: 'sidebar'})
156
159
  el += `</div>`
@@ -13,6 +13,9 @@ if (item.placeholder == null && item.filter == 'auto') {
13
13
  }
14
14
  function layoutDiv() {
15
15
  var el = ''
16
+ if (page.search == false) {
17
+ return el
18
+ }
16
19
  el += `<div class="search-wrapper" id="search-wrapper">`
17
20
  el += `<form class="search-form">`
18
21
  var filter = ''
@@ -21,8 +21,20 @@ function layoutDiv() {
21
21
  if (item.username == undefined || item.username.length == 0) {
22
22
  return el;
23
23
  }
24
+ if (item.follow == null) {
25
+ item.follow = true;
26
+ }
24
27
 
25
28
  el += `<widget class="widget-wrapper${scrollreveal(' ')} user-card ghuser">`
29
+ // header
30
+ if (item.header) {
31
+ el += `<div class="widget-header">`
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>
34
+ <span>github/${item.username}</span>
35
+ </a>`
36
+ el += `</div>`
37
+ }
26
38
  // body
27
39
  el += '<div class="widget-body data-service ds-ghinfo" data-api="https://' + theme.api_host.ghapi + '/users/' + item.username + '">';
28
40
  if (item.avatar) {
@@ -41,10 +53,12 @@ function layoutDiv() {
41
53
  });
42
54
  el += '</div>';
43
55
  // follow
44
- el += '<a class="follow" href="https://github.com/' + item.username + '">';
45
- el += icon('github:logo')
46
- el += 'Follow';
47
- el += '</a>';
56
+ if (item.follow) {
57
+ el += '<a class="follow" href="https://github.com/' + item.username + '">';
58
+ el += icon('github:logo')
59
+ el += 'Follow';
60
+ el += '</a>';
61
+ }
48
62
  el += '</div>';
49
63
  el += '</widget>';
50
64
  return el;
@@ -1,7 +1,7 @@
1
1
  <%
2
2
  function layoutDiv() {
3
3
  var el = '';
4
- el += `<widget class="widget-wrapper${scrollreveal(' ')} linklist">`
4
+ el += `<widget class="widget-wrapper linklist">`
5
5
  if (item.title?.length > 0) {
6
6
  el += '<div class="widget-header dis-select">';
7
7
  el += '<span class="name">' + item.title + '</span>';
@@ -1,33 +1,36 @@
1
- <%
2
- function layoutDiv() {
3
- var el = '';
4
- if (item.api == undefined) {
5
- return el;
6
- }
7
- el += `<widget class="widget-wrapper${scrollreveal(' ')} timeline">`
8
- if (item.title) {
9
- el += '<div class="widget-header dis-select">';
10
- el += '<span class="name">' + item.title + '</span>';
11
- el += '</div>';
12
- }
13
- el += '<div class="widget-body fs14">';
14
- el += `<div class="tag-plugin timeline data-service ds-${(item.type || 'timeline')}"`;
15
- // dataset
16
- ['api'].forEach(key => {
17
- if (item[key]) {
18
- el += ' data-' + key + '="' + item[key] + '"';
19
- }
20
- });
21
- ['user', 'hide', 'limit'].forEach(key => {
22
- if (item[key]) {
23
- el += ' ' + key + '="' + item[key] + '"';
24
- }
25
- });
26
- el += '>';
27
- el += '</div>';
28
- el += '</div>';
29
- el += '</widget>';
30
- return el;
31
- }
32
- %>
33
- <%- layoutDiv() %>
1
+ <%
2
+ function layoutDiv() {
3
+ var el = '';
4
+ if (item.api == undefined) {
5
+ return el;
6
+ }
7
+ el += `<widget class="widget-wrapper${scrollreveal(' ')} timeline">`
8
+ if (item.title) {
9
+ el += '<div class="widget-header dis-select">';
10
+ el += '<span class="name">' + item.title + '</span>';
11
+ el += '</div>';
12
+ }
13
+ el += '<div class="widget-body fs14">';
14
+ el += `<div class="tag-plugin timeline data-service ds-${(item.type || 'timeline')}"`;
15
+ // 动态遍历所有属性
16
+ const excludeKeys = ['title', 'type']; // 已用于其他用途的属性
17
+ const dataKeys = ['api']; // 需要添加 data- 前缀的属性
18
+
19
+ Object.keys(item).forEach(key => {
20
+ if (excludeKeys.includes(key) || !item[key]) {
21
+ return;
22
+ }
23
+ if (dataKeys.includes(key)) {
24
+ el += ' data-' + key + '="' + item[key] + '"';
25
+ } else {
26
+ el += ' ' + key + '="' + item[key] + '"';
27
+ }
28
+ });
29
+ el += '>';
30
+ el += '</div>';
31
+ el += '</div>';
32
+ el += '</widget>';
33
+ return el;
34
+ }
35
+ %>
36
+ <%- layoutDiv() %>
@@ -1,13 +1,17 @@
1
1
  <script>
2
- document.addEventListener('DOMContentLoaded', function () {
3
- window.codeElements = document.querySelectorAll('.code');
4
- if (window.codeElements.length > 0) {
2
+ utils.initPlugin(() => {
3
+ const codeElements = document.querySelectorAll('.code');
4
+ if (codeElements.length > 0) {
5
5
  ctx.copycode = {
6
6
  default_text: `<%- conf.default_text %>`,
7
7
  success_text: `<%- conf.success_text %>`,
8
8
  toast: `<%- conf.toast %>`,
9
9
  };
10
- utils.js('/js/plugins/copycode.js');
10
+ utils.js('/js/plugins/copycode.js', { defer: true }).then(() => {
11
+ if (typeof createCopyButtons === 'function') {
12
+ createCopyButtons(codeElements);
13
+ }
14
+ });
11
15
  }
12
- });
16
+ }, 'copycode');
13
17
  </script>
@@ -1,20 +1,49 @@
1
- <script type="text/javascript" src="<%- conf.js %>"></script>
1
+ <% if (conf.v3) { %>
2
+ <script>
3
+ window.MathJax = {
4
+ tex: {
5
+ inlineMath: [
6
+ ["$", "$"],
7
+ ["\\(", "\\)"],
8
+ ],
9
+ processEscapes: true,
10
+ skipTags: ["script", "noscript", "style", "textarea", "pre", "code"],
11
+ },
12
+ startup: {
13
+ ready() {
14
+ MathJax.startup.defaultReady();
15
+ MathJax.typesetPromise().then(() => {
16
+ const math = document.querySelectorAll("mjx-container");
17
+ math.forEach((element) => {
18
+ if (element.parentNode) {
19
+ element.parentNode.classList.add("has-jax");
20
+ }
21
+ });
22
+ });
23
+ },
24
+ },
25
+ };
26
+ </script>
27
+ <% } %>
28
+ <script id="MathJax-script" src="<%- conf.js %>" async></script>
29
+ <% if (!conf.v3) { %>
2
30
  <script type="text/x-mathjax-config">
3
- MathJax.Hub.Config({
4
- tex2jax: {
5
- inlineMath: [ ['$','$'], ["\\(","\\)"] ],
6
- processEscapes: true
7
- }
8
- });
9
- MathJax.Hub.Config({
10
- tex2jax: {
11
- skipTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code']
12
- }
13
- });
14
- MathJax.Hub.Queue(function() {
15
- var all = MathJax.Hub.getAllJax(), i;
16
- for(i=0; i < all.length; i += 1) {
17
- all[i].SourceElement().parentNode.className += ' has-jax';
18
- }
19
- });
31
+ MathJax.Hub.Config({
32
+ tex2jax: {
33
+ inlineMath: [ ['$','$'], ["\\(","\\)"] ],
34
+ processEscapes: true
35
+ }
36
+ });
37
+ MathJax.Hub.Config({
38
+ tex2jax: {
39
+ skipTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code']
40
+ }
41
+ });
42
+ MathJax.Hub.Queue(function() {
43
+ var all = MathJax.Hub.getAllJax(), i;
44
+ for(i=0; i < all.length; i += 1) {
45
+ all[i].SourceElement().parentNode.className += ' has-jax';
46
+ }
47
+ });
20
48
  </script>
49
+ <% } %>