hexo-theme-stellar 1.33.1 → 1.35.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 (82) hide show
  1. package/README.md +2 -0
  2. package/_config.yml +19 -14
  3. package/layout/_partial/comments/artalk/script.ejs +56 -41
  4. package/layout/_partial/comments/beaudar/script.ejs +35 -20
  5. package/layout/_partial/comments/giscus/script.ejs +34 -19
  6. package/layout/_partial/comments/twikoo/script.ejs +29 -14
  7. package/layout/_partial/comments/utterances/script.ejs +35 -20
  8. package/layout/_partial/comments/waline/script.ejs +40 -28
  9. package/layout/_partial/cover/wiki_cover.ejs +1 -1
  10. package/layout/_partial/head.ejs +53 -7
  11. package/layout/_partial/scripts/defines.ejs +21 -3
  12. package/layout/_partial/scripts/lazyload.ejs +2 -2
  13. package/layout/_partial/scripts/services.ejs +101 -41
  14. package/layout/_partial/scripts/utils.ejs +100 -4
  15. package/layout/_partial/sidebar/index_leftbar.ejs +3 -0
  16. package/layout/_partial/sidebar/search.ejs +3 -0
  17. package/layout/_partial/widgets/ghuser.ejs +18 -4
  18. package/layout/_partial/widgets/linklist.ejs +1 -1
  19. package/layout/_partial/widgets/timeline.ejs +36 -33
  20. package/layout/_plugins/copycode.ejs +9 -5
  21. package/layout/_plugins/mermaid.ejs +28 -24
  22. package/layout/_plugins/scrollreveal.ejs +14 -13
  23. package/layout/_plugins/swiper.ejs +2 -2
  24. package/layout/index.ejs +4 -2
  25. package/layout/index_topic.ejs +4 -2
  26. package/layout/index_wiki.ejs +6 -4
  27. package/layout/layout.ejs +2 -2
  28. package/package.json +5 -2
  29. package/scripts/commands/new-note.js +49 -0
  30. package/scripts/events/index.js +2 -1
  31. package/scripts/events/lib/doc_tree.js +7 -4
  32. package/scripts/events/lib/merge_posts.js +3 -1
  33. package/scripts/events/lib/notebooks.js +5 -3
  34. package/scripts/events/lib/path_normalize.js +22 -0
  35. package/scripts/filters/lib/img_onerror.js +0 -2
  36. package/scripts/generators/search.js +3 -1
  37. package/scripts/helpers/json_ld.js +14 -10
  38. package/scripts/helpers/pretty_url.js +12 -8
  39. package/scripts/lib/path_utils.js +24 -0
  40. package/scripts/tags/lib/chat.js +16 -9
  41. package/scripts/tags/lib/frame.js +2 -1
  42. package/scripts/tags/lib/image.js +6 -5
  43. package/scripts/tags/lib/link.js +6 -4
  44. package/scripts/tags/lib/timeline.js +101 -79
  45. package/source/css/_common/canonical.styl +11 -6
  46. package/source/css/_common/highlight.styl +4 -1
  47. package/source/css/_common/html.styl +4 -0
  48. package/source/css/_common/loading.styl +5 -1
  49. package/source/css/_components/md.styl +1 -0
  50. package/source/css/_components/pages/article-story.styl +2 -0
  51. package/source/css/_components/partial/article-footer.styl +1 -0
  52. package/source/css/_components/partial/navbar.styl +2 -0
  53. package/source/css/_components/sidebar/menu.styl +1 -0
  54. package/source/css/_components/sidebar/search.styl +4 -0
  55. package/source/css/_components/sidebar/sidebar.styl +12 -5
  56. package/source/css/_components/tag-plugins/chat.styl +6 -8
  57. package/source/css/_components/tag-plugins/folding.styl +1 -0
  58. package/source/css/_components/tag-plugins/gallery.styl +2 -0
  59. package/source/css/_components/tag-plugins/grid.styl +1 -0
  60. package/source/css/_components/tag-plugins/image.styl +3 -0
  61. package/source/css/_components/tag-plugins/link.styl +1 -0
  62. package/source/css/_components/tag-plugins/navbar.styl +15 -13
  63. package/source/css/_components/tag-plugins/note.styl +1 -0
  64. package/source/css/_components/tag-plugins/okr.styl +1 -0
  65. package/source/css/_components/tag-plugins/sites.styl +2 -0
  66. package/source/css/_components/tag-plugins/timeline.styl +9 -0
  67. package/source/css/_components/tag-plugins/vote.styl +2 -0
  68. package/source/css/_components/widgets/components.styl +1 -0
  69. package/source/css/_components/widgets/ghuser.styl +19 -3
  70. package/source/css/_components/widgets/list.styl +1 -0
  71. package/source/css/_components/widgets/toc.styl +3 -0
  72. package/source/css/_components/widgets/widgets.styl +4 -0
  73. package/source/css/_custom.styl +2 -1
  74. package/source/css/_defines/func.styl +8 -0
  75. package/source/css/_plugins/comments/artalk.styl +6 -2
  76. package/source/css/_plugins/comments/twikoo.styl +2 -0
  77. package/source/css/_plugins/index.styl +0 -1
  78. package/source/js/main.js +76 -26
  79. package/source/js/plugins/copycode.js +34 -27
  80. package/source/js/search/local-search.js +5 -2
  81. package/source/js/services/memos.js +42 -4
  82. package/source/js/services/rss.js +200 -0
