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
@@ -4,6 +4,7 @@
4
4
  padding: 6px 16px
5
5
  display: block
6
6
  border-radius: $border-bar
7
+ corner-shape: superellipse(1.2)
7
8
  color: var(--text-p1)
8
9
  trans1 background
9
10
  &.active,&:hover
@@ -16,6 +16,9 @@
16
16
  @media screen and (min-width: $device-laptop)
17
17
  background: var(--background)
18
18
 
19
+ .l_left .widget-wrapper.toc
20
+ @media screen and (min-width: $device-laptop)
21
+ background: none
19
22
 
20
23
  // 目录基本样式
21
24
  .widget-wrapper.toc .toc
@@ -77,3 +77,7 @@
77
77
  @media screen and (min-width: $device-laptop)
78
78
  .widget-wrapper.markdown .widget-body
79
79
  background: var(--block)
80
+
81
+ .leftbar-container
82
+ .header+.widgets
83
+ margin-top: 1rem
@@ -31,7 +31,8 @@ $fsp3 = 'calc(%s - 3px)' % $fs-body
31
31
  // site main
32
32
  $site-background-image = hexo-config('style.site.background-image')
33
33
  $leftbar-background-image = hexo-config('style.leftbar.background-image')
34
- $leftbar-background-color = hexo-config('style.leftbar.background-color')
34
+ $leftbar-background-color-light = hexo-config('style.leftbar.background-color-light')
35
+ $leftbar-background-color-dark = hexo-config('style.leftbar.background-color-dark')
35
36
 
36
37
 
37
38
  // colors
@@ -137,10 +137,14 @@ newblur()
137
137
  box-shadow: 0 0 2px rgba(black, .04), 0 2px 8px rgba(black, .04), 0 4px 16px rgba(black, .04)
138
138
  trans1: all
139
139
  text-shadow: 0 0 4px rgba(white, 1), 0 0 8px rgba(white, 0.8), 0 0 16px rgba(white, 0.6)
140
+ :root[data-theme="dark"] &
141
+ text-shadow: 0 0 2px rgba(black, 0.2), 0 0 4px rgba(black, 0.4)
140
142
  @media (prefers-color-scheme: dark)
141
143
  text-shadow: 0 0 2px rgba(black, 0.2), 0 0 4px rgba(black, 0.4)
142
144
  nav a:not(.active,:hover)
143
145
  filter: drop-shadow(0 0 0 white) drop-shadow(0 0 4px white)
146
+ :root[data-theme="dark"] &
147
+ filter: none
144
148
  @media (prefers-color-scheme: dark)
145
149
  filter: none
146
150
  &:before,&:after
@@ -158,6 +162,8 @@ newblur()
158
162
  -webkit-backdrop-filter: blur(8px)
159
163
  background: rgba(white, 0.3)
160
164
  margin: 1px
165
+ :root[data-theme="dark"] &
166
+ background: rgba(white, 0.2)
161
167
  @media (prefers-color-scheme: dark)
162
168
  background: rgba(white, 0.2)
163
169
  &:after
@@ -169,5 +175,7 @@ newblur()
169
175
  -webkit-mask-image: linear-gradient(white, 20%, transparent, 80%, white), linear-gradient(90deg, white, 20%, transparent, 80%, white)
170
176
  box-shadow: inset 0 0 4px 1px rgba(white, 0.5)
171
177
  trans1 all
178
+ :root[data-theme="dark"] &
179
+ box-shadow: inset 0 0 2px 1px rgba(white, 0.2)
172
180
  @media (prefers-color-scheme: dark)
173
181
  box-shadow: inset 0 0 2px 1px rgba(white, 0.2)
@@ -158,6 +158,7 @@
158
158
  >img
159
159
  margin: var(--gap-p) auto
160
160
  border-radius: $border-image-s
161
+ corner-shape: superellipse(1.2)
161
162
  a>img
162
163
  display: inline
163
164
  img[atk-emoticon]
