hexo-theme-stellar 1.40.0 → 1.41.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 (106) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/_config.yml +33 -10
  3. package/_data/icons.yml +250 -37
  4. package/languages/en.yml +6 -0
  5. package/languages/zh-CN.yml +6 -0
  6. package/languages/zh-TW.yml +6 -0
  7. package/layout/_partial/comments/artalk/script.ejs +25 -8
  8. package/layout/_partial/comments/beaudar/script.ejs +1 -0
  9. package/layout/_partial/comments/twikoo/script.ejs +1 -0
  10. package/layout/_partial/comments/utterances/script.ejs +1 -0
  11. package/layout/_partial/comments/waline/script.ejs +1 -0
  12. package/layout/_partial/head.ejs +43 -7
  13. package/layout/_partial/main/article/article_tags.ejs +16 -0
  14. package/layout/_partial/main/navbar/article_banner.ejs +11 -1
  15. package/layout/_partial/main/notebook/note_card.ejs +1 -1
  16. package/layout/_partial/main/notebook/paginator.ejs +2 -2
  17. package/layout/_partial/main/pin_slider.ejs +2 -2
  18. package/layout/_partial/main/post_list/paginator.ejs +2 -2
  19. package/layout/_partial/menubtn.ejs +2 -2
  20. package/layout/_partial/scripts/defines.ejs +26 -2
  21. package/layout/_partial/scripts.ejs +30 -7
  22. package/layout/_partial/sidebar/logo.ejs +1 -1
  23. package/layout/_partial/sidebar/menu.ejs +1 -1
  24. package/layout/_partial/sidebar/search.ejs +1 -1
  25. package/layout/_partial/widgets/author.ejs +1 -1
  26. package/layout/_partial/widgets/components/link.ejs +2 -2
  27. package/layout/_partial/widgets/ghuser.ejs +1 -1
  28. package/layout/_partial/widgets/related.ejs +1 -1
  29. package/layout/_partial/widgets/toc.ejs +1 -1
  30. package/layout/_partial/widgets/tree.ejs +1 -1
  31. package/layout/_plugins/fancybox.ejs +4 -2
  32. package/layout/_plugins/mermaid.ejs +2 -1
  33. package/layout/_plugins/search/local_search.ejs +4 -3
  34. package/layout/_plugins/swiper.ejs +1 -0
  35. package/layout/categories.ejs +1 -1
  36. package/layout/page.ejs +3 -0
  37. package/layout/tags.ejs +1 -0
  38. package/package.json +1 -1
  39. package/scripts/events/lib/doc_tree.js +12 -208
  40. package/scripts/events/lib/notebooks.js +8 -148
  41. package/scripts/events/lib/utils.js +10 -1
  42. package/scripts/filters/index.js +1 -0
  43. package/scripts/filters/lib/img_lazyload.js +4 -0
  44. package/scripts/filters/lib/img_onerror.js +4 -0
  45. package/scripts/filters/lib/md_table.js +46 -0
  46. package/scripts/generators/search.js +12 -18
  47. package/scripts/generators/stellar-icons.js +47 -0
  48. package/scripts/helpers/ai_label.js +29 -0
  49. package/scripts/helpers/icon.js +2 -2
  50. package/scripts/helpers/json_ld.js +10 -13
  51. package/scripts/helpers/related_posts.js +0 -7
  52. package/scripts/helpers/seo.js +9 -0
  53. package/scripts/lib/ai_label.js +55 -0
  54. package/scripts/lib/doc_tree.js +267 -0
  55. package/scripts/lib/notebooks.js +181 -0
  56. package/scripts/lib/search_index.js +82 -0
  57. package/scripts/lib/seo.js +79 -0
  58. package/scripts/tags/lib/about.js +2 -2
  59. package/scripts/tags/lib/albums.js +1 -1
  60. package/scripts/tags/lib/banner.js +1 -1
  61. package/scripts/tags/lib/chat.js +49 -49
  62. package/scripts/tags/lib/copy.js +1 -1
  63. package/scripts/tags/lib/friends.js +1 -1
  64. package/scripts/tags/lib/gallery.js +1 -1
  65. package/scripts/tags/lib/hashtag.js +1 -1
  66. package/scripts/tags/lib/image.js +3 -3
  67. package/scripts/tags/lib/posters.js +1 -1
  68. package/scripts/tags/lib/sites.js +2 -2
  69. package/source/css/_common/base.styl +5 -9
  70. package/source/css/_common/device.styl +3 -3
  71. package/source/css/_components/md-table.styl +23 -0
  72. package/source/css/_components/pages/archives.styl +2 -22
  73. package/source/css/_components/pages/article-story.styl +4 -4
  74. package/source/css/_components/partial/article-tags.styl +10 -0
  75. package/source/css/_components/partial/bread-nav.styl +14 -0
  76. package/source/css/_components/partial/navbar.styl +10 -2
  77. package/source/css/_components/sidebar/search.styl +24 -4
  78. package/source/css/_components/tag-plugins/table.styl +16 -11
  79. package/source/css/_components/widgets/components.styl +6 -0
  80. package/source/css/_components/widgets/list.styl +6 -0
  81. package/source/css/_components/widgets/toc.styl +1 -1
  82. package/source/css/_custom.styl +1 -12
  83. package/source/css/_defines/func.styl +28 -3
  84. package/source/css/_plugins/index.styl +0 -18
  85. package/source/css/{_plugins/comments → comments}/artalk.styl +4 -0
  86. package/source/css/{_plugins/comments → comments}/twikoo.styl +4 -0
  87. package/source/css/{_plugins → plugins}/mermaid.styl +3 -0
  88. package/source/css/{_plugins → plugins}/swiper.styl +5 -1
  89. package/source/js/icons.js +60 -0
  90. package/source/js/main.js +114 -0
  91. package/source/js/search/highlight.js +82 -0
  92. package/source/js/search/local-search.js +294 -103
  93. package/source/js/services/timeline.js +1 -1
  94. package/source/js/services/weibo.js +3 -3
  95. package/source/js/services.js +179 -0
  96. package/source/js/tagtree.js +29 -0
  97. package/source/js/theme.js +61 -0
  98. package/{layout/_partial/scripts/utils.ejs → source/js/utils.js} +13 -10
  99. package/layout/_partial/scripts/services.ejs +0 -178
  100. package/layout/_partial/scripts/sidebar.ejs +0 -25
  101. package/layout/_partial/scripts/tagtree.ejs +0 -29
  102. package/layout/_partial/scripts/theme.ejs +0 -67
  103. /package/source/css/{_plugins/comments → comments}/beaudar.styl +0 -0
  104. /package/source/css/{_plugins/comments → comments}/utterances.styl +0 -0
  105. /package/source/css/{_plugins/comments → comments}/waline.styl +0 -0
  106. /package/source/css/{_plugins → plugins}/fancybox.styl +0 -0