@@ -30,7 +30,7 @@
30
30
  container = document.querySelector(container);
31
31
  }
32
32
  if (!container) return;
33
-
33
+
34
34
  const images = container.querySelectorAll('img');
35
35
  images.forEach((img) => {
36
36
  if (img.classList.contains('lazy')) return;
@@ -63,5 +63,5 @@
63
63
  window.lazyLoadInstance.update();
64
64
  }
65
65
  }
66
-
66
+
67
67
  </script>
@@ -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,6 +140,7 @@
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)
@@ -134,7 +156,9 @@
134
156
 
135
157
  onLoading: (el) => {
136
158
  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>');
159
+ if ($(el).find('.loading-wrap').length === 0){
160
+ $(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>');
161
+ }
138
162
  }
139
163
  },
140
164
  onLoadSuccess: (el) => {
@@ -150,6 +174,10 @@
150
174
  }
151
175
  },
152
176
  request: (el, url, callback, onFailure) => {
177
+ // 检查元素实例是否已加载 (而不是检查属性)
178
+ if (el && utils._loadedElements.has(el)) {
179
+ return;
180
+ }
153
181
  const maxRetry = 3;
154
182
  let retryCount = 0;
155
183
 
@@ -179,6 +207,8 @@
179
207
  return resp;
180
208
  }).then(data => {
181
209
  if (timedOut) return;
210
+ // 标记元素实例为已加载
211
+ if (el) utils._loadedElements.add(el);
182
212
  utils.onLoadSuccess?.(el);
183
213
  callback(data);
184
214
  resolve(data);
@@ -237,6 +267,72 @@
237
267
  window.requestAnimationFrame(fn)
238
268
  },
239
269
  dark: {},
270
+
271
+ // 插件初始化管理器 - 统一处理 DOMContentLoaded 和 pjax:complete 事件
272
+ _pluginInitializers: [],
273
+ _pluginCleanups: new Map(), // 存储每个插件的清理函数
274
+
275
+ initPlugin: (initFn, name, options = {}) => {
276
+ if (!initFn || typeof initFn !== 'function') return;
277
+
278
+ // 避免重复注册
279
+ const pluginName = name || initFn.name;
280
+ if (utils._pluginInitializers.some(p => p.name === pluginName)) return;
281
+
282
+ // 包装初始化函数,添加防重复执行的保护
283
+ const wrappedInit = () => {
284
+ try {
285
+ // 在执行前先清理旧的资源
286
+ if (utils._pluginCleanups.has(pluginName)) {
287
+ const cleanup = utils._pluginCleanups.get(pluginName);
288
+ if (typeof cleanup === 'function') {
289
+ cleanup();
290
+ }
291
+ }
292
+
293
+ // 执行初始化,可能返回清理函数
294
+ const cleanup = initFn();
295
+
296
+ // 如果初始化函数返回了清理函数,保存它
297
+ if (typeof cleanup === 'function') {
298
+ utils._pluginCleanups.set(pluginName, cleanup);
299
+ }
300
+ } catch (error) {
301
+ console.error(`[Plugin ${pluginName}] 初始化失败:`, error);
302
+ }
303
+ };
304
+
305
+ utils._pluginInitializers.push({
306
+ fn: wrappedInit,
307
+ name: pluginName,
308
+ options: options
309
+ });
310
+
311
+ // 如果 DOM 已经加载完成,立即执行
312
+ if (document.readyState === 'complete' || document.readyState === 'interactive') {
313
+ wrappedInit();
314
+ } else {
315
+ // 否则等待 DOMContentLoaded
316
+ window.addEventListener('DOMContentLoaded', wrappedInit, { once: true });
317
+ }
318
+ },
319
+
320
+ // 清理所有插件资源
321
+ cleanupPlugins: () => {
322
+ utils._pluginCleanups.forEach((cleanup, name) => {
323
+ try {
324
+ cleanup();
325
+ } catch (error) {
326
+ console.error(`[Plugin ${name}] 清理失败:`, error);
327
+ }
328
+ });
329
+ utils._pluginCleanups.clear();
330
+ },
331
+
332
+ // 清理所有资源(用于页面卸载或重置)
333
+ cleanupAll: () => {
334
+ utils.cleanupPlugins();
335
+ },
240
336
  };
