hexo-theme-stellar 1.42.0 → 1.43.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 (122) hide show
  1. package/.claude/skills/stellar-theme-dev/SKILL.md +2 -0
  2. package/CHANGELOG.md +42 -0
  3. package/THIRD-PARTY-NOTICES.md +28 -0
  4. package/_config.yml +20 -10
  5. package/_data/icons.yml +10 -9
  6. package/languages/en.yml +11 -0
  7. package/languages/zh-CN.yml +11 -0
  8. package/languages/zh-TW.yml +11 -0
  9. package/layout/_partial/comments/artalk/layout.ejs +1 -1
  10. package/layout/_partial/comments/beaudar/layout.ejs +1 -1
  11. package/layout/_partial/comments/giscus/layout.ejs +1 -1
  12. package/layout/_partial/comments/twikoo/layout.ejs +1 -1
  13. package/layout/_partial/comments/utterances/layout.ejs +1 -1
  14. package/layout/_partial/comments/waline/layout.ejs +1 -1
  15. package/layout/_partial/components/collection-item.ejs +44 -0
  16. package/layout/_partial/components/collection.ejs +26 -0
  17. package/layout/_partial/components/widget-frame.ejs +35 -0
  18. package/layout/_partial/cover/wiki_cover.ejs +82 -26
  19. package/layout/_partial/dropdown.ejs +62 -0
  20. package/layout/_partial/head.ejs +9 -0
  21. package/layout/_partial/main/navbar/breadcrumb/blog.ejs +1 -1
  22. package/layout/_partial/main/post_list/archive_item.ejs +12 -0
  23. package/layout/_partial/main/post_list/topic_card.ejs +21 -9
  24. package/layout/_partial/main/post_list/wiki_card.ejs +49 -10
  25. package/layout/_partial/scripts/bootstrap.ejs +3 -1
  26. package/layout/_partial/scripts/defines.ejs +2 -8
  27. package/layout/_partial/scripts/lazyload.ejs +0 -3
  28. package/layout/_partial/scripts.ejs +1 -0
  29. package/layout/_partial/sidebar/index_leftbar.ejs +12 -2
  30. package/layout/_partial/sidebar/logo.ejs +1 -1
  31. package/layout/_partial/sidebar/menu.ejs +21 -23
  32. package/layout/_partial/widgets/components/linklist.ejs +23 -21
  33. package/layout/_partial/widgets/linklist.ejs +7 -12
  34. package/layout/_partial/widgets/recent.ejs +52 -41
  35. package/layout/_partial/widgets/related.ejs +47 -36
  36. package/layout/_partial/widgets/tagtree.ejs +70 -54
  37. package/layout/_partial/widgets/toc.ejs +37 -62
  38. package/layout/_partial/widgets/tree.ejs +38 -51
  39. package/layout/_plugins/index.ejs +25 -0
  40. package/layout/_plugins/mermaid.ejs +3 -4
  41. package/layout/archive.ejs +11 -20
  42. package/layout/collection-preview.ejs +60 -0
  43. package/layout/index_topic.ejs +12 -6
  44. package/layout/index_wiki.ejs +49 -3
  45. package/layout/layout.ejs +4 -3
  46. package/package.json +1 -1
  47. package/scripts/events/index.js +0 -2
  48. package/scripts/filters/lib/img_lazyload.js +184 -40
  49. package/scripts/generators/stellar-icons.js +1 -1
  50. package/scripts/lib/subtitle.js +5 -1
  51. package/scripts/tags/index.js +1 -0
  52. package/scripts/tags/lib/about.js +1 -1
  53. package/scripts/tags/lib/albums.js +1 -1
  54. package/scripts/tags/lib/banner.js +1 -1
  55. package/scripts/tags/lib/dropdown.js +64 -0
  56. package/scripts/tags/lib/friends.js +1 -1
  57. package/scripts/tags/lib/gallery.js +3 -3
  58. package/scripts/tags/lib/image.js +1 -1
  59. package/scripts/tags/lib/posters.js +1 -1
  60. package/scripts/tags/lib/sites.js +8 -4
  61. package/source/css/_common/base.styl +4 -4
  62. package/source/css/_common/blockquote.styl +2 -2
  63. package/source/css/_common/dropdown.styl +132 -0
  64. package/source/css/_common/html.styl +1 -1
  65. package/source/css/_common/loading.styl +5 -2
  66. package/source/css/_common/title.styl +8 -8
  67. package/source/css/_components/collection.styl +188 -0
  68. package/source/css/_components/layout.styl +1 -1
  69. package/source/css/_components/list.styl +44 -46
  70. package/source/css/_components/md.styl +3 -3
  71. package/source/css/_components/page-transition.styl +22 -0
  72. package/source/css/_components/pages/archives.styl +19 -18
  73. package/source/css/_components/pages/article-indent.styl +2 -2
  74. package/source/css/_components/pages/article-story.styl +9 -5
  75. package/source/css/_components/pages/collection-preview.styl +68 -0
  76. package/source/css/_components/pages/notebook.styl +11 -9
  77. package/source/css/_components/partial/article-footer.styl +7 -4
  78. package/source/css/_components/partial/cover.styl +280 -1
  79. package/source/css/_components/partial/related.styl +10 -6
  80. package/source/css/_components/sidebar/footer.styl +65 -12
  81. package/source/css/_components/sidebar/nav-area.styl +6 -1
  82. package/source/css/_components/sidebar/search.styl +6 -2
  83. package/source/css/_components/tag-plugins/chat.styl +1 -1
  84. package/source/css/_components/tag-plugins/folders.styl +4 -4
  85. package/source/css/_components/tag-plugins/folding.styl +3 -3
  86. package/source/css/_components/tag-plugins/hashtag.styl +2 -3
  87. package/source/css/_components/tag-plugins/mdrender.styl +1 -1
  88. package/source/css/_components/tag-plugins/note.styl +4 -4
  89. package/source/css/_components/tag-plugins/poetry.styl +5 -5
  90. package/source/css/_components/tag-plugins/quot.styl +1 -2
  91. package/source/css/_components/tag-plugins/quote.styl +1 -1
  92. package/source/css/_components/tag-plugins/tabs.styl +2 -2
  93. package/source/css/_components/tag-plugins/timeline.styl +4 -4
  94. package/source/css/_components/widgets/ghrepo.styl +4 -0
  95. package/source/css/_components/widgets/markdown.styl +1 -14
  96. package/source/css/_components/widgets/timeline.styl +2 -2
  97. package/source/css/_components/widgets/toc.styl +22 -20
  98. package/source/css/_components/widgets/widgets.styl +13 -1
  99. package/source/css/_components/wiki-card.styl +209 -0
  100. package/source/css/_custom.styl +22 -20
  101. package/source/css/_plugins/lazyload.styl +8 -5
  102. package/source/css/comments/utterances.styl +2 -2
  103. package/source/css/comments/waline.styl +24 -0
  104. package/source/css/plugins/mermaid.styl +31 -12
  105. package/source/js/icons.js +1 -1
  106. package/source/js/main.js +62 -6
  107. package/source/js/plugins/adaptive-text.js +46 -2
  108. package/source/js/plugins/dropdown.js +435 -0
  109. package/source/js/plugins/galaxy.js +423 -0
  110. package/source/js/search/algolia-search.js +1 -1
  111. package/source/js/search/local-search.js +1 -1
  112. package/source/js/services/ghinfo.js +31 -3
  113. package/source/js/services/siteinfo.js +26 -0
  114. package/source/js/services/sites.js +6 -2
  115. package/source/js/services.js +5 -2
  116. package/source/js/tagtree.js +7 -7
  117. package/source/js/utils.js +1 -1
  118. package/layout/_partial/widgets/components/link.ejs +0 -21
  119. package/source/css/_components/sidebar/menu.styl +0 -77
  120. package/source/css/_components/widgets/components.styl +0 -68
  121. package/source/css/_components/widgets/list.styl +0 -46
  122. package/source/css/_components/widgets/related.styl +0 -40