@@ -175,5 +176,8 @@
175
176
  padding: 0.25rem 1rem
176
177
  p
177
178
  color: var(--text-p3)
178
- .atk-content a
179
- --at-color-main: var(--link)
179
+ .atk-content
180
+ a
181
+ --at-color-main: var(--link)
182
+ pre>code
183
+ padding: 0
@@ -6,6 +6,7 @@
6
6
  .vemoji, .tk-owo-emotion
7
7
  width: unset
8
8
  border-radius: $border-image-s
9
+ corner-shape: superellipse(1.2)
9
10
  max-height: 24px
10
11
  max-width: 100px
11
12
  display: inline
@@ -14,6 +15,7 @@
14
15
  max-width: 400px
15
16
  max-height: 400px
16
17
  border-radius: $border-image-s
18
+ corner-shape: superellipse(1.2)
17
19
 
18
20
  .OwO
19
21
  .OwO-body
@@ -14,7 +14,6 @@ if hexo-config('plugins.copycode.enable')
14
14
  @import 'copycode'
15
15
  if hexo-config('plugins.tianli_gpt.enable')
16
16
  @import 'tianli_gpt'
17
-
18
17
  if hexo-config('plugins.katex.enable')
19
18
  @import 'katex'
20
19
 
package/source/js/main.js CHANGED
@@ -37,10 +37,11 @@ const util = {
37
37
  const el = document.getElementById(id);
38
38
  if (el) {
39
39
  el.select();
40
- document.execCommand("Copy");
41
- if (msg && msg.length > 0) {
42
- hud.toast(msg, 2500);
43
- }
40
+ navigator.clipboard.writeText(el.value).then(() => {
41
+ if (msg && msg.length > 0) {
42
+ hud.toast(msg, 2500);
43
+ }
44
+ }).catch(() => {});
44
45
  }
45
46
  },
46
47
 
@@ -52,11 +53,11 @@ const util = {
52
53
  },
53
54
 
54
55
  scrollTop: () => {
55
- window.scrollTo({top: 0, behavior: "smooth"});
56
+ window.scrollTo({ top: 0, behavior: "smooth" });
56
57
  },
57
58
 
58
59
  scrollComment: () => {
59
- document.getElementById('comments').scrollIntoView({behavior: "smooth"});
60
+ document.getElementById('comments').scrollIntoView({ behavior: "smooth" });
60
61
  },
61
62
 
62
63
  viewportLazyload: (target, func, enabled = true) => {
@@ -83,8 +84,8 @@ const hud = {
83
84
  el.innerHTML = msg;
84
85
  document.body.appendChild(el);
85
86
 
86
- setTimeout(function(){ document.body.removeChild(el) }, d);
87
-
87
+ setTimeout(function () { document.body.removeChild(el) }, d);
88
+
88
89
  },
89
90
 
90
91
  }
@@ -138,20 +139,20 @@ const init = {
138
139
  const offsetBottom = e1.getBoundingClientRect().bottom - e0.getBoundingClientRect().bottom + 100;
139
140
  const offsetTop = e1.getBoundingClientRect().top - e0.getBoundingClientRect().top - 64;
140
141
  if (offsetTop < 0) {
141
- e0.scrollBy({top: offsetTop, behavior: "smooth"});
142
+ e0.scrollBy({ top: offsetTop, behavior: "smooth" });
142
143
  } else if (offsetBottom > 0) {
143
- e0.scrollBy({top: offsetBottom, behavior: "smooth"});
144
+ e0.scrollBy({ top: offsetBottom, behavior: "smooth" });
144
145
  }
145
146
  }
146
-
147
+
147
148
  var timeout = null;
148
- window.addEventListener('scroll', function() {
149
+ window.addEventListener('scroll', function () {
149
150
  activeTOC();
150
- if(timeout !== null) clearTimeout(timeout);
151
- timeout = setTimeout(function() {
151
+ if (timeout !== null) clearTimeout(timeout);
152
+ timeout = setTimeout(function () {
152
153
  scrollTOC();
153
154
  }.bind(this), 50);
154
- });
155
+ });
155
156
  })
