hexo-theme-stellar 1.28.0 → 1.29.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 (126) hide show
  1. package/_config.yml +55 -6
  2. package/_data/icons.yml +3 -1
  3. package/_data/widgets.yml +6 -0
  4. package/languages/en.yml +7 -0
  5. package/languages/zh-CN.yml +7 -0
  6. package/languages/zh-TW.yml +7 -0
  7. package/layout/_partial/comments/artalk/script.ejs +9 -13
  8. package/layout/_partial/comments/beaudar/script.ejs +20 -23
  9. package/layout/_partial/comments/giscus/script.ejs +11 -11
  10. package/layout/_partial/comments/twikoo/script.ejs +12 -17
  11. package/layout/_partial/comments/utterances/script.ejs +20 -23
  12. package/layout/_partial/comments/waline/script.ejs +9 -16
  13. package/layout/_partial/head.ejs +1 -1
  14. package/layout/_partial/main/article/article_footer.ejs +6 -2
  15. package/layout/_partial/main/article/related_posts.ejs +1 -1
  16. package/layout/_partial/main/navbar/article_banner.ejs +2 -0
  17. package/layout/_partial/main/navbar/breadcrumb/note.ejs +8 -0
  18. package/layout/_partial/main/navbar/dateinfo.ejs +11 -0
  19. package/layout/_partial/main/notebook/note_card.ejs +36 -0
  20. package/layout/_partial/main/notebook/note_tags.ejs +9 -0
  21. package/layout/_partial/main/notebook/notebook_card.ejs +10 -0
  22. package/layout/_partial/main/notebook/paginator.ejs +9 -0
  23. package/layout/_partial/scripts/tagtree.ejs +29 -0
  24. package/layout/_partial/scripts/theme.ejs +62 -0
  25. package/layout/_partial/scripts.ejs +4 -1
  26. package/layout/_partial/sidebar/index_leftbar.ejs +10 -0
  27. package/layout/_partial/sidebar/index_rightbar.ejs +10 -0
  28. package/layout/_partial/sidebar/search.ejs +4 -0
  29. package/layout/_partial/widgets/recent.ejs +10 -0
  30. package/layout/_partial/widgets/tagtree.ejs +59 -0
  31. package/layout/_partial/widgets/toc.ejs +7 -5
  32. package/layout/_plugins/fancybox.ejs +2 -2
  33. package/layout/_plugins/tianli_gpt.ejs +1 -1
  34. package/layout/layout.ejs +5 -1
  35. package/layout/notebooks.ejs +8 -0
  36. package/layout/notes.ejs +12 -0
  37. package/layout/page.ejs +11 -1
  38. package/package.json +1 -1
  39. package/scripts/events/index.js +1 -0
  40. package/scripts/events/lib/notebooks.js +153 -0
  41. package/scripts/generators/notebooks.js +71 -0
  42. package/scripts/tags/index.js +5 -0
  43. package/scripts/tags/lib/image.js +16 -4
  44. package/scripts/tags/lib/quot.js +1 -2
  45. package/scripts/tags/lib/read/paper.js +90 -0
  46. package/scripts/tags/lib/read/reel.js +43 -0
  47. package/scripts/tags/lib/video.js +11 -6
  48. package/source/css/_common/button.styl +3 -3
  49. package/source/css/_common/device.styl +3 -3
  50. package/source/css/_common/highlight.styl +4 -2
  51. package/source/css/{_layout → _components}/md.styl +21 -19
  52. package/source/css/_components/pages/notebook.styl +65 -0
  53. package/source/css/{_layout → _components}/partial/article-banner.styl +4 -4
  54. package/source/css/{_layout → _components}/tag-plugins/common.styl +2 -2
  55. package/source/css/_components/tag-plugins/index.styl +1 -0
  56. package/source/css/{_layout → _components}/tag-plugins/media.styl +1 -1
  57. package/source/css/{_layout → _components}/tag-plugins/quot.styl +0 -2
  58. package/source/css/_components/tag-plugins/read/paper.styl +98 -0
  59. package/source/css/_components/tag-plugins/read/reel.styl +103 -0
  60. package/source/css/_defines/func.styl +4 -6
  61. package/source/css/_defines/theme.styl +36 -38
  62. package/source/css/_plugins/comments/waline.styl +3 -3
  63. package/source/css/_plugins/fancybox.styl +1 -1
  64. package/source/css/_plugins/mermaid.styl +2 -2
  65. package/source/css/main.styl +1 -1
  66. package/source/js/main.js +14 -0
  67. package/source/js/plugins/copycode.js +1 -1
  68. package/source/js/search/local-search.js +2 -2
  69. /package/source/css/{_layout → _components}/index.styl +0 -0
  70. /package/source/css/{_layout → _components}/layout.styl +0 -0
  71. /package/source/css/{_layout → _components}/list.styl +0 -0
  72. /package/source/css/{_layout → _components}/main.styl +0 -0
  73. /package/source/css/{_layout → _components}/pages/archives.styl +0 -0
  74. /package/source/css/{_layout → _components}/pages/error.styl +0 -0
  75. /package/source/css/{_layout → _components}/partial/article-footer.styl +0 -0
  76. /package/source/css/{_layout → _components}/partial/bread-nav.styl +0 -0
  77. /package/source/css/{_layout → _components}/partial/cover.styl +0 -0
  78. /package/source/css/{_layout → _components}/partial/footer.styl +0 -0
  79. /package/source/css/{_layout → _components}/partial/navbar.styl +0 -0
  80. /package/source/css/{_layout → _components}/partial/paginator.styl +0 -0
  81. /package/source/css/{_layout → _components}/partial/related.styl +0 -0
  82. /package/source/css/{_layout → _components}/sidebar/footer.styl +0 -0
  83. /package/source/css/{_layout → _components}/sidebar/logo.styl +0 -0
  84. /package/source/css/{_layout → _components}/sidebar/menu.styl +0 -0
  85. /package/source/css/{_layout → _components}/sidebar/nav-area.styl +0 -0
  86. /package/source/css/{_layout → _components}/sidebar/search.styl +0 -0
  87. /package/source/css/{_layout → _components}/sidebar/sidebar.styl +0 -0
  88. /package/source/css/{_layout → _components}/tag-plugins/about.styl +0 -0
  89. /package/source/css/{_layout → _components}/tag-plugins/banner.styl +0 -0
  90. /package/source/css/{_layout → _components}/tag-plugins/button.styl +0 -0
  91. /package/source/css/{_layout → _components}/tag-plugins/checkbox.styl +0 -0
  92. /package/source/css/{_layout → _components}/tag-plugins/copy.styl +0 -0
  93. /package/source/css/{_layout → _components}/tag-plugins/emoji.styl +0 -0
  94. /package/source/css/{_layout → _components}/tag-plugins/folders.styl +0 -0
  95. /package/source/css/{_layout → _components}/tag-plugins/folding.styl +0 -0
  96. /package/source/css/{_layout → _components}/tag-plugins/frame.styl +0 -0
  97. /package/source/css/{_layout → _components}/tag-plugins/friends.styl +0 -0
  98. /package/source/css/{_layout → _components}/tag-plugins/gallery.styl +0 -0
  99. /package/source/css/{_layout → _components}/tag-plugins/ghcard.styl +0 -0
  100. /package/source/css/{_layout → _components}/tag-plugins/grid.styl +0 -0
  101. /package/source/css/{_layout → _components}/tag-plugins/hashtag.styl +0 -0
  102. /package/source/css/{_layout → _components}/tag-plugins/icon.styl +0 -0
  103. /package/source/css/{_layout → _components}/tag-plugins/image.styl +0 -0
  104. /package/source/css/{_layout → _components}/tag-plugins/inline-labels.styl +0 -0
  105. /package/source/css/{_layout → _components}/tag-plugins/link.styl +0 -0
  106. /package/source/css/{_layout → _components}/tag-plugins/mark.styl +0 -0
  107. /package/source/css/{_layout → _components}/tag-plugins/navbar.styl +0 -0
  108. /package/source/css/{_layout → _components}/tag-plugins/note.styl +0 -0
  109. /package/source/css/{_layout → _components}/tag-plugins/okr.styl +0 -0
  110. /package/source/css/{_layout → _components}/tag-plugins/override.styl +0 -0
  111. /package/source/css/{_layout → _components}/tag-plugins/poetry.styl +0 -0
  112. /package/source/css/{_layout → _components}/tag-plugins/sites.styl +0 -0
  113. /package/source/css/{_layout → _components}/tag-plugins/tabs.styl +0 -0
  114. /package/source/css/{_layout → _components}/tag-plugins/timeline.styl +0 -0
  115. /package/source/css/{_layout → _components}/tag-plugins/toc.styl +0 -0
  116. /package/source/css/{_layout → _components}/widgets/components.styl +0 -0
  117. /package/source/css/{_layout → _components}/widgets/ghrepo.styl +0 -0
  118. /package/source/css/{_layout → _components}/widgets/ghuser.styl +0 -0
  119. /package/source/css/{_layout → _components}/widgets/list.styl +0 -0
  120. /package/source/css/{_layout → _components}/widgets/markdown.styl +0 -0
  121. /package/source/css/{_layout → _components}/widgets/related.styl +0 -0
  122. /package/source/css/{_layout → _components}/widgets/tagcloud.styl +0 -0
  123. /package/source/css/{_layout → _components}/widgets/timeline.styl +0 -0
  124. /package/source/css/{_layout → _components}/widgets/toc.styl +0 -0
  125. /package/source/css/{_layout → _components}/widgets/tree.styl +0 -0
  126. /package/source/css/{_layout → _components}/widgets/widgets.styl +0 -0