@@ -1,44 +1,100 @@
1
1
  <%
2
2
  function layoutWikiCover() {
3
3
  var el = '';
4
- const proj = theme.wiki.tree[page.wiki]
5
- if (proj == undefined) {
6
- return el;
7
- }
8
- if (proj.homepage.path !== page.path) {
4
+ const proj = theme.wiki.tree[page.wiki];
5
+ if (proj == undefined || proj.homepage.path !== page.path) {
9
6
  return el;
10
7
  }
11
8
  if (proj.coverpage == undefined || proj.coverpage === false || proj.coverpage === '[]') {
12
9
  return el;
13
10
  }
14
- var coverpage = proj.coverpage;
15
- let cover = proj.cover;
16
- let title = proj.title || page.title;
17
- let desc = proj.description || page.description;
18
- if (coverpage === true) {
19
- coverpage = ['cover', 'title', 'description'];
11
+
12
+ var background = proj.background;
13
+ var preview = proj.preview || {};
14
+ var commands = Array.isArray(preview.commands) ? preview.commands.filter(function (item) {
15
+ return item && item.codes;
16
+ }) : [];
17
+ var hasPreview = (preview.type === 'image' && preview.src) || commands.length > 0;
18
+ var title = proj.headline || proj.title || page.title;
19
+ var desc = proj.description || page.description;
20
+ var repo = proj.repo;
21
+ var actions = Array.isArray(proj.actions) ? proj.actions.filter(function (item) {
22
+ return item && item.title && item.url;
23
+ }) : [];
24
+ var isGalaxy = background === 'galaxy';
25
+ var isImageBackground = background && !isGalaxy;
26
+ var projectName = proj.name || proj.title || page.wiki;
27
+ var adaptiveTextAttr = '';
28
+ if (isImageBackground) {
29
+ adaptiveTextAttr = ' data-text-adaptive="split" style="--cover-url:url(\'' + escape_html(background.replace(/'/g, '%27')) + '\')"';
30
+ } else if (isGalaxy) {
31
+ adaptiveTextAttr = ' data-text-adaptive="split" style="--adaptive-background:#000000"';
20
32
  }
21
- el += '<div class="l_cover wiki">';
22
- el += '<article class="cover-wrap md-text">';
23
33
 
24
- if (cover?.length > 0 && coverpage.includes('cover')) {
25
- el += `<div class="preview cover"><img src="${cover}"/></div>`
34
+ el += '<div class="l_cover wiki wiki-hero' + (hasPreview ? '' : ' without-preview') + '">';
35
+ el += '<div class="wiki-cover-background' + (isGalaxy ? ' galaxy' : '') + (isImageBackground ? ' image' : '') + '" aria-hidden="true">';
36
+ if (isGalaxy) {
37
+ el += '<canvas></canvas>';
38
+ } else if (isImageBackground) {
39
+ el += '<img class="wiki-cover-background-image" src="' + escape_html(background) + '" alt="" />';
40
+ el += '<img class="wiki-cover-background-blur" src="' + escape_html(background) + '" alt="" />';
41
+ }
42
+ el += '<div class="wiki-cover-background-overlay"></div></div>';
43
+ el += '<div class="wiki-cover-layout"' + adaptiveTextAttr + '>';
44
+ el += '<nav class="wiki-navbar" aria-label="' + escape_html(config.title) + '">';
45
+ el += '<a class="wiki-navbar-home" href="' + escape_html(pretty_url('/')) + '">' + escape_html(config.title) + '</a>';
46
+ el += '</nav>';
47
+ el += '<article class="cover-wrap wiki-cover-content">';
48
+ el += '<section class="wiki-cover-intro">';
49
+ if (repo) {
50
+ el += '<a class="wiki-cover-release ds-ghinfo is-loading" data-project-name="' + escape_html(projectName) + '" data-repo="' + escape_html(repo) + '" data-api="https://' + theme.api_host.ghapi + '/repos/' + escape_html(repo) + '/tags" index="0" target="_blank" rel="external nofollow noopener noreferrer">';
51
+ el += '<span class="wiki-cover-release-value"><span type="text" id="latest-tag-name"></span></span></a>';
26
52
  }
27
- if (title && coverpage.includes('title')) {
28
- el += '<div class="cover-title">';
29
- el += '<span>' + title + '</span>';
30
- el += '</div>';
53
+ if (title) {
54
+ el += '<h1 class="cover-title" data-text-adaptive="contrast">' + escape_html(title) + '</h1>';
31
55
  }
32
- if (desc && coverpage.includes('description')) {
56
+ if (desc) {
33
57
  el += '<div class="description">' + desc + '</div>';
34
58
  }
35
- el += '<div class="start-wrap">';
36
- el += '<a class="button start gradient" href="#start">' + (proj.start || __('btn.getting_started')) + '</a>';
37
- el += '</div>';
38
- el += '</article>';
39
- el += '</div>';
40
- el += '<hr>';
59
+ el += '<div class="wiki-cover-actions">';
60
+ if (repo) {
61
+ el += '<a class="button wiki-cover-source" href="https://github.com/' + escape_html(repo) + '" target="_blank" rel="external nofollow noopener noreferrer">' + icon('default:github') + '<span>' + escape_html(__('btn.source')) + '</span></a>';
62
+ }
63
+ el += '<a class="button" href="#start">' + icon('default:documents') + '<span>' + escape_html(__('btn.docs')) + '</span></a>';
64
+ actions.forEach(function (item) {
65
+ el += '<a class="button" href="' + escape_html(item.url) + '" target="_blank" rel="external nofollow noopener noreferrer">';
66
+ if (item.icon) {
67
+ el += icon(item.icon);
68
+ }
69
+ el += '<span>' + escape_html(item.title) + '</span></a>';
70
+ });
71
+ el += '</div></section>';
72
+ if (hasPreview) {
73
+ el += '<section class="wiki-cover-preview" aria-label="' + escape_html(__('meta.project_preview')) + '">';
74
+ if (preview.type === 'image' && preview.src) {
75
+ el += '<img src="' + escape_html(preview.src) + '" alt="' + escape_html(preview.alt || '') + '" />';
76
+ } else if (commands.length > 0) {
77
+ el += '<div class="wiki-cover-terminal">';
78
+ el += '<div class="wiki-cover-terminal-toolbar"><div class="wiki-cover-terminal-tabs" role="tablist" aria-label="' + escape_html(__('meta.install_method')) + '">';
79
+ commands.forEach(function (item, index) {
80
+ el += '<button type="button" role="tab" aria-selected="' + (index === 0 ? 'true' : 'false') + '" class="' + (index === 0 ? 'active' : '') + '" data-codes="' + escape_html(item.codes) + '">' + escape_html(item.label || __('meta.command', index + 1)) + '</button>';
81
+ });
82
+ el += '</div><button type="button" class="wiki-cover-copy" data-copy-message="' + escape_html(__('message.copied')) + '" aria-label="' + escape_html(__('btn.copy')) + '">' + icon('copy:copy') + '<span>' + escape_html(__('btn.copy')) + '</span></button></div>';
83
+ el += '<pre><code>' + terminalCodes(commands[0].codes) + '</code></pre>';
84
+ el += '</div>';
85
+ }
86
+ el += '</section>';
87
+ }
88
+ el += '</article></div></div>';
41
89
  return el;
42
90
  }
91
+
92
+ function terminalCodes(codes) {
93
+ return String(codes || '').split(/\r?\n/).filter(function (line) {
94
+ return line.trim().length > 0;
95
+ }).map(function (line) {
96
+ return '<span>' + escape_html(line) + '</span>';
97
+ }).join('');
98
+ }
43
99
  %>
44
100
  <%- layoutWikiCover() %>
@@ -0,0 +1,62 @@
1
+ <%
2
+ const dropdownItem = typeof item !== 'undefined' && item ? item : {}
3
+ const items = Array.isArray(dropdownItem.items) ? dropdownItem.items.filter(child => child && child.title && child.url) : []
4
+ const classes = ['dropdown']
5
+ const dropdownRootClass = typeof rootClass === 'string' ? rootClass : ''
6
+ if (dropdownRootClass) {
7
+ classes.push(dropdownRootClass)
8
+ }
9
+ const triggerClasses = ['dropdown-trigger']
10
+ const dropdownTriggerClass = typeof triggerClass === 'string' ? triggerClass : ''
11
+ if (dropdownTriggerClass) {
12
+ triggerClasses.push(dropdownTriggerClass)
13
+ }
14
+ const dropdownDirection = typeof direction === 'string' ? direction : ''
15
+ const dropdownShowTitle = typeof showTitle === 'undefined' || showTitle !== false
16
+
17
+ function linkAttrs(child) {
18
+ const url = String(child.url)
19
+ var attrs = ' href="' + escape_html(url_for(url)) + '"'
20
+ if (url.includes('://')) {
21
+ attrs += ' target="_blank" rel="external nofollow noopener noreferrer"'
22
+ } else {
23
+ attrs += ' rel="noopener noreferrer"'
24
+ }
25
+ return attrs
26
+ }
27
+
28
+ function layoutDiv() {
29
+ if (!dropdownItem.icon || items.length === 0) {
30
+ return ''
31
+ }
32
+ const title = dropdownItem.title ? escape_html(dropdownItem.title) : ''
33
+ var el = '<details class="' + classes.join(' ') + '"'
34
+ if (dropdownDirection) {
35
+ el += ' direction="' + escape_html(dropdownDirection) + '"'
36
+ }
37
+ el += '>'
38
+ el += '<summary class="' + triggerClasses.join(' ') + '"'
39
+ if (title) {
40
+ el += ' title="' + title + '" aria-label="' + title + '"'
41
+ }
42
+ el += '>'
43
+ // dropdown 首次展开时需要立即显示图标,使用内联 SVG 避免 /js/icons.js 异步替换造成闪烁。
44
+ el += icon(dropdownItem.icon, 'no-lazy', true)
45
+ if (dropdownShowTitle && dropdownItem.title) {
46
+ el += '<span>' + title + '</span>'
47
+ }
48
+ el += '</summary>'
49
+ el += '<div class="dropdown-menu ui-collection" data-ui-surface="glass" data-layout="list" data-variant="nav" data-density="compact">'
50
+ for (let child of items) {
51
+ el += '<a class="dropdown-item ui-collection__item"' + linkAttrs(child) + '>'
52
+ if (child.icon) {
53
+ el += '<span class="ui-collection__leading">' + icon(child.icon, 'no-lazy', true) + '</span>'
54
+ }
55
+ el += '<span class="ui-collection__content"><span class="ui-collection__title">' + escape_html(child.title) + '</span></span>'
56
+ el += '</a>'
57
+ }
58
+ el += '</div></details>'
59
+ return el
60
+ }
61
+ %>
62
+ <%- layoutDiv() %>
@@ -237,6 +237,15 @@ function generate_canonical() {
237
237
  const raw = theme.icons && theme.icons[key];
238
238
  if (raw) vars.push(` ${name}: url("data:image/svg+xml,${encodeURIComponent(raw)}");`);
239
239
  }
240
+ // 加载占位:由 icons.yml 的 default:loading 提供(内联 SVG 编码为 data URI,URL 直用)
241
+ const loadingRaw = theme.icons && theme.icons['default:loading'];
242
+ if (loadingRaw) {
243
+ if (loadingRaw.trim().startsWith('<svg')) {
244
+ vars.push(` --icon-loading: url("data:image/svg+xml,${encodeURIComponent(loadingRaw)}");`);
245
+ } else {
246
+ vars.push(` --icon-loading: url("${loadingRaw}");`);
247
+ }
248
+ }
240
249
  return vars.length > 0 ? `:root {\n${vars.join('\n')}\n}` : '';
241
250
  })();
242
251
  %>
@@ -31,4 +31,4 @@ function layoutDiv() {
31
31
  return el
32
32
  }
33
33
  %>
34
- <%- layoutDiv() %>
34
+ <%- layoutDiv() %>
@@ -0,0 +1,12 @@
1
+ <%
2
+ var item = post || {};
3
+ var href = pretty_url(item.link || item.path || '/');
4
+ var itemDate = item.date ? date(item.date, 'MM-DD') : '';
5
+ var itemTitle = item.title || (item.date ? date(item.date, config.date_format) : '');
6
+ %>
7
+ <div class="archive-list">
8
+ <a class="post fs14" href="<%- escape_html(href) %>">
9
+ <% if (itemDate) { %><time><%- escape_html(itemDate) %></time><% } %>
10
+ <span><%- escape_html(itemTitle) %></span>
11
+ </a>
12
+ </div>
@@ -17,17 +17,29 @@ function layoutDiv() {
17
17
  background: bg,
18
18
  post: latest
19
19
  });
20
- // 其他文章(排除最新,最多 3 条,按发布时间倒序)
20
+ // 其他文章(排除最新,默认展示 3 条,剩余文章可展开)
21
21
  if (topic.pages && topic.pages.length > 1) {
22
- el += '<div class="post-panel">';
23
- el += '<div class="posts">';
24
- topic.pages.slice(1, 4).forEach(function(post) {
25
- el += '<a class="post-link" href="' + pretty_url(post.path) + '">';
26
- el += '<span class="title">' + escape_html(post.title) + '</span>';
27
- el += '<span class="date">' + escape_html(date(post.date, config.date_format)) + '</span>';
28
- el += '</a>';
22
+ var otherPosts = topic.pages.slice(1);
23
+ var visiblePosts = otherPosts.slice(0, 3);
24
+ var hiddenPosts = otherPosts.slice(3);
25
+ el += '<div class="topic-posts">';
26
+ visiblePosts.forEach(function(post) {
27
+ el += partial('_partial/main/post_list/archive_item', {post: post});
29
28
  });
30
- el += '</div>';
29
+ if (hiddenPosts.length > 0) {
30
+ el += '<details class="topic-posts-more">';
31
+ el += '<summary class="topic-posts-toggle">';
32
+ el += '<span class="when-closed">' + escape_html(__('btn.expand_posts', hiddenPosts.length)) + '</span>';
33
+ el += '<span class="when-open">' + escape_html(__('btn.collapse_posts')) + '</span>';
34
+ el += icon('default:arrow-right', '', true);
35
+ el += '</summary>';
36
+ el += '<div class="topic-posts-extra">';
37
+ hiddenPosts.forEach(function(post) {
38
+ el += partial('_partial/main/post_list/archive_item', {post: post});
39
+ });
40
+ el += '</div>';
41
+ el += '</details>';
42
+ }
31
43
  el += '</div>';
32
44
  }
33
45
  el += '</article>';
@@ -1,21 +1,60 @@
1
1
  <%
2
2
  function layoutDiv() {
3
3
  var el = '';
4
- el += '<article class="md-text">';
5
- el += `<div class="preview"><img src="${proj.icon ? proj.icon : theme.default.project}"/></div>`
6
- el += '<div class="excerpt">';
4
+ var cover = proj.cover;
5
+ var projectIcon = proj.icon;
6
+ var caption = subtitle(proj);
7
+ var headline = proj.headline || proj.title || proj.name || '';
8
+ el += '<article class="wiki-card-content">';
9
+ el += '<div class="wiki-card-cover' + (cover ? '' : ' no-cover') + '" position="bottom"';
10
+ if (cover) {
11
+ el += ' style="--cover-url:url(\'' + escape_html(cover.replace(/'/g, '%27')) + '\')">';
12
+ el += '<img src="' + escape_html(cover) + '" alt=""/>';
13
+ } else {
14
+ el += '>';
15
+ }
16
+ el += '<div class="wiki-card-info" position="bottom"';
17
+ if (cover) {
18
+ el += ' data-text-adaptive="split"';
19
+ }
20
+ el += '>';
21
+ el += '<div class="wiki-card-summary">';
7
22
  if (proj.tags && proj.tags.length > 0) {
8
- el += '<div class="caps">';
9
- for (let tag of proj.tags) {
10
- el += '<span class="cap breadcrumb"' + category_color(tag) + '>' + tag + '</span>';
11
- }
23
+ el += '<div class="wiki-tags wiki-meta">';
24
+ for (var i = 0; i < proj.tags.length; i++) {
25
+ var tag = proj.tags[i];
26
+ el += '<span>' + escape_html(tag) + '</span>';
27
+ }
28
+ el += '</div>';
29
+ }
30
+ el += '<h2 class="headline">' + escape_html(headline) + '</h2>';
31
+ if (proj.available || proj.repo) {
32
+ el += '<div class="wiki-meta">';
33
+ if (proj.available) {
34
+ el += '<span class="wiki-available">' + icon('default:platforms') + '<span class="label">' + escape_html(__('meta.available')) + '</span><span class="value">' + escape_html(proj.available) + '</span></span>';
35
+ }
36
+ if (proj.repo) {
37
+ el += '<span class="wiki-stars data-service ds-ghinfo" data-api="https://' + theme.api_host.ghapi + '/repos/' + escape_html(proj.repo) + '">';
38
+ el += icon('default:fire') + '<span type="text" id="stargazers_count"></span>';
39
+ el += '</span>';
40
+ }
12
41
  el += '</div>';
13
42
  }
14
- el += '<h2 class="post-title">' + (proj.title || proj.name) + '</h2>';
15
- if (proj.description) {
16
- el += '<p>' + proj.description + '</p>';
43
+ el += '</div>';
44
+ el += '<div class="wiki-project">';
45
+ el += '<div class="project-icon">';
46
+ if (projectIcon) {
47
+ el += '<img src="' + escape_html(projectIcon) + '" alt=""/>';
48
+ } else {
49
+ el += icon('default:documents');
17
50
  }
18
51
  el += '</div>';
52
+ el += '<div class="project-text"><div class="project-name">' + escape_html(proj.name || proj.title || '') + '</div>';
53
+ if (caption.length > 0) {
54
+ el += '<div class="project-subtitle">' + escape_html(caption) + '</div>';
55
+ }
56
+ el += '</div></div>';
57
+ el += '</div></div>';
19
58
  el += '</article>';
20
59
  return el;
21
60
  }
@@ -24,7 +24,9 @@
24
24
  if (typeof utils === 'object') return;
25
25
  // utils 未就绪时补载;加载成功后由 utils.js 内部补跑插件队列
26
26
  var s = document.createElement('script');
27
- s.src = '<%- url_for('/js/utils.js') %>?v=<%- stellar_info('version') %>';
27
+ // 用 setAttribute 赋值 src,避免被基于 `s.src = "..."` 的正则
28
+ // (图片懒加载 / 脚本延迟优化器)误改写
29
+ s.setAttribute('src', '<%- url_for('/js/utils.js') %>?v=<%- stellar_info('version') %>');
28
30
  s.async = false;
29
31
  s.onerror = function () {
30
32
  console.error('[Stellar] 无法加载 /js/utils.js,已启用内容兜底显示(sr-fallback)');
@@ -17,8 +17,6 @@
17
17
  }
18
18
  (function() {
19
19
  const scrollHandler = () => {
20
- const wikiCover = document.querySelector('#l_cover .l_cover.wiki');
21
- const startEl = document.getElementById('start');
22
20
  // 存在有效锚点时,等待布局稳定后滚动到目标
23
21
  // (浏览器原生锚点滚动发生在布局完成前,长文章的懒加载图片/异步组件会把目标顶偏)
24
22
  const hash = window.location.hash;
@@ -137,11 +135,8 @@
137
135
  return;
138
136
  }
139
137
  }
140
- if (wikiCover && startEl) {
141
- window.scrollTo(0, startEl.offsetTop);
142
- } else {
143
- window.scrollTo(0, 0);
144
- }
138
+ // 没有锚点时始终保持页面顶部;Wiki Hero 只在用户显式访问 #start 时定位正文。
139
+ window.scrollTo(0, 0);
145
140
  };
146
141
  if (document.readyState !== 'loading') scrollHandler();
147
142
  else document.addEventListener('DOMContentLoaded', scrollHandler);
@@ -174,7 +169,6 @@
174
169
  const def = {
175
170
  avatar: `<%- theme.default.avatar %>`,
176
171
  cover: `<%- theme.default.cover %>`,
177
- loading: `<%- theme.default.loading || (theme.icons && theme.icons['default:loading-placeholder']) %>`,
178
172
  data_cache: <%- JSON.stringify(theme.data_cache || {}) %>,
179
173
  };
180
174
  const deps = {
@@ -73,9 +73,6 @@
73
73
 
74
74
  const icon = document.createElement('div');
75
75
  icon.className = 'lazy-icon';
76
- if (def.loading) {
77
- icon.style.backgroundImage = `url("${def.loading}")`;
78
- }
79
76
 
80
77
  wrapper.appendChild(newImg);
81
78
  wrapper.appendChild(icon);
@@ -48,6 +48,7 @@ function tagtreeWidgetOnPage() {
48
48
  <script defer src="<%- url_for('/js/stellar-icons.js') %>?v=<%- stellar_info('version') %>"></script>
49
49
  <!-- 非首屏图标异步加载:占位符替换依赖构建期生成的 js/icons/{ns}.json -->
50
50
  <script defer src="<%- url_for('/js/icons.js') %>?v=<%- stellar_info('version') %>" data-version="<%- stellar_info('version') %>"></script>
51
+ <script defer src="<%- url_for('/js/plugins/dropdown.js') %>?v=<%- stellar_info('version') %>"></script>
51
52
  <script src="<%- url_for(`${theme.stellar.main_js}?v=${stellar_info('version')}`) %>" defer></script>
52
53
  <script defer src="<%- url_for('/js/theme.js') %>?v=<%- stellar_info('version') %>"></script>
53
54
 
@@ -125,10 +125,20 @@ function layoutFooterDiv() {
125
125
  return ''
126
126
  }
127
127
  var el = '<footer class="footer dis-select">';
128
- el += '<div class="social-wrap">';
128
+ el += '<div class="social-wrap" style="--item-grad:url(#footer-social-icon-grad)">';
129
+ el += partial('grad-def', {id: 'footer-social-icon-grad'})
129
130
  for (let id of Object.keys(social)) {
130
131
  let item = social[id];
131
- if (item.icon && (item.url || item.onclick)) {
132
+ if (id === 'spacer') {
133
+ el += '<span class="social-spacer" aria-hidden="true"></span>';
134
+ } else if (item?.type === 'dropdown') {
135
+ el += partial('../dropdown', {
136
+ item: item,
137
+ rootClass: 'social-dropdown',
138
+ triggerClass: 'social',
139
+ showTitle: false
140
+ })
141
+ } else if (item?.icon && (item.url || item.onclick)) {
132
142
  el += '<a class="social"';
133
143
  if (item.title) {
134
144
  el += ' title="' + escape_html(item.title) + '"';
@@ -50,7 +50,7 @@ function layoutBack() {
50
50
  var el = ''
51
51
  el += `<div class="wiki-home-wrap">`
52
52
  el += `<a class="wiki-home cap" href="${pretty_url(theme.site_tree.index_wiki.base_dir)}">`
53
- el += icon('default:goback', '', true)
53
+ el += icon('default:arrow-left', '', true)
54
54
  el += `<span>${__('btn.all_wiki')}</span>`
55
55
  el += `</a>`
56
56
  el += `</div>`
@@ -1,31 +1,29 @@
1
1
  <%
2
2
  function layoutDiv() {
3
- var el = ''
4
- el += `<nav class="menu dis-select${where == 'main' ? ' mobile-hidden' : ''}">`
5
- for (let item of (theme.menubar.items || [])) {
6
- if (item == undefined || item.id == null || item.url == null) {
3
+ var entries = []
4
+ for (var menuItem of (theme.menubar.items || [])) {
5
+ if (menuItem == null || menuItem.id == null || menuItem.url == null || menuItem.title == null) {
7
6
  continue
8
7
  }
9
- var active = item.id == page.menu_id
10
- el += `<a class="nav-item${active ? ' active' : ''}${item.theme?.length > 0 ? ' themed' : ''}"`
11
- var style = `--item-grad:url(#menu-grad-${item.id})`
12
- if (item.theme?.length > 0) {
13
- style += `;--item-theme:${item.theme}`
14
- }
15
- el += ` style="${style}"`
16
- el += ` title="${item.title}" href="${pretty_url(item.url)}">`
17
- if (item.icon?.length > 0) {
18
- el += icon(item.icon, 'no-lazy', true)
19
- }
20
- el += `<span>${__(item.title)}</span>`
21
- if (active) {
22
- el += `<span class="active-dot"></span>`
23
- }
24
- el += partial('grad-def', {id: 'menu-grad-' + item.id})
25
- el += `</a>`
8
+ entries.push({
9
+ href: pretty_url(menuItem.url),
10
+ title: __(menuItem.title),
11
+ icon: menuItem.icon,
12
+ theme: menuItem.theme,
13
+ active: menuItem.id == page.menu_id,
14
+ className: menuItem.theme?.length > 0 ? 'is-themed' : '',
15
+ gradientId: 'menu-grad-' + menuItem.id
16
+ })
17
+ }
18
+ var body = partial('_partial/components/collection', {options: {
19
+ layout: 'list',
20
+ variant: 'nav',
21
+ items: entries
22
+ }})
23
+ if ((body || '').trim().length === 0) {
24
+ return ''
26
25
  }
27
- el += `</nav>`
28
- return el
26
+ return `<nav class="menu dis-select${where == 'main' ? ' mobile-hidden' : ''}">${body}</nav>`
29
27
  }
30
28
  %>
31
29
 
@@ -1,27 +1,29 @@
1
1
  <%
2
2
  function layoutDiv(linklist) {
3
- var links = linklist.items || []
4
- if (links.length == 0) {
5
- return ''
3
+ var links = Array.isArray(linklist.items) ? linklist.items : []
4
+ var view = linklist.view === 'grid' ? 'grid' : 'list'
5
+ var columns = view === 'grid' ? (linklist.columns || 2) : 1
6
+ var showTitle = linklist.show_title !== false
7
+ var entries = []
8
+ for (var link of links) {
9
+ if (!link || !link.url || !link.title) {
10
+ continue
11
+ }
12
+ entries.push({
13
+ href: link.url,
14
+ title: link.title,
15
+ icon: link.icon,
16
+ active: pretty_url(link.url) == pretty_url(page.path),
17
+ showTitle: showTitle,
18
+ gradientRef: 'leftbar-active-icon-grad'
19
+ })
6
20
  }
7
- var el = ''
8
- var style = ''
9
- var columns = linklist.columns || links.length
10
- if (links.length == 1) {
11
- style += `style="grid-template-columns:repeat(1,1fr);"`
12
- } else {
13
- style += `style="grid-template-columns:repeat(${columns},1fr);"`
14
- }
15
- var align = 'center'
16
- if (columns == 1 && links.length > 1) {
17
- align = 'left'
18
- }
19
- el += `<div class="linklist ${align}${columns > 1 ? ' multi' : ''}" ${style}>`
20
- for (let link of links) {
21
- el += partial('link', {item: link, columns: columns})
22
- }
23
- el += `</div>`
24
- return el
21
+ return partial('_partial/components/collection', {options: {
22
+ layout: view,
23
+ variant: showTitle ? 'nav' : 'icon',
24
+ columns: columns,
25
+ items: entries
26
+ }})
25
27
  }
26
28
  %>
27
29
  <%- layoutDiv(item) %>
@@ -1,17 +1,12 @@
1
1
  <%
2
2
  function layoutDiv() {
3
- var el = '';
4
- el += `<widget class="widget-wrapper linklist">`
5
- if (item.title?.length > 0) {
6
- el += '<div class="widget-header dis-select">';
7
- el += '<span class="name">' + item.title + '</span>';
8
- el += '</div>';
9
- }
10
- el += '<div class="widget-body fs14">';
11
- el += partial('components/linklist', {item: item})
12
- el += '</div>';
13
- el += '</widget>';
14
- return el;
3
+ var body = partial('components/linklist', {item: item})
4
+ return partial('_partial/components/widget-frame', {
5
+ className: 'link-list-widget',
6
+ title: item.title,
7
+ body: body,
8
+ reveal: false
9
+ })
15
10
  }
16
11
  %>
17
12
  <%- layoutDiv() %>