156
157
  },
157
158
  sidebar: () => {
@@ -202,24 +203,44 @@ const init = {
202
203
 
203
204
  canonicalCheck: () => {
204
205
  const canonical = window.canonical;
205
- function showTip(isOfficial = false) {
206
+ function originStatusCheck() {
207
+ return new Promise((resolve) => {
208
+ if (window.canonical.originalHost === window.location.hostname) {
209
+ resolve(true);
210
+ return;
211
+ }
212
+ const scriptUrl = `https://${window.canonical.originalHost}${window.canonical.param.checklink}`;
213
+ const script = document.createElement('script');
214
+ script.src = scriptUrl;
215
+ script.type = 'text/javascript';
216
+ script.onload = function () { resolve(true); };
217
+ script.onerror = function () { resolve(false); };
218
+ document.head.appendChild(script);
219
+ });
220
+ }
221
+ async function showTip(isOfficial = false) {
206
222
  const meta = document.createElement('meta');
207
223
  meta.name = 'robots';
208
224
  meta.content = 'noindex, nofollow';
209
225
  document.head.appendChild(meta);
210
226
  const notice = document.createElement('div');
211
227
  const originalURL = `https://${canonical.originalHost}`;
228
+ const currentURL = canonical.param.permalink.startsWith("http") ? canonical.param.permalink : originalURL;
212
229
  if (isOfficial) {
230
+ const closeEnable = window.localStorage.getItem('Stellar.canonical.closeEnable') === 'true'
231
+ const closedToday = window.localStorage.getItem('Stellar.canonical.closeTime') === new Date().toDateString()
232
+ if ((closeEnable && closedToday) || !(await originStatusCheck())) return;
213
233
  notice.className = 'canonical-tip official';
214
234
  notice.innerHTML = `
215
- <a href="${originalURL}" target="_self" rel="noopener noreferrer">
216
- 本站为官方备用站,仅供应急。主站:${originalURL}
217
- </a>
235
+ <a href="${currentURL}" target="_self" rel="noopener noreferrer">
236
+ 本站为官方备用站,仅供应急。点击移步主站<br>${originalURL}
237
+ </a>
238
+ ${canonical.closeEnable ? '<button id="canonical-close">' + canonical.closeText || '关闭提示' + '</button>' : ''}
218
239
  `;
219
240
  } else {
220
241
  notice.className = 'canonical-tip unofficial';
221
242
  notice.innerHTML = `
222
- <a href="${originalURL}" target="_self" rel="noopener noreferrer">
243
+ <a href="${currentURL}" target="_self" rel="noopener noreferrer">
223
244
  <div class="headline icon">☠️</div>
224
245
  本站为非法克隆站,请前往官方源站访问。<br>
225
246
  源站:${originalURL}
@@ -227,6 +248,14 @@ const init = {
227
248
  `;
228
249
  }
229
250
  document.body.appendChild(notice);
251
+ const closeBtn = notice.querySelector('#canonical-close');
252
+ if (closeBtn) {
253
+ closeBtn.addEventListener('click', function () {
254
+ window.localStorage.setItem('Stellar.canonical.closeEnable', "true")
255
+ window.localStorage.setItem('Stellar.canonical.closeTime', new Date().toDateString())
256
+ notice.style.display = 'none';
257
+ });
258
+ }
230
259
  }
231
260
  if (!canonical.originalHost) return;
232
261
  const currentURL = new URL(window.location.href);
@@ -259,9 +288,30 @@ const init = {
259
288
  }
260
289
 
261
290
 
262
- // init
263
- init.toc()
264
- init.sidebar()
265
- init.relativeDate(document.querySelectorAll('#post-meta time'))
266
- init.registerTabsTag()
267
- init.canonicalCheck()
291
+ // Stellar namespace
292
+ window.stellar = window.stellar || {};
293
+
294
+ /**
295
+ * Initialize page components
296
+ * Called on initial load and after PJAX navigation
297
+ */
298
+ stellar.initPage = function () {
299
+ init.toc();
300
+ init.sidebar();
301
+ init.relativeDate(document.querySelectorAll('#post-meta time'));
302
+ init.registerTabsTag();
303
+
304
+ // Reinitialize comments after PJAX navigation
305
+ if (stellar.initComments) {
306
+ for (const commentSystem in stellar.initComments) {
307
+ if (typeof stellar.initComments[commentSystem] === 'function') {
308
+ stellar.initComments[commentSystem]();
309
+ }
310
+ }
311
+ }
312
+ };
313
+
314
+ // Initial page load
315
+ stellar.initPage();
316
+ init.canonicalCheck();
317
+
@@ -1,32 +1,39 @@
1
- window.codeElements.forEach((codeElement) => {
2
- // 创建复制按钮
3
- const codeCopyBtn = document.createElement('div');
4
- codeCopyBtn.className = 'copy-btn';
5
- codeCopyBtn.textContent = ctx.copycode.default_text;
6
- codeElement.appendChild(codeCopyBtn);
1
+ function createCopyButtons(codeElements) {
2
+ codeElements.forEach((codeElement) => {
3
+ if (codeElement.querySelector('.copy-btn')) return;
4
+ // 创建复制按钮
5
+ const codeCopyBtn = document.createElement('div');
6
+ codeCopyBtn.className = 'copy-btn';
7
+ codeCopyBtn.textContent = ctx.copycode.default_text;
8
+ codeElement.appendChild(codeCopyBtn);
7
9
 
8
- // 添加点击事件监听
9
- codeCopyBtn.addEventListener('click', async () => {
10
- const codeToCopy = codeElement.querySelector('pre')?.innerText || '';
11
- if (navigator.clipboard) {
12
- try {
13
- await navigator.clipboard.writeText(codeToCopy);
14
- codeCopyBtn.textContent = ctx.copycode.success_text;
15
- codeCopyBtn.classList.add('success');
16
- hud.toast(ctx.copycode.toast, 2500);
17
- } catch (error) {
18
- codeCopyBtn.textContent = '未获得用户许可';
10
+ // 添加点击事件监听
11
+ codeCopyBtn.addEventListener('click', async () => {
12
+ const codeToCopy = codeElement.querySelector('pre')?.innerText || '';
13
+ if (navigator.clipboard) {
14
+ try {
15
+ await navigator.clipboard.writeText(codeToCopy);
16
+ codeCopyBtn.textContent = ctx.copycode.success_text;
17
+ codeCopyBtn.classList.add('success');
18
+ hud.toast(ctx.copycode.toast, 2500);
19
+ } catch (error) {
20
+ codeCopyBtn.textContent = '未获得用户许可';
21
+ codeCopyBtn.classList.add('warning');
22
+ }
23
+ } else {
24
+ codeCopyBtn.textContent = '浏览器不支持/非HTTPS';
19
25
  codeCopyBtn.classList.add('warning');
20
26
  }
21
- } else {
22
- codeCopyBtn.textContent = '浏览器不支持/非HTTPS';
23
- codeCopyBtn.classList.add('warning');
24
- }
25
27
 
26
- // 3秒后恢复默认文本
27
- setTimeout(() => {
28
- codeCopyBtn.textContent = ctx.copycode.default_text;
29
- codeCopyBtn.classList.remove('success', 'warning');
30
- }, 3000);
28
+ // 3秒后恢复默认文本
29
+ setTimeout(() => {
30
+ codeCopyBtn.textContent = ctx.copycode.default_text;
31
+ codeCopyBtn.classList.remove('success', 'warning');
32
+ }, 3000);
33
+ });
31
34
  });
32
- });
35
+ }
36
+
37
+ if (typeof window.codeElements !== 'undefined') {
38
+ createCopyButtons(window.codeElements);
39
+ }
@@ -2,6 +2,9 @@ var searchCache = null;
2
2
  var searchCacheKey = 'search_cache_v1';
3
3
 
4
4
  var searchFunc = function(path, filter, wrapperId, searchId, contentId) {
5
+ var $input = document.getElementById(searchId);
6
+ if (!$input || $input._searchInitialized === true) return;
7
+ if ($input._searchInitialized === 'pending' && !searchCache) return;
5
8
 
6
9
  function getAllCombinations(keywords) {
7
10
  const result = [];
@@ -19,9 +22,8 @@ var searchFunc = function(path, filter, wrapperId, searchId, contentId) {
19
22
  }
20
23
 
21
24
  function initSearch(datas) {
22
- var $input = document.getElementById(searchId);
23
25
  if (!$input) { return; }
24
- if ($input._searchInitialized) return; // 防止重复绑定
26
+ if ($input._searchInitialized === true) return; // 防止重复绑定
25
27
  $input._searchInitialized = true;
26
28
 
27
29
  var $resultContent = document.getElementById(contentId);
@@ -115,6 +117,7 @@ var searchFunc = function(path, filter, wrapperId, searchId, contentId) {
115
117
  }
116
118
 
117
119
  if (!searchCache) {
120
+ $input._searchInitialized = 'pending';
118
121
  // 数据还没准备好,延迟初始化
119
122
  const timer = setInterval(() => {
120
123
  if (searchCache) {
@@ -28,7 +28,7 @@ utils.jq(() => {
28
28
  <div class="header">${!users.length && !hide.includes('user') ? await versionHandler.buildUser(item, memos, default_avatar) : ''}
29
29
  <span>${versionHandler.buildDate(item).toLocaleString()}</span></div>
30
30
  <div class="body">${marked.parse(item.content || '')}
31
- <div class="tag-plugin image">${versionHandler.buildImages(item, host).join('')}</div>
31
+ <p>${versionHandler.buildImages(item, host).join('')}</p>
32
32
  </div></div>`;
33
33
  }
34
34
 
@@ -39,7 +39,7 @@ utils.jq(() => {
39
39
  `<div class="user-info">${default_avatar ? `<img src="${default_avatar}">` : ''}<span>${item.creatorName}</span></div>`,
40
40
  buildDate: item => new Date(item.createdTs * 1000),
41
41
  buildImages: (item, host) => (item.resourceList || []).filter(res => res.type?.includes('image/')).map(res =>
42
- `<div class="image-bg"><img src="${res.externalLink || `https://${host}/o/r/${res.id}`}"></div>`
42
+ `<p><img src="${res.externalLink || `https://${host}/o/r/${res.id}`}"></p>`
43
43
  )
44
44
  },
45
45
  "22+": {
@@ -69,7 +69,38 @@ utils.jq(() => {
69
69
  },
70
70
  buildDate: item => new Date(item.createTime),
71
71
  buildImages: (item) => (item.resources || []).filter(res => res.type?.includes('image/')).map(res =>
72
- `<div class="image-bg"><img src="${res.externalLink || `https://${host}/o/r/${res.id}`}"></div>`
72
+ `<p><img src="${res.externalLink || `https://${host}/o/r/${res.id}`}"></p>`
73
+ )
74
+ },
75
+ "25+": {
76
+ buildUser: async (item, memos, default_avatar) => {
77
+ const creatorId = item?.creator.split('/')[1];
78
+ let user = memos.users.find(user => user.name.split('/')[1] === creatorId);
79
+ if (!user) {
80
+ if (!memos.requests[creatorId]) {
81
+ memos.requests[creatorId] = fetch(`${memos.site}/api/v1/users/${creatorId}`)
82
+ .then(response => response.json())
83
+ .then(data => {
84
+ if (data.username) {
85
+ user = data;
86
+ memos.users.push(data);
87
+ } else {
88
+ user = null;
89
+ }
90
+ })
91
+ .finally(() => delete memos.requests[creatorId]);
92
+ }
93
+ await memos.requests[creatorId];
94
+ user = memos.users.find(user => user.name.split('/')[1] === creatorId);
95
+ }
96
+ console.log(JSON.stringify(user));
97
+ const name = user ? user.displayName || user.username : 'memos';
98
+ const avatarUrl = user?.avatarUrl ? `${memos.site}${user.avatarUrl}` : default_avatar || '';
99
+ return `<div class="user-info">${avatarUrl ? `<img src="${avatarUrl}">` : ''}<span>${name}</span></div>`;
100
+ },
101
+ buildDate: item => new Date(item.createTime),
102
+ buildImages: (item) => (item.attachments || []).filter(res => res.type?.includes('image/')).map(res =>
103
+ `<div class="image-bg"><img src="${res.externalLink || `https://${host}/file/${res.name}/${res.filename}`}"></div>`
73
104
  )
74
105
  },
75
106
  "feature": {
@@ -82,9 +113,15 @@ utils.jq(() => {
82
113
  if (Array.isArray(data)) {
83
114
  memos.version = "22-";
84
115
  memos.data = data;
85
- } else if (data.memos) {
116
+ console.log("当前Memos版本为22-");
117
+ } else if (data.memos && !data.memos[0].attachments) {
86
118
  memos.version = "22+";
87
119
  memos.data = data.memos;
120
+ console.log("当前Memos版本为22+");
121
+ } else if (data.memos && data.memos[0].attachments) {
122
+ memos.version = "25+";
123
+ memos.data = data.memos;
124
+ console.log("当前Memos版本为25+");
88
125
  } else {
89
126
  memos.version = "feature";
90
127
  console.log("当前Memos版本过高,请到Stellar社区反馈");
@@ -94,3 +131,4 @@ utils.jq(() => {
94
131
  };
95
132
  });
96
133
  });
134
+
@@ -0,0 +1,200 @@
1
+ utils.jq(() => {
2
+ $(function () {
3
+ const els = document.getElementsByClassName('ds-rss');
4
+
5
+ for (let i = 0; i < els.length; i++) {
6
+ const el = els[i];
7
+ const api = el.dataset.api;
8
+ if (!api) continue;
9
+
10
+ utils.request(el, api, async resp => {
11
+ const text = await resp.text();
12
+
13
+ const head = text.slice(0, 1024).trim();
14
+ const content_type = el.getAttribute('content_type') || 'content';
15
+ const show_title = el.getAttribute('show_title') !== 'false';
16
+ const show_content = el.getAttribute('show_content') !== 'false';
17
+ const limit = parseInt(el.getAttribute('limit')) || 10;
18
+ // JSON Feed
19
+ if (head.startsWith('{') && head.includes('jsonfeed.org/version')) {
20
+ handleJsonFeed(el, JSON.parse(text), content_type, show_title, show_content, limit);
21
+ return;
22
+ }
23
+
24
+ const parser = new DOMParser();
25
+ const doc = parser.parseFromString(text, 'text/xml');
26
+
27
+ if (doc.querySelector('parsererror')) {
28
+ console.error('Failed to parse feed XML.');
29
+ return;
30
+ }
31
+ // Atom Feed
32
+ if (doc.documentElement.nodeName === 'feed' &&
33
+ doc.documentElement.namespaceURI === 'http://www.w3.org/2005/Atom') {
34
+ handleAtom(el, doc, content_type, show_title, show_content, limit);
35
+ return;
36
+ }
37
+ // RSS 2.0 Feed
38
+ if (doc.documentElement.nodeName === 'rss') {
39
+ handleRSS2(el, doc, content_type, show_title, show_content, limit);
40
+ return;
41
+ }
42
+ // RSS 1.0 Feed
43
+ if (doc.documentElement.nodeName === 'rdf:RDF') {
44
+ handleRSS1(el, doc, content_type, show_title, show_content, limit);
45
+ return;
46
+ }
47
+ });
48
+ }
49
+ });
50
+ });
51
+
52
+
53
+ function handleAtom(el, doc, content_type, show_title, show_content, limit) {
54
+ const entries = doc.querySelectorAll('entry');
55
+ const feedAuthorName = doc.querySelector('feed > author > name')?.textContent || '匿名';
56
+
57
+ const limitedEntries = Array.from(entries).slice(0, limit);
58
+
59
+ const htmlBuffer = limitedEntries.map((item, i) => {
60
+ const title = item.querySelector('title')?.textContent || '无题';
61
+ const link = item.querySelector('link')?.getAttribute('href') || '#';
62
+ const published = item.querySelector('published, updated')?.textContent;
63
+ const content = item.querySelector('content')?.textContent || '';
64
+ const summary = item.querySelector('summary')?.textContent || '';
65
+ const authorName = item.querySelector('author > name')?.textContent || feedAuthorName;
66
+
67
+ let cell = `<div class="timenode" index="${i}">`;
68
+ cell += `<div class="header">`;
69
+ cell += `<span class="user-info"><strong>${authorName}</strong></span>`;
70
+
71
+ if (published) {
72
+ const date = new Date(published);
73
+ cell += `<span>${date.toLocaleString()}</span>`;
74
+ }
75
+ cell += `</div>`;
76
+
77
+ cell += `<div class="body">`;
78
+ if (show_title) {
79
+ const titleAttr = show_content ? '' : ' style="border-bottom:none;"' ;
80
+ const linkAttr = show_content ? '' : ' style="padding-bottom:0"' ;
81
+ cell += `<p class="title"${titleAttr}><a href="${link}"${linkAttr} target="_blank" rel="external nofollow noopener noreferrer">${title}</a></p>`;
82
+ }
83
+ if (show_content){
84
+ cell += `<div class="content">${content_type === 'summary' ? summary : content}</div>`;
85
+ }
86
+ cell += `</div></div>`;
87
+
88
+ return cell;
89
+ }).join('');
90
+
91
+ $(el).append(htmlBuffer);
92
+ }
93
+
94
+ function handleRSS2(el, doc, content_type, show_title, show_content, limit) {
95
+ const items = Array.from(doc.querySelectorAll('item')).slice(0, limit);
96
+ const feedAuthorName = doc.querySelector('channel > managingEditor, channel > webMaster, channel > title')?.textContent || '匿名';
97
+
98
+ const htmlBuffer = items.map((item, i) => {
99
+ const title = item.querySelector('title')?.textContent || '无题';
100
+ const link = item.querySelector('link')?.textContent || '#';
101
+ const pubDate = item.querySelector('pubDate')?.textContent;
102
+ const content = item.getElementsByTagName('content:encoded')[0]?.textContent || '';
103
+ const description = item.getElementsByTagName('description')[0]?.textContent || '';
104
+ const authorName = item.querySelector('author, creator')?.textContent || feedAuthorName;
105
+
106
+ let cell = `<div class="timenode" index="${i}">`;
107
+ cell += `<div class="header">`;
108
+ cell += `<span class="user-info"><strong>${authorName}</strong></span>`;
109
+ if (pubDate) {
110
+ const date = new Date(pubDate);
111
+ cell += `<span>${isNaN(date) ? pubDate : date.toLocaleString()}</span>`;
112
+ }
113
+ cell += `</div>`;
114
+ cell += `<div class="body">`;
115
+ if (show_title) {
116
+ const titleAttr = show_content ? '' : ' style="border-bottom:none;"' ;
117
+ const linkAttr = show_content ? '' : ' style="padding-bottom:0"' ;
118
+ cell += `<p class="title"${titleAttr}><a href="${link}"${linkAttr} target="_blank" rel="external nofollow noopener noreferrer">${title}</a></p>`;
119
+ }
120
+ if (show_content){
121
+ cell += `<div class="content">${content_type === 'summary' ? description : content}</div>`;
122
+ }
123
+ cell += `</div></div>`;
124
+ return cell;
125
+ }).join('');
126
+
127
+ $(el).append(htmlBuffer);
128
+ }
129
+ function handleRSS1(el, doc, content_type, show_title, show_content, limit) {
130
+ const items = Array.from(doc.querySelectorAll('item')).slice(0, limit);
131
+ const feedTitle = doc.querySelector('channel > title')?.textContent || '匿名';
132
+
133
+ const htmlBuffer = items.map((item, i) => {
134
+ const title = item.querySelector('title')?.textContent || '无题';
135
+ const link = item.querySelector('link')?.textContent || '#';
136
+ const pubDate = (item.querySelector('pubDate') || item.getElementsByTagName('dc:date')[0])?.textContent;
137
+ const content = item.getElementsByTagName('content:encoded')[0]?.textContent || '';
138
+ const description = (item.querySelector('description') || item.querySelector('summary'))?.textContent || '';
139
+ const authorName = (item.querySelector('author') || item.getElementsByTagName('dc:creator')[0])?.textContent || feedTitle;
140
+
141
+ let cell = `<div class="timenode" index="${i}">`;
142
+ cell += `<div class="header">`;
143
+ cell += `<span class="user-info"><strong>${authorName}</strong></span>`;
144
+ if (pubDate) {
145
+ const date = new Date(pubDate);
146
+ cell += `<span>${isNaN(date) ? pubDate : date.toLocaleString()}</span>`;
147
+ }
148
+ cell += `</div>`;
149
+ cell += `<div class="body">`;
150
+ if (show_title) {
151
+ const titleAttr = show_content ? '' : ' style="border-bottom:none;"' ;
152
+ const linkAttr = show_content ? '' : ' style="padding-bottom:0"' ;
153
+ cell += `<p class="title"${titleAttr}><a href="${link}"${linkAttr} target="_blank" rel="external nofollow noopener noreferrer">${title}</a></p>`;
154
+ }
155
+ if (show_content){
156
+ cell += `<div class="content">${content_type === 'summary' ? description : content}</div>`;
157
+ }
158
+ cell += `</div></div>`;
159
+ return cell;
160
+ }).join('');
161
+
162
+ $(el).append(htmlBuffer);
163
+ }
164
+ function handleJsonFeed(el, data, content_type, show_title, show_content, limit) {
165
+ const items = (data.items || []).slice(0, limit);
166
+ const feedAuthorName = data.authors?.[0]?.name || data.title || '匿名';
167
+
168
+ const htmlBuffer = items.map((item, i) => {
169
+ const id = item.id || i;
170
+ const title = item.title || '无题';
171
+ const link = item.url || '#';
172
+ const pubDate = item.date_published;
173
+ const content = item.content_html || '';
174
+ const summary = item.summary || '';
175
+
176
+ let authorName = (item.authors || []).map(a => a.name).filter(Boolean).join(' ') || feedAuthorName;
177
+
178
+ let cell = `<div class="timenode" index="${id}">`;
179
+ cell += `<div class="header">`;
180
+ cell += `<span class="user-info"><strong>${authorName}</strong></span>`;
181
+ if (pubDate) {
182
+ const date = new Date(pubDate);
183
+ cell += `<span>${isNaN(date.getTime()) ? pubDate : date.toLocaleString()}</span>`;
184
+ }
185
+ cell += `</div>`;
186
+ cell += `<div class="body">`;
187
+ if (show_title) {
188
+ const titleAttr = show_content ? '' : ' style="border-bottom:none;"' ;
189
+ const linkAttr = show_content ? '' : ' style="padding-bottom:0"' ;
190
+ cell += `<p class="title"${titleAttr}><a href="${link}"${linkAttr} target="_blank" rel="external nofollow noopener noreferrer">${title}</a></p>`;
191
+ }
192
+ if (show_content){
193
+ cell += `<div class="content">${content_type === 'summary' ? summary : content}</div>`;
194
+ }
195
+ cell += `</div></div>`;
196
+ return cell;
197
+ }).join('');
198
+
199
+ $(el).append(htmlBuffer);
200
+ }