@@ -0,0 +1,9 @@
1
+ <% if ((page.total || 0) > 1) { %>
2
+ <div class="paginator-wrap dis-select<%= scrollreveal(' ') %>">
3
+ <%- paginator({
4
+ prev_text: '',
5
+ next_text: '',
6
+ force_prev_next: true,
7
+ }) %>
8
+ </div>
9
+ <% } %>
@@ -0,0 +1,29 @@
1
+ <script type="text/javascript">
2
+ (() => {
3
+ const tagSwitchers = document.querySelectorAll('.tag-subtree.parent-tag > a > .tag-switcher-wrapper')
4
+ for (const tagSwitcher of tagSwitchers) {
5
+ tagSwitcher.addEventListener('click', (e) => {
6
+ const parent = e.target.closest('.tag-subtree.parent-tag')
7
+ parent.classList.toggle('expanded')
8
+ e.preventDefault()
9
+ })
10
+ }
11
+
12
+ // Get active tag from query string, then activate it.
13
+ const urlParams = new URLSearchParams(window.location.search)
14
+ const activeTag = urlParams.get('tag')
15
+ if (activeTag) {
16
+ let tag = document.querySelector(`.tag-subtree[data-tag="${activeTag}"]`)
17
+ if (tag) {
18
+ tag.querySelector('a').classList.add('active')
19
+ <% if (!theme.widgets.tagtree.expand_active) { %>
20
+ tag = tag.parentElement.closest('.tag-subtree.parent-tag')
21
+ <% } %>
22
+ while (tag) {
23
+ tag.classList.add('expanded')
24
+ tag = tag.parentElement.closest('.tag-subtree.parent-tag')
25
+ }
26
+ }
27
+ }
28
+ })()
29
+ </script>
@@ -0,0 +1,62 @@
1
+ <script type="text/javascript">
2
+ const applyTheme = (theme) => {
3
+ if (theme === 'auto') {
4
+ document.documentElement.removeAttribute('data-theme')
5
+ } else {
6
+ document.documentElement.setAttribute('data-theme', theme)
7
+ }
8
+
9
+ applyThemeToGiscus(theme)
10
+ }
11
+
12
+ const applyThemeToGiscus = (theme) => {
13
+ theme = theme === 'auto' ? 'preferred_color_scheme' : theme
14
+
15
+ const cmt = document.getElementById('giscus')
16
+ if (cmt) {
17
+ // This works before giscus load.
18
+ cmt.setAttribute('data-theme', theme)
19
+ }
20
+
21
+ const iframe = document.querySelector('#comments > section.giscus > iframe')
22
+ if (iframe) {
23
+ // This works after giscus loaded.
24
+ const src = iframe.src
25
+ const newSrc = src.replace(/theme=[\w]+/, `theme=${theme}`)
26
+ iframe.src = newSrc
27
+ }
28
+ }
29
+
30
+ const switchTheme = () => {
31
+ // light -> dark -> auto -> light -> ...
32
+ const currentTheme = document.documentElement.getAttribute('data-theme')
33
+ let newTheme;
34
+ switch (currentTheme) {
35
+ case 'light':
36
+ newTheme = 'dark'
37
+ break
38
+ case 'dark':
39
+ newTheme = 'auto'
40
+ break
41
+ default:
42
+ newTheme = 'light'
43
+ }
44
+ applyTheme(newTheme)
45
+ window.localStorage.setItem('Stellar.theme', newTheme)
46
+
47
+ const messages = {
48
+ light: `<%- __('message.theme_switched.light') %>`,
49
+ dark: `<%- __('message.theme_switched.dark') %>`,
50
+ auto: `<%- __('message.theme_switched.auto') %>`,
51
+ }
52
+ hud?.toast?.(messages[newTheme])
53
+ }
54
+
55
+ (() => {
56
+ // Apply user's preferred theme, if any.
57
+ const theme = window.localStorage.getItem('Stellar.theme')
58
+ if (theme !== null) {
59
+ applyTheme(theme)
60
+ }
61
+ })()
62
+ </script>
@@ -18,9 +18,12 @@ function custom_inject() {
18
18
  <%- partial('scripts/defines') %>
19
19
  <%- partial('scripts/utils') %>
20
20
  <%- partial('scripts/sidebar') %>
21
+ <%- partial('scripts/tagtree') %>
21
22
 
22
23
  <!-- required -->
23
- <script src="<%- `${theme.stellar.main_js}?v=${stellar_info('version')}` %>" async></script>
24
+ <script src="<%- `${theme.stellar.main_js}?v=${stellar_info('version')}` %>" defer></script>
25
+
26
+ <%- partial('scripts/theme') %>
24
27
 
25
28
  <!-- optional -->
26
29
  <%- partial('comments/script') %>
@@ -2,12 +2,22 @@
2
2
 
3
3
  const wiki = theme.wiki.tree[page.wiki]
4
4
  const topic = theme.topic.tree[page.topic]
5
+ const notebook = theme.notebooks.tree[page.notebook]
5
6
 
6
7
  if (page.leftbar == null) {
7
8
  const { site_tree } = theme
8
9
  var sidebar
9
10
  if (is_home()) {
10
11
  sidebar = site_tree.home.leftbar
12
+ } else if (page.layout === 'notebooks') {
13
+ // 笔记本列表页
14
+ sidebar = site_tree.notebooks.leftbar
15
+ } else if (page.layout === 'notes') {
16
+ // 笔记列表页
17
+ sidebar = notebook ? notebook.leftbar : site_tree.notes.leftbar
18
+ } else if (notebook) {
19
+ // 笔记本文章内页
20
+ sidebar = page.leftbar ?? notebook.note_leftbar
11
21
  } else if (is_category() || is_tag() || is_archive() || ['categories', 'tags', 'archives'].includes(page.layout)) {
12
22
  sidebar = site_tree.index_blog.leftbar
13
23
  } else if (page.layout === 'index_topic') {
@@ -2,12 +2,22 @@
2
2
 
3
3
  const wiki = theme.wiki.tree[page.wiki]
4
4
  const topic = theme.topic.tree[page.topic]
5
+ const notebook = theme.notebooks.tree[page.notebook]
5
6
 
6
7
  if (page.rightbar == null) {
7
8
  const { site_tree } = theme
8
9
  var sidebar
9
10
  if (is_home()) {
10
11
  sidebar = site_tree.home.rightbar
12
+ } else if (page.layout === 'notebooks') {
13
+ // 笔记本列表页
14
+ sidebar = site_tree.notebooks.rightbar
15
+ } else if (page.layout === 'notes') {
16
+ // 笔记列表页
17
+ sidebar = notebook ? notebook.rightbar : site_tree.notes.rightbar
18
+ } else if (notebook) {
19
+ // 笔记本文章内页
20
+ sidebar = page.rightbar ?? notebook.note_rightbar
11
21
  } else if (is_category() || is_tag() || is_archive() || ['categories', 'tags', 'archives'].includes(page.layout)) {
12
22
  sidebar = site_tree.index_blog.rightbar
13
23
  } else if (page.layout === 'index_topic') {
@@ -1,10 +1,14 @@
1
1
  <%
2
+ const notebook = theme.notebooks.tree[page.notebook]
2
3
  if (item.filter == null) {
3
4
  item.filter = 'auto'
4
5
  }
5
6
  if (item.placeholder == null && item.filter == 'auto') {
6
7
  if (theme.wiki.tree[page.wiki]?.name) {
7
8
  item.placeholder = __('search.search_in', theme.wiki.tree[page.wiki]?.name)
9
+ } else if (notebook) {
10
+ item.placeholder = __('search.search_in', notebook.name || __('btn.notebook'))
11
+ item.filter = notebook.base_dir
8
12
  }
9
13
  }
10
14
  function layoutDiv() {
@@ -21,6 +21,10 @@ function layoutDiv() {
21
21
  return false
22
22
  })
23
23
  arr = arr.sort((p1, p2) => p1.updated > p2.updated ? -1 : 1)
24
+ } else if (page.layout === 'notebooks') {
25
+ arr = site.pages.filter(p => p.notebook).sort('-updated')
26
+ } else if (page.notebook) {
27
+ arr = site.pages.filter(p => p.notebook === page.notebook).sort('-updated')
24
28
  } else {
25
29
  arr = site.posts.filter( p => p.title && p.title.length > 0)
26
30
  arr = arr.sort("updated", -1)
@@ -38,6 +42,12 @@ function layoutDiv() {
38
42
  if (name) {
39
43
  el += '<strong>' + name + '</strong>' + '<span class="dot"></span>';
40
44
  }
45
+ } else if (page.layout === 'notebooks') {
46
+ const notebook = theme.notebooks.tree[post.notebook]
47
+ const name = notebook?.name || post.notebook
48
+ if (name) {
49
+ el += '<strong>' + name + '</strong>' + '<span class="dot"></span>';
50
+ }
41
51
  }
42
52
  el += post.title + '</span>';
43
53
  el += '</a>';
@@ -0,0 +1,59 @@
1
+ <%# 笔记本的标签树 %>
2
+ <% const notebook = theme.notebooks.tree[page.notebook] %>
3
+ <% const tagTree = notebook?.tagTree %>
4
+ <% const isLeaf = tag => tag.id === '' || tag.children.length === 0 %>
5
+ <% const getTagcon = tag => {
6
+ const tagcons = theme.notebook.tagcons || {}
7
+ return tagcons[tag.name] || tagcons[tag.id] || tagcons[tag.part] || tagcons[tag.part.toLowerCase()] || tagcons['']
8
+ } %>
9
+
10
+ <% function layoutTag(tag, level) { %>
11
+ <% const active = page.activeTag === tag.id %>
12
+ <a class="link<%= active ? ' active' : '' %>" href="<%= url_for(tag.path) %>">
13
+ <span class="toc-text"<% if (level > 0) { %> style="padding-left: <%= level * 0.875 %>rem;"<% } %>>
14
+ <% if (tag.id === '') { %>
15
+ <%= __('meta.all_notes') %>
16
+ <% } else { %>
17
+ <% const tagcon = item.show_tagcon && getTagcon(tag) %>
18
+ <% if (tagcon) { %><span class="tagcon"><%- icon(tagcon) %></span><% } %>
19
+ <%= tag.part %>
20
+ <% } %>
21
+ </span>
22
+ <span class="toc-text tag-switcher-wrapper">
23
+ <span class="tag-switcher" />
24
+ </span>
25
+ </a>
26
+ <% } %>
27
+
28
+ <% function layoutChildTags(tag, level) { %>
29
+ <% for (const child of tag.children) { %>
30
+ <%= tagAndSub(tagTree.get(child), level + 1) %>
31
+ <% } %>
32
+ <% } %>
33
+
34
+ <% function tagAndSub(tag, level) { %>
35
+ <% if (!tag) return '' %>
36
+ <% const active = page.activeTag === tag.id %>
37
+ <% const expanded = item.expand_all || (item.expand_active && active) || page.activeTag?.startsWith(`${tag.id}/`) %>
38
+ <% const classes = [isLeaf(tag) ? ' leaf-tag' : ' parent-tag', expanded ? ' expanded' : ''] %>
39
+ <div class="tag-subtree<%= classes.join('') %>" data-tag="<%= tag.id %>">
40
+ <%= layoutTag(tag, level) %>
41
+ <% if (!isLeaf(tag)) { %>
42
+ <%= layoutChildTags(tag, level) %>
43
+ <% } %>
44
+ </div>
45
+ <% } %>
46
+
47
+ <% if (tagTree) { %>
48
+ <widget class="widget-wrapper<%= scrollreveal(' ') %> post-list">
49
+ <div class="widget-header dis-select">
50
+ <span class="name"><%= __('meta.tag_tree') %></span>
51
+ </div>
52
+ <div class="widget-body fs14 tag-tree">
53
+ <%= tagAndSub(tagTree.get(''), 0) %>
54
+ <% for (const child of tagTree.get('').children) { %>
55
+ <%= tagAndSub(tagTree.get(child), 0) %>
56
+ <% } %>
57
+ </div>
58
+ </widget>
59
+ <% } %>
@@ -55,11 +55,13 @@ function layoutDiv(fallback) {
55
55
  el += icon('default:upup')
56
56
  el += `<span>${__('btn.top')}</span>`
57
57
  el += `</a>`
58
- if (page.comments) {
59
- el += `<a class="buttom" onclick="util.scrollComment()">`
60
- el += icon('default:tocomment')
61
- el += `<span>${__('btn.comments')}</span>`
62
- el += `</a>`
58
+ if (theme.comments.service && theme.comments.service.length > 0) {
59
+ if (page.comments == undefined || page.comments != false) {
60
+ el += `<a class="buttom" onclick="util.scrollComment()">`
61
+ el += icon('default:tocomment')
62
+ el += `<span>${__('btn.comments')}</span>`
63
+ el += `</a>`
64
+ }
63
65
  }
64
66
  el += `</div>`
65
67
  el += `</widget>`
@@ -24,9 +24,9 @@
24
24
  autoStart: false,
25
25
  },
26
26
  caption: (fancybox, slide) => {
27
- return slide.triggerEl.alt || null
27
+ return slide.triggerEl.alt || slide.triggerEl.dataset.caption || null
28
28
  }
29
29
  });
30
30
  })
31
31
  }
32
- </script>
32
+ </script>
@@ -1,6 +1,6 @@
1
1
  <%
2
2
  const { field } = theme.plugins.tianli_gpt;
3
- const matchesField = field === 'all' || field === layout || (field === 'wiki' && page.wiki) || (field === 'topic' && page.topic);
3
+ const matchesField = field === 'all' || field === page.layout || (field === 'wiki' && page.wiki) || (field === 'topic' && page.topic);
4
4
  if (matchesField) { %>
5
5
  <script defer src="<%- theme.plugins.tianli_gpt?.js %>"></script>
6
6
  <script defer>
package/layout/layout.ejs CHANGED
@@ -37,7 +37,11 @@ if (theme.style.site && theme.style.site['background-image']) {
37
37
  }
38
38
 
39
39
  var html = `<!DOCTYPE html>`
40
- html += `<html lang="${page.lang}">`
40
+ if (theme.style.prefers_theme === 'auto') {
41
+ html += `<html lang="${page.lang}">`
42
+ } else {
43
+ html += `<html lang="${page.lang}" data-theme="${theme.style.prefers_theme}">`
44
+ }
41
45
  html += partial('_partial/head')
42
46
  html += `<body>`
43
47
  html += site_background
@@ -0,0 +1,8 @@
1
+ <%# 笔记本列表页主体部分 %>
2
+ <% for (const notebook of Object.values(theme.notebooks.tree)) { %>
3
+ <div class="post-list wiki">
4
+ <a class="post-card wiki<%= scrollreveal(' ') %>" href="<%= url_for(notebook.base_dir) %>">
5
+ <%- include('_partial/main/notebook/notebook_card', { notebook: notebook }) %>
6
+ </a>
7
+ </div>
8
+ <% } %>
@@ -0,0 +1,12 @@
1
+ <%# 笔记列表页主体部分 %>
2
+ <div class="post-list post">
3
+ <% page.posts.each(post => { %>
4
+ <a
5
+ class="post-card<%= scrollreveal(' ') %> post"
6
+ href="<%= url_for(post.link || post.path) %><%= page.activeTag ? `?tag=${page.activeTag}` : '' %>"
7
+ >
8
+ <%- include('_partial/main/notebook/note_card', { note: post }) %>
9
+ </a>
10
+ <% }) %>
11
+ </div>
12
+ <%- include('_partial/main/notebook/paginator') %>
package/layout/page.ejs CHANGED
@@ -3,6 +3,13 @@ const { layout } = page
3
3
  // 是否使用 Heti 布局插件
4
4
  const isUsingHeti = theme.plugins.heti?.enable
5
5
 
6
+ const notebook = theme.notebooks.tree[page.notebook]
7
+ if (notebook) {
8
+ page.menu_id ??= notebook.menu_id
9
+ page.license ??= notebook.license
10
+ page.share ??= notebook.share
11
+ }
12
+
6
13
  // 默认的 menu_id
7
14
  if (page.menu_id == null) {
8
15
  if (page.wiki?.length > 0) {
@@ -40,7 +47,10 @@ function layoutDiv() {
40
47
  if (page.content && page.content.length > 0) {
41
48
  el += page.content
42
49
  }
43
- if (layout === 'post') {
50
+ if (notebook) {
51
+ el += partial('_partial/main/notebook/note_tags', { notebook: notebook })
52
+ }
53
+ if (layout === 'post' || page.wiki || notebook) {
44
54
  el += partial('_partial/main/article/article_footer')
45
55
  }
46
56
  el += `</article>`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hexo-theme-stellar",
3
- "version": "1.28.0",
3
+ "version": "1.29.0",
4
4
  "description": "Elegant and powerful theme for Hexo.",
5
5
  "main": "package.json",
6
6
  "scripts": {
@@ -10,6 +10,7 @@ hexo.on('generateBefore', () => {
10
10
  require('./lib/doc_tree')(hexo);
11
11
  require('./lib/topic_tree')(hexo);
12
12
  require('./lib/utils')(hexo);
13
+ require('./lib/notebooks')(hexo);
13
14
  });
14
15
 
15
16
  hexo.on('generateAfter', () => {
@@ -0,0 +1,153 @@
1
+ /**
2
+ * notebooks.js v1
3
+ */
4
+
5
+ 'use strict'
6
+
7
+ class NotePage {
8
+ constructor(page) {
9
+ this.id = page._id
10
+ this.notebook = page.notebook
11
+ this.title = page.title
12
+ this.tags = page.tags
13
+ this.path = page.path
14
+ this.path_key = page.path.replace('.html', '')
15
+ this.layout = page.layout
16
+ this.date = page.date
17
+ this.updated = page.updated || page.date
18
+
19
+ const pin = page.pin ?? page.sticky ?? 0
20
+ if (pin === true) {
21
+ this.pin = 1
22
+ } else if (pin === false) {
23
+ this.pin = 0
24
+ } else {
25
+ this.pin = pin
26
+ }
27
+ }
28
+ }
29
+
30
+ function splitTag(tag) {
31
+ return tag.split('/').filter(t => t.length > 0)
32
+ }
33
+
34
+ function prepareNotebook(id, info, ctx) {
35
+ const notebook = info
36
+ notebook.id = id
37
+
38
+ if (notebook.base_dir) {
39
+ if (notebook.base_dir.startsWith('/')) {
40
+ notebook.base_dir = notebook.base_dir.substring(1)
41
+ }
42
+ if (notebook.base_dir.endsWith('/')) {
43
+ notebook.base_dir = notebook.base_dir.substring(0, notebook.base_dir.length - 1)
44
+ }
45
+ } else {
46
+ const notebooksBaseDir = ctx.theme.config.site_tree.notebooks.base_dir
47
+ notebook.base_dir = notebooksBaseDir ? `${notebooksBaseDir}/${id}` : id
48
+ }
49
+
50
+ notebook.sort ||= 0
51
+ notebook.auto_excerpt ||= ctx.theme.config.notebook.auto_excerpt || 0
52
+ notebook.per_page ??= ctx.theme.config.notebook.per_page ?? ctx.config.per_page ?? 10
53
+ notebook.order_by ||= ctx.theme.config.notebook.order_by || '-updated'
54
+ notebook.menu_id ??= ctx.theme.config.site_tree.notes.menu_id
55
+ notebook.license ??= ctx.theme.config.notebook.license
56
+ notebook.share ??= ctx.theme.config.notebook.share
57
+
58
+ notebook.leftbar ??= ctx.theme.config.site_tree.notes.leftbar
59
+ notebook.rightbar ??= ctx.theme.config.site_tree.notes.rightbar
60
+ notebook.note_leftbar ??= ctx.theme.config.site_tree.note.leftbar
61
+ notebook.note_rightbar ??= ctx.theme.config.site_tree.note.rightbar
62
+
63
+ const tagMap = new Map() // tagId: tagInfo
64
+ notebook.tagTree = tagMap
65
+
66
+ const rootTag = {
67
+ id: '',
68
+ name: '',
69
+ part: '',
70
+ path: notebook.base_dir,
71
+ parent: null, // parent tag id
72
+ childSet: new Set(), // child tag ids
73
+ noteSet: new Set(), // note ids
74
+ }
75
+ tagMap.set(rootTag.id, rootTag)
76
+
77
+ // Iterate through all notes in the notebook, build the tag tree.
78
+ const allPages = ctx.locals.get('pages')
79
+ const pages = allPages.filter(p => p.notebook === notebook.id)
80
+ for (const page of pages.data) {
81
+ rootTag.noteSet.add(page._id)
82
+
83
+ if (!page.tags) {
84
+ continue
85
+ }
86
+
87
+ for (const hierarchyTag of page.tags) {
88
+ const parts = splitTag(hierarchyTag)
89
+ let parent = rootTag
90
+ for (const part of parts) {
91
+ const tagName = parent.name ? `${parent.name}/${part}` : part
92
+ const tagId = tagName.toLowerCase()
93
+ let tag = tagMap.get(tagId)
94
+ if (tag == null) {
95
+ tag = {
96
+ id: tagId,
97
+ name: tagName,
98
+ part: part,
99
+ path: `${notebook.base_dir}/tags/${tagId}`,
100
+ parent: parent.id,
101
+ childSet: new Set(),
102
+ noteSet: new Set(),
103
+ }
104
+ tagMap.set(tagId, tag)
105
+ parent.childSet.add(tagId)
106
+ }
107
+
108
+ tag.noteSet.add(page._id)
109
+ parent = tag
110
+ }
111
+ }
112
+ }
113
+
114
+ notebook.noteMap = pages.map(p => new NotePage(p)).reduce((map, note) => {
115
+ map.set(note.id, note)
116
+ return map
117
+ }, new Map())
118
+
119
+ // Sort child tags for each tag.
120
+ for (const [_, tag] of tagMap) {
121
+ tag.children = Array.from(tag.childSet)
122
+ tag.children.sort()
123
+ }
124
+
125
+ return notebook
126
+ }
127
+
128
+ function getNotebooksObject(ctx) {
129
+ const notebooks = {
130
+ tree: {},
131
+ }
132
+
133
+ const data = ctx.locals.get('data')
134
+ const list = []
135
+ for (const [key, info] of Object.entries(data)) {
136
+ if (!key.startsWith('notebooks/') || key.endsWith('.DS_Store')) {
137
+ continue
138
+ }
139
+ const id = key.substring(10)
140
+ list.push(prepareNotebook(id, info, ctx))
141
+ }
142
+ list.sort((a, b) => a.sort - b.sort)
143
+ for (const info of list) {
144
+ notebooks.tree[info.id] = info
145
+ }
146
+
147
+ return notebooks
148
+ }
149
+
150
+ module.exports = ctx => {
151
+ const notebooks = getNotebooksObject(ctx)
152
+ ctx.theme.config.notebooks = notebooks
153
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * notebooks v1
3
+ */
4
+ const pagination = require('hexo-pagination')
5
+
6
+ function paginationWithEmpty(base, posts, options={}) {
7
+ const { layout, data = {} } = options
8
+ if (posts.length === 0) {
9
+ base = `${base}/`
10
+ return [{
11
+ path: base,
12
+ layout: layout,
13
+ data: {
14
+ ...data,
15
+ base: base,
16
+ total: 1,
17
+ current: 1,
18
+ current_url: base,
19
+ posts: posts,
20
+ prev: 0,
21
+ prev_link: '',
22
+ next: 0,
23
+ next_link: '',
24
+ }
25
+ }]
26
+ } else {
27
+ return pagination(base, posts, options)
28
+ }
29
+ }
30
+
31
+ hexo.extend.generator.register('notebooks', function (locals) {
32
+ const { site_tree, notebooks } = hexo.theme.config
33
+ if (notebooks.tree.length === 0) {
34
+ return []
35
+ }
36
+
37
+ const routes = []
38
+
39
+ // The index page of all notebooks.
40
+ routes.push({
41
+ path: site_tree.notebooks.base_dir + '/index.html',
42
+ layout: ['notebooks'],
43
+ data: {
44
+ layout: 'notebooks',
45
+ menu_id: site_tree.notebooks.menu_id,
46
+ }
47
+ })
48
+
49
+ for (const notebook of Object.values(notebooks.tree)) {
50
+ const pages = locals.pages.filter(p => notebook.noteMap.has(p._id)).sort(notebook.order_by)
51
+ pages.data.sort((a, b) => notebook.noteMap.get(b._id).pin - notebook.noteMap.get(a._id).pin)
52
+
53
+ // Note list pages (for every tag) of current notebook.
54
+ for (const [_, tag] of notebook.tagTree) {
55
+ const notes = pages.filter(p => tag.noteSet.has(p._id))
56
+ const slices = paginationWithEmpty(tag.path, notes, {
57
+ perPage: notebook.per_page,
58
+ layout: ['notes'],
59
+ data: {
60
+ layout: 'notes',
61
+ menu_id: notebook.menu_id,
62
+ notebook: notebook.id,
63
+ activeTag: tag.id,
64
+ }
65
+ })
66
+ routes.push(...slices)
67
+ }
68
+ }
69
+
70
+ return routes
71
+ })
@@ -43,4 +43,9 @@ hexo.extend.tag.register('hashtag', require('./lib/hashtag')(hexo))
43
43
  hexo.extend.tag.register('okr', require('./lib/okr')(hexo), {ends: true})
44
44
  hexo.extend.tag.register('audio', require('./lib/audio')(hexo))
45
45
  hexo.extend.tag.register('video', require('./lib/video')(hexo))
46
+
47
+ // read 阅读类
48
+ hexo.extend.tag.register('reel', require('./lib/read/reel')(hexo), true)
49
+ hexo.extend.tag.register('paper', require('./lib/read/paper')(hexo), true)
50
+
46
51
  // others