package/source/js/main.js CHANGED
@@ -97,6 +97,30 @@ const hud = {
97
97
 
98
98
  const l_body = document.querySelector('.l_body');
99
99
 
100
+ const sidebar = {
101
+ leftbar: () => {
102
+ if (l_body) {
103
+ l_body.toggleAttribute('leftbar');
104
+ l_body.removeAttribute('rightbar');
105
+ }
106
+ },
107
+ rightbar: () => {
108
+ if (l_body) {
109
+ l_body.toggleAttribute('rightbar');
110
+ l_body.removeAttribute('leftbar');
111
+ }
112
+ },
113
+ dismiss: () => {
114
+ if (l_body) {
115
+ l_body.removeAttribute('leftbar');
116
+ l_body.removeAttribute('rightbar');
117
+ }
118
+ },
119
+ toggleTOC: () => {
120
+ document.querySelector('#data-toc').classList.toggle('collapse');
121
+ }
122
+ }
123
+
100
124
  // 通用平滑滚动(自定义动画,TOC / 回到顶部 / 参与讨论共用)
101
125
  let scrollAnim = null;
102
126
  function cancelSmoothScroll() {
@@ -217,6 +241,40 @@ function bindTocClick(widget) {
217
241
  });
218
242
  }
219
243
 
244
+ // 通用页内锚点平滑滚动(标题左侧 headerlink、{% navbar %} 页内导航、脚注回链等)
245
+ // 已被其他处理器拦截的点击(TOC、tabs、wiki #start 等)通过 defaultPrevented 跳过,避免重复滚动
246
+ function bindAnchorClick() {
247
+ document.addEventListener('click', function (e) {
248
+ const link = e.target.closest('a[href^="#"]');
249
+ if (!link || e.defaultPrevented) {
250
+ return;
251
+ }
252
+ const href = link.getAttribute('href');
253
+ if (!href || href.indexOf('#') !== 0) {
254
+ return;
255
+ }
256
+ let id = href.slice(1);
257
+ try {
258
+ id = decodeURIComponent(id);
259
+ } catch (err) {
260
+ // 片段含非法编码时按原样查找
261
+ }
262
+ const target = id && document.getElementById(id);
263
+ if (!target) {
264
+ return;
265
+ }
266
+ e.preventDefault();
267
+ // #start 锚点贴顶滚动,不预留 offset;其余锚点与 TOC 点击滚动保持一致(32px)
268
+ const offset = id === 'start' ? 0 : 32;
269
+ const targetY = target.getBoundingClientRect().top + window.scrollY - offset;
270
+ smoothScrollTo(targetY);
271
+ if (window.history && window.history.pushState) {
272
+ window.history.pushState(null, '', href);
273
+ }
274
+ });
275
+ }
276
+ bindAnchorClick();
277
+
220
278
  // 远程 md 渲染完成后由页面层重建右栏 TOC