241
337
 
242
338
  // utils.dark.mode 当前模式 dark or light
@@ -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,29 +1,33 @@
1
1
  <script defer type="text/javascript" src="<%- conf.js %>"></script>
2
2
  <script>
3
- window.addEventListener('DOMContentLoaded', (event) => {
4
- var mermaid_config = {
5
- startOnLoad: true,
6
- theme:
7
- "<%- theme.style.prefers_theme %>" == "auto" &&
8
- window.matchMedia("(prefers-color-scheme: dark)").matches
9
- ? "dark"
10
- : "<%- conf.theme %>",
11
- logLevel: 3,
12
- themeVariables: {
13
- darkMode: true
14
- },
15
- flowchart: {
16
- useMaxWidth: false,
17
- htmlLabels: true,
18
- curve: "linear"
19
- },
20
- gantt: {
21
- axisFormat: "%Y/%m/%d"
22
- },
23
- sequence: {
24
- actorMargin: 50
3
+ utils.initPlugin(() => {
4
+ const els = document.querySelectorAll('.mermaid');
5
+ if (els.length > 0) {
6
+ const mermaid_config = {
7
+ startOnLoad: false,
8
+ theme:
9
+ "<%- theme.style.prefers_theme %>" == "auto" &&
10
+ window.matchMedia("(prefers-color-scheme: dark)").matches
11
+ ? "dark"
12
+ : "<%- conf.theme %>",
13
+ logLevel: 3,
14
+ themeVariables: {
15
+ darkMode: true
16
+ },
17
+ flowchart: {
18
+ useMaxWidth: false,
19
+ htmlLabels: true,
20
+ curve: "linear"
21
+ },
22
+ gantt: {
23
+ axisFormat: "%Y/%m/%d"
24
+ },
25
+ sequence: {
26
+ actorMargin: 50
27
+ }
25
28
  }
29
+ mermaid.initialize(mermaid_config);
30
+ mermaid.run();
26
31
  }
27
- mermaid.initialize(mermaid_config);
28
- });
32
+ }, 'mermaid');
29
33
  </script>
@@ -1,16 +1,17 @@
1
1
  <script defer src="<%- conf.js %>"></script>
2
2
  <script>
3
- window.addEventListener('DOMContentLoaded', (event) => {
4
- const slideUp = {
5
- distance: `<%- conf.distance %>`,
6
- duration: `<%- conf.duration %>`,
7
- interval: `<%- conf.interval %>`,
8
- scale: `<%- conf.scale %>`,
9
- opacity: 0,
10
- easing: "ease-out"
11
- };
12
- ScrollReveal({ container: '.widgets' }).reveal('.slide-up', { ...slideUp });
13
- ScrollReveal({ container: '.l_main' }).reveal('.slide-up', { ...slideUp });
14
- ScrollReveal({ container: '.l_right' }).reveal('.slide-up', { ...slideUp });
15
- });
3
+ utils.initPlugin(() => {
4
+ const els = document.querySelectorAll('.slide-up');
5
+ if (els.length > 0) {
6
+ const slideUp = {
7
+ distance: `<%- conf.distance %>`,
8
+ duration: `<%- conf.duration %>`,
9
+ interval: `<%- conf.interval %>`,
10
+ scale: `<%- conf.scale %>`,
11
+ opacity: 0,
12
+ easing: "ease-out"
13
+ };
14
+ ScrollReveal().reveal('.slide-up', { ...slideUp });
15
+ }
16
+ }, 'scrollreveal');
16
17
  </script>
@@ -1,5 +1,5 @@
1
1
  <script>
2
- window.addEventListener('DOMContentLoaded', (event) => {
2
+ utils.initPlugin(() => {
3
3
  const swiper_api = document.getElementById('swiper-api');
4
4
  if (swiper_api != undefined) {
5
5
  utils.css(`<%- conf.css %>`);
@@ -22,5 +22,5 @@
22
22
  });
23
23
  })
24
24
  }
25
- });
25
+ }, 'swiper');
26
26
  </script>