221
279
  document.addEventListener('stellar:mdrender', function (e) {
222
280
  rebuildToc(e.detail && e.detail.target);
@@ -367,6 +425,61 @@ const init = {
367
425
  }
368
426
  } catch (e) {}
369
427
  },
428
+ navbarPin: () => {
429
+ // 列表页 navbar top 背景条状态切换:未吸顶为卡片样式(var(--card) + 文章卡片同款阴影),
430
+ // 吸顶后恢复玻璃效果。在吸顶边界切换 .pinned 类,视觉由 CSS 控制。
431
+ // 吸顶判定直接测 navbar 的实际视口位置,而非用 scrollY 推算:
432
+ // 移动端浏览器顶栏伸缩会改变 scrollY(展开顶栏时 scrollY 减小),
433
+ // 即使 navbar 仍吸顶也可能跌破阈值,导致玻璃效果误消失。
434
+ const navbars = document.querySelectorAll('.navbar.top');
435
+ if (navbars.length === 0) {
436
+ return;
437
+ }
438
+ // 视口顶部允许的偏差(px),吸收亚像素/取整误差
439
+ const TOLERANCE = 2;
440
+ let states = [];
441
+ function update() {
442
+ states.forEach((state) => {
443
+ const top = state.navbar.getBoundingClientRect().top;
444
+ state.bar.classList.toggle('pinned', top <= state.stickyTop + TOLERANCE);
445
+ });
446
+ }
447
+ function measure() {
448
+ states = [];
449
+ navbars.forEach((navbar) => {
450
+ const bar = navbar.querySelector('.navbar-blur');
451
+ if (bar == null) {
452
+ return;
453
+ }
454
+ // getComputedStyle().top 自动兼容桌面 var(--gap-margin) 与移动端 8pt
455
+ const stickyTop = parseFloat(getComputedStyle(navbar).top) || 16;
456
+ states.push({
457
+ navbar: navbar,
458
+ bar: bar,
459
+ stickyTop: stickyTop
460
+ });
461
+ });
462
+ update();
463
+ }
464
+ let ticking = false;
465
+ window.addEventListener('scroll', () => {
466
+ if (ticking) {
467
+ return;
468
+ }
469
+ ticking = true;
470
+ utils.requestAnimationFrame(() => {
471
+ update();
472
+ ticking = false;
473
+ });
474
+ }, { passive: true });
475
+ // 顶栏伸缩不一定触发 scroll,兜底监听 visualViewport 尺寸变化
476
+ if (window.visualViewport) {
477
+ window.visualViewport.addEventListener('resize', update);
478
+ }
479
+ window.addEventListener('resize', measure);
480
+ window.addEventListener('pageshow', measure);
481
+ measure();
482
+ },
370
483
  relativeDate: (selector) => {
371
484
  selector.forEach(item => {
372
485
  const $this = item
@@ -509,6 +622,7 @@ stellar.initPage = function () {
509
622
  init.sidebar();
510
623
  init.wikiStart();
511
624
  init.leftbarScroll();
625
+ init.navbarPin();
512
626
  init.relativeDate(document.querySelectorAll('#post-meta time'));
513
627
  init.registerTabsTag();
514
628
  };
@@ -0,0 +1,82 @@
1
+ // 搜索跳转高亮:URL 带 ?kw= 时在正文中高亮匹配词
2
+ (function () {
3
+ var params;
4
+ try {
5
+ params = new URLSearchParams(window.location.search);
6
+ } catch (e) {
7
+ return;
8
+ }
9
+ var keyword = (params.get('kw') || '').trim();
10
+ if (!keyword) return;
11
+
12
+ function escapeRegExp(string) {
13
+ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
14
+ }
15
+
16
+ function highlightKeyword() {
17
+ // 优先主栏正文(避免命中 wiki 封面等其它 md-text 容器)
18
+ var root = document.querySelector('.l_main .md-text') || document.querySelector('.md-text');
19
+ if (!root) return;
20
+
21
+ var re = new RegExp(escapeRegExp(keyword), 'gi');
22
+ var textNodes = [];
23
+ var walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
24
+ acceptNode: function (node) {
25
+ var parent = node.parentElement;
26
+ if (!parent) return NodeFilter.FILTER_REJECT;
27
+ // 跳过脚本/样式内容与已存在的 mark 高亮
28
+ if (parent.closest('script, style, noscript, .tag-plugin.mark')) {
29
+ return NodeFilter.FILTER_REJECT;
30
+ }
31
+ return NodeFilter.FILTER_ACCEPT;
32
+ }
33
+ });
34
+ var node;
35
+ while ((node = walker.nextNode())) {
36
+ textNodes.push(node);
37
+ }
38
+
39
+ var limit = 50;
40
+ var count = 0;
41
+ for (var i = 0; i < textNodes.length && count < limit; i++) {
42
+ var textNode = textNodes[i];
43
+ var text = textNode.nodeValue || '';
44
+ var frag = document.createDocumentFragment();
45
+ var lastIndex = 0;
46
+ var m;
47
+ re.lastIndex = 0;
48
+ while (count < limit && (m = re.exec(text)) !== null) {
49
+ if (m.index > lastIndex) {
50
+ frag.appendChild(document.createTextNode(text.slice(lastIndex, m.index)));
51
+ }
52
+ var mark = document.createElement('mark');
53
+ mark.className = 'tag-plugin colorful mark';
54
+ mark.setAttribute('color', 'yellow');
55
+ mark.textContent = m[0];
56
+ frag.appendChild(mark);
57
+ count += 1;
58
+ lastIndex = m.index + m[0].length;
59
+ }
60
+ if (lastIndex === 0) continue;
61
+ if (lastIndex < text.length) {
62
+ frag.appendChild(document.createTextNode(text.slice(lastIndex)));
63
+ }
64
+ textNode.parentNode.replaceChild(frag, textNode);
65
+ }
66
+
67
+ // 无锚点时(intro / 仅标题命中)滚动到第一个匹配词,实现定位
68
+ if (!window.location.hash) {
69
+ var first = root.querySelector('mark.tag-plugin.mark[color="yellow"]');
70
+ if (first) {
71
+ var top = first.getBoundingClientRect().top + window.scrollY - 32;
72
+ window.scrollTo({ top: Math.max(0, top), behavior: 'instant' });
73
+ }
74
+ }
75
+ }
76
+
77
+ if (document.readyState !== 'loading') {
78
+ highlightKeyword();
79
+ } else {
80
+ document.addEventListener('DOMContentLoaded', highlightKeyword);
81
+ }
82
+ })();