[feat] add notebook support (#464)
This commit is contained in:
parent
376a3d8a2e
commit
589efabefb
47
_config.yml
47
_config.yml
|
@ -98,6 +98,29 @@ site_tree:
|
|||
menu_id: wiki # 未在 front-matter 中指定 menu_id 时,layout 为 wiki 的页面默认使用这里配置的 menu_id
|
||||
leftbar: tree, related, recent # for wiki
|
||||
rightbar: ghrepo, toc
|
||||
# 笔记本列表页配置
|
||||
notebooks:
|
||||
base_dir: notebooks # 笔记本列表页的路径。以及未指定 base_dir 的笔记本的路径前缀。
|
||||
menu_id: notebooks # 笔记本列表页高亮的主导航栏菜单按钮。
|
||||
# 笔记本列表页的左侧栏和右侧栏。
|
||||
leftbar: recent # recent within all notebooks
|
||||
rightbar: null
|
||||
# 笔记列表页配置
|
||||
notes:
|
||||
# 笔记列表页和笔记页高亮的主导航栏菜单按钮。
|
||||
# 可以在笔记本 yaml 的 menu_id 字段中覆盖此参数。
|
||||
# 可以在笔记的 front-matter/menu_id 中覆盖此参数。
|
||||
menu_id: notebooks
|
||||
# 笔记列表页的左侧栏和右侧栏。可以在笔记本 yaml 的 leftbar 和 rightbar 字段中覆盖此参数。
|
||||
leftbar: tagtree, recent # recent of current notebook
|
||||
rightbar: null
|
||||
# 笔记页配置
|
||||
note:
|
||||
# 笔记页的左侧栏和右侧栏
|
||||
# 可以在笔记本 yaml 的 note_leftbar 和 note_rightbar 字段中覆盖此参数。
|
||||
# 可以在笔记的 front-matter/leftbar 和 rightbar 字段中覆盖此参数。
|
||||
leftbar: tagtree, recent # recent of current notebook
|
||||
rightbar: toc
|
||||
# 作者信息配置
|
||||
author:
|
||||
base_dir: author # 只影响自动生成的页面路径
|
||||
|
@ -116,6 +139,30 @@ site_tree:
|
|||
rightbar: toc
|
||||
|
||||
|
||||
######## Notebook ########
|
||||
notebook:
|
||||
# 如果没有指定 excerpt 和 description,将自动取多长的内容作为文章摘要。
|
||||
auto_excerpt: 128
|
||||
# 可以为某个 tag 设定图标(显示在标签树中)。
|
||||
tagcons:
|
||||
'': solar:hashtag-linear
|
||||
# 每页显示多少篇笔记。0 表示不分页,null 则 fallback 到 hexo 的配置。
|
||||
# 可以在笔记本 yaml 的 per_page 字段中覆盖此参数。
|
||||
per_page: null
|
||||
# 笔记的排序方式。默认按照 updated 降序排序。
|
||||
# 可以在笔记本 yaml 的 order_by 字段中覆盖此参数。
|
||||
# 注意:置顶的笔记会始终排在最前面。
|
||||
# 在 front-matter 中设置 pin:true|number 或 sticky:true|number 来置顶。
|
||||
order_by: -updated
|
||||
# 是否在笔记页面显示许可协议。false 表示不显示。true 表示沿用主题许可协议内容。也可以给定具体的文本指定协议内容。
|
||||
# 可以在笔记本 yaml 的 license 字段中覆盖此参数。
|
||||
# 可以在笔记的 front-matter/license 中覆盖此参数。
|
||||
license: false
|
||||
# 是否在笔记页面显示分享按钮。
|
||||
# 可以在笔记本 yaml 的 share 字段中覆盖此参数。
|
||||
# 可以在笔记的 front-matter/share 中覆盖此参数。
|
||||
share: false
|
||||
|
||||
|
||||
######## Article ########
|
||||
article:
|
||||
|
|
|
@ -7,7 +7,8 @@ solar:documents-bold-duotone: <svg xmlns="http://www.w3.org/2000/svg" width="32"
|
|||
solar:chat-square-like-bold-duotone: <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="m13.629 20.472l-.542.916c-.483.816-1.69.816-2.174 0l-.542-.916c-.42-.71-.63-1.066-.968-1.262c-.338-.197-.763-.204-1.613-.219c-1.256-.021-2.043-.098-2.703-.372a5 5 0 0 1-2.706-2.706C2 14.995 2 13.83 2 11.5v-1c0-3.273 0-4.91.737-6.112a5 5 0 0 1 1.65-1.651C5.59 2 7.228 2 10.5 2h3c3.273 0 4.91 0 6.113.737a5 5 0 0 1 1.65 1.65C22 5.59 22 7.228 22 10.5v1c0 2.33 0 3.495-.38 4.413a5 5 0 0 1-2.707 2.706c-.66.274-1.447.35-2.703.372c-.85.015-1.275.022-1.613.219c-.338.196-.548.551-.968 1.262" opacity=".5"/><path fill="currentColor" d="M10.99 14.308c-1.327-.978-3.49-2.84-3.49-4.593c0-2.677 2.475-3.677 4.5-1.609c2.025-2.068 4.5-1.068 4.5 1.609c0 1.752-2.163 3.615-3.49 4.593c-.454.335-.681.502-1.01.502c-.329 0-.556-.167-1.01-.502"/></svg>
|
||||
solar:planet-bold-duotone: <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M20 12a8 8 0 1 1-16 0a8 8 0 0 1 16 0" opacity=".5"/><path fill="currentColor" d="M17.712 5.453c1.047-.193 2.006-.259 2.797-.152c.77.103 1.536.393 1.956 1.064c.446.714.312 1.542-.012 2.258c-.33.728-.918 1.499-1.672 2.268c-1.516 1.547-3.836 3.226-6.597 4.697c-2.763 1.472-5.495 2.484-7.694 2.92c-1.095.217-2.098.299-2.923.201c-.8-.095-1.6-.383-2.032-1.075c-.47-.752-.296-1.63.07-2.379c.375-.768 1.032-1.586 1.872-2.403L4 12.416c0 .219.083.71.168 1.146c.045.23.09.444.123.596c-.652.666-1.098 1.263-1.339 1.756c-.277.567-.208.825-.145.925c.072.116.305.305.937.38c.609.073 1.44.018 2.455-.183c2.02-.4 4.613-1.351 7.28-2.772c2.667-1.42 4.85-3.015 6.23-4.423c.694-.707 1.15-1.334 1.377-1.836c.233-.515.167-.75.107-.844c-.07-.112-.289-.294-.883-.374c-.542-.072-1.272-.041-2.163.112L16.87 5.656c.338-.101.658-.17.842-.203"/></svg>
|
||||
solar:notebook-bookmark-bold-duotone: <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" fill-rule="evenodd" d="M14.25 4.48v3.057c0 .111 0 .27.02.406a.936.936 0 0 0 .445.683a.96.96 0 0 0 .783.072c.13-.04.272-.108.378-.159L17 8.005l1.124.534c.106.05.248.119.378.16a.958.958 0 0 0 .783-.073a.936.936 0 0 0 .444-.683c.021-.136.021-.295.021-.406V3.031c.113-.005.224-.01.332-.013C21.154 2.98 22 3.86 22 4.933v11.21c0 1.112-.906 2.01-2.015 2.08c-.97.06-2.108.179-2.985.41c-1.082.286-1.99 1.068-3.373 1.436c-.626.167-1.324.257-1.627.323V5.174c.32-.079 1.382-.203 1.674-.371c.184-.107.377-.216.576-.323m5.478 8.338a.75.75 0 0 1-.546.91l-4 1a.75.75 0 0 1-.364-1.456l4-1a.75.75 0 0 1 .91.546" clip-rule="evenodd"/><path fill="currentColor" d="M18.25 3.151c-.62.073-1.23.18-1.75.336a8.2 8.2 0 0 0-.75.27v3.182l.75-.356l.008-.005a1.13 1.13 0 0 1 .492-.13c.047 0 .094.004.138.01c.175.029.315.1.354.12l.009.005l.749.356V3.647z"/><path fill="currentColor" d="M12 5.214c-.334-.064-1.057-.161-1.718-.339C8.938 4.515 8.05 3.765 7 3.487c-.887-.234-2.041-.352-3.018-.412C2.886 3.007 2 3.9 2 4.998v11.146c0 1.11.906 2.01 2.015 2.079c.97.06 2.108.179 2.985.41c.486.129 1.216.431 1.873.726c1.005.451 2.052.797 3.127 1.034z" opacity=".5"/><path fill="currentColor" d="M4.273 12.818a.75.75 0 0 1 .91-.545l4 1a.75.75 0 1 1-.365 1.455l-4-1a.75.75 0 0 1-.545-.91m.909-4.545a.75.75 0 1 0-.364 1.455l4 1a.75.75 0 0 0 .364-1.455z"/></svg>
|
||||
|
||||
solar:pin-linear: <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path d="m15.9894 4.9502.5306-.53006zm3.0822 3.08542-.5306.53006zm-10.33323 11.39338-.5306.5301zm-4.11668-4.1209.5306-.53zm12.94521-.3138-.2637-.7021zm-1.9171.7203.2638.7021zm-7.3176-7.33283-.70422-.25802zm.69445-1.89541.70422.25802zm-3.18161 4.18714.19934.723zm1.45663-.5384-.43821-.60864zm.37415-.34472.57083.48642zm6.57518 6.59012.491.567zm-.8704 1.8207-.7232-.199zm.5363-1.4546-.6094-.4372zm-11.19844-3.9914-.74998.0048zm.21199-.8031-.64992-.3743zm8.37475 9.391.0012-.75zm.7881-.2084-.3718-.6514zm-.396-19.09864.1615.73239zm-10.2279 19.43384c-.29274.2931-.2925.768.00054 1.0607s.76792.2925 1.06066-.0005zm5.71443-3.5978c.29274-.293.2925-.7679-.00054-1.0607-.29305-.2927-.76792-.2925-1.06066.0006zm8.27497-12.39184 3.0822 3.08542 1.0612-1.06012-3.0822-3.08542zm-6.18983 13.41864-4.11668-4.1208-1.0612 1.0601 4.11668 4.1209zm8.03423-4.6067-1.9172.7203.5276 1.4042 1.9171-.7203zm-8.26678-5.65241.69445-1.89541-1.40844-.51604-.69445 1.89541zm-2.99204 2.75671c.71145-.1962 1.25281-.334 1.69549-.6527l-.87641-1.21734c-.17293.12451-.39745.19782-1.21776.42398zm1.5836-3.27275c-.29296.79957-.3846 1.01778-.52299 1.18016l1.14166.97289c.35376-.41505.53565-.94343.78977-1.63701zm.11189 2.62005c.18709-.1347.35725-.2915.50678-.467l-1.14166-.97289c-.07129.08366-.15239.15838-.24153.22255zm7.64613 4.2687c-.689.2589-1.2144.4446-1.626.801l.982 1.134c.1608-.1393.3772-.2323 1.1716-.5308zm-1.2823 3.3876c.2253-.8188.2984-1.0432.4226-1.2163l-1.2188-.8744c-.3173.4423-.4546.9825-.6501 1.6928zm-.3437-2.5866c-.1697.147-.3216.3134-.4525.4959l1.2188.8744c.0624-.087.1348-.1663.2157-.2363zm-8.60771-1.0354c-.64614-.6468-1.0843-1.0871-1.36863-1.4443-.28531-.3585-.31477-.5137-.31521-.5833l-1.49997.0095c.00367.5822.29279 1.0697.64154 1.5079.34974.4394.86113.9497 1.48107 1.5703zm.49341-4.82766c-.84522.23306-1.54174.42386-2.06274.63306-.51955.2085-1.0118.4897-1.30243.9943l1.29985.7486c.03462-.0601.13655-.1804.56142-.3509.42343-.17 1.02157-.3361 1.90258-.579zm-2.17725 2.80006c-.00094-.1489.03771-.2952.11193-.4241l-1.29985-.7486c-.20699.3594-.31467.7674-.31205 1.1822zm4.73932 7.2086c.62387.6245 1.13687 1.1396 1.5787 1.4915.44053.3509.93143.6416 1.51763.6425l.0023-1.5c-.0695-.0001-.225-.0287-.5854-.3158-.3592-.2861-.80177-.7274-1.45203-1.3784zm4.44963-1.9569c-.2441.8875-.4112 1.4902-.5823 1.9166-.1718.4279-.293.5299-.3536.5645l.7435 1.3028c.509-.2905.7923-.7857 1.0021-1.3086.2105-.5243.4023-1.2259.6366-2.0774zm-1.3533 4.0909c.4071.0006.8073-.1052 1.1609-.307l-.7435-1.3028c-.1265.0722-.2696.11-.4151.1098zm7.2369-13.52742c1.0635 1.06454 1.7993 1.80382 2.2507 2.41312.4436.5986.4946.9171.4404 1.1676l1.4661.317c.1899-.878-.16-1.6472-.7013-2.3777-.5334-.71978-1.3664-1.55084-2.3947-2.58014zm-.7103 7.13072c1.3614-.5115 2.4633-.9234 3.2464-1.358.7947-.4411 1.4312-.9968 1.6211-1.875l-1.4661-.317c-.0541.2504-.232.5191-.883.8804-.6626.3678-1.6379.7364-3.0459 1.2654zm-1.3107-11.27626c-1.0359-1.03694-1.8719-1.87661-2.5954-2.41376-.7338-.54473-1.5071-.89726-2.3889-.70271l.3231 1.46478c.2498-.0551.5689-.00515 1.1716.4423.6129.45504 1.3572 1.19726 2.4284 2.26951zm-6.78913 2.32424c.52163-1.42363.88523-2.41035 1.25033-3.08123.359-.65977.6278-.83958.8776-.8947l-.3231-1.46478c-.8817.19452-1.4352.83965-1.87208 1.64251-.43084.79175-.83674 1.90536-1.34119 3.28216zm-7.20027 15.78572 4.65323-4.658-1.0612-1.0601-4.65323 4.6579z" fill="currentColor"/></svg>
|
||||
solar:hashtag-linear: <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><g stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"><path d="m10 3-5 18"/><path d="m19 3-5 18"/><path d="m22 9h-18"/><path d="m20 16h-18"/></g></svg>
|
||||
|
||||
default:goback: <svg aria-hidden="true" viewBox="0 0 16 16" fill="currentColor"><path fill-rule="evenodd" d="M7.78 12.53a.75.75 0 01-1.06 0L2.47 8.28a.75.75 0 010-1.06l4.25-4.25a.75.75 0 011.06 1.06L4.81 7h7.44a.75.75 0 010 1.5H4.81l2.97 2.97a.75.75 0 010 1.06z"></path></svg>
|
||||
|
||||
|
|
|
@ -70,3 +70,9 @@ timeline:
|
|||
user: # 默认显示所有人的数据,设置名称可过滤为仅显示某人的数据,多个名称用英文逗号隔开,不要加空格
|
||||
type: # 默认不用写,如果是友链朋友圈数据请写 fcircle
|
||||
limit: # 默认通过 api 上增加 per_page 来设置,如果是友链朋友圈,可通过这个设置数量
|
||||
|
||||
tagtree:
|
||||
layout: tagtree
|
||||
expand_all: false # 是否展开所有节点
|
||||
expand_active: true # 是否展开当前节点
|
||||
show_tagcon: true # 是否显示标签 icon
|
||||
|
|
|
@ -3,6 +3,7 @@ btn:
|
|||
blog: Blog
|
||||
wiki: Wiki
|
||||
topic: Topic
|
||||
notebook: Notebook
|
||||
recent_publish: Recent
|
||||
all_wiki: All Products
|
||||
category: Category
|
||||
|
@ -18,6 +19,8 @@ btn:
|
|||
|
||||
meta:
|
||||
recent_update: Recent Update
|
||||
tag_tree: Tags
|
||||
all_notes: All Notes
|
||||
toc: On This Page
|
||||
read_next: READ NEXT
|
||||
prev: Prev
|
||||
|
|
|
@ -3,6 +3,7 @@ btn:
|
|||
blog: 文章
|
||||
wiki: 文档
|
||||
topic: 专栏
|
||||
notebook: 笔记本
|
||||
recent_publish: 近期发布
|
||||
all_wiki: 所有项目
|
||||
category: 分类
|
||||
|
@ -18,6 +19,8 @@ btn:
|
|||
|
||||
meta:
|
||||
recent_update: 最近更新
|
||||
tag_tree: 标签
|
||||
all_notes: 所有笔记
|
||||
toc: 本文目录
|
||||
read_next: 接下来阅读
|
||||
prev: 回顾上一篇
|
||||
|
|
|
@ -3,6 +3,7 @@ btn:
|
|||
blog: 網誌
|
||||
wiki: 文檔
|
||||
topic: 專欄
|
||||
notebook: 筆記本
|
||||
recent_publish: 近期發布
|
||||
all_wiki: 所有文檔
|
||||
category: 分類
|
||||
|
@ -18,6 +19,8 @@ btn:
|
|||
|
||||
meta:
|
||||
recent_update: 最近更新
|
||||
tag_tree: 標籤
|
||||
all_notes: 所有筆記
|
||||
toc: 本文目錄
|
||||
read_next: 接下來閱讀
|
||||
prev: 回顧上一篇
|
||||
|
|
|
@ -53,8 +53,10 @@ function layoutDiv() {
|
|||
if (theme.article.license && (page.license != false)) {
|
||||
license = page.license || theme.article.license
|
||||
}
|
||||
} else if (page.license) {
|
||||
license = page.license === true ? theme.article.license : page.license
|
||||
}
|
||||
if (license.length > 0) {
|
||||
if (license?.length > 0) {
|
||||
var author = null
|
||||
if (theme.authors) {
|
||||
if (page.author?.length > 0 && theme.authors[page.author] != null) {
|
||||
|
@ -84,6 +86,8 @@ function layoutDiv() {
|
|||
}
|
||||
} else if (page.layout == 'post') {
|
||||
showSharePlugin = page.share != false
|
||||
} else if (page.share) {
|
||||
showSharePlugin = true
|
||||
}
|
||||
if (theme.article.share && showSharePlugin) {
|
||||
function socialButtons() {
|
||||
|
|
|
@ -34,6 +34,8 @@ function layoutBreadcrumb() {
|
|||
el += `<a class="cap breadcrumb" href="${url_for(config.root)}">${__("btn.home")}</a>`
|
||||
if (theme.wiki.tree[page.wiki]) {
|
||||
el += partial('breadcrumb/wiki')
|
||||
} else if (page.notebook) {
|
||||
el += partial('breadcrumb/note')
|
||||
} else if (page.layout == 'post') {
|
||||
el += partial('breadcrumb/blog')
|
||||
} else {
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
<%# 笔记页面的面包屑 %>
|
||||
<span class="sep"></span>
|
||||
<a class="cap breadcrumb" href="<%= url_for(theme.site_tree.notebooks.base_dir) %>"><%= __('btn.notebook') %></a>
|
||||
<% const notebook = theme.notebooks.tree[page.notebook] %>
|
||||
<% if (notebook) { %>
|
||||
<span class="sep"></span>
|
||||
<a class="cap breadcrumb" href="<%= url_for(notebook.base_dir) %>"><%= notebook.name || notebook.title %></a>
|
||||
<% } %>
|
|
@ -7,6 +7,17 @@ function layoutDiv() {
|
|||
el += `<span class="text created">${__("meta.updated") + __("symbol.colon")}`
|
||||
el += `<time datetime="${date_xml(page.updated)}">${date(page.updated, config.date_format)}</time>`
|
||||
el += `</span>`
|
||||
} else if (page.notebook) {
|
||||
// 更新日期
|
||||
el += `<span class="text created">${__("meta.updated") + __("symbol.colon")}`
|
||||
el += `<time datetime="${date_xml(page.updated)}">${date(page.updated, config.date_format)}</time>`
|
||||
el += `</span>`
|
||||
// 发布日期
|
||||
el += `<span class="sep updated"></span>`
|
||||
el += `<span class="text updated">`
|
||||
el += `${__("meta.created") + __("symbol.colon")}`
|
||||
el += `<time datetime="${date_xml(page.date)}">${date(page.date, config.date_format)}</time>`
|
||||
el += `</span>`
|
||||
} else {
|
||||
const author = theme.authors ? (theme.authors[page.author] || theme.default_author) : null
|
||||
if (author) {
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
<%# 笔记卡片 %>
|
||||
<article class="md-text">
|
||||
<% if (note.cover) { %>
|
||||
<div class="post-cover">
|
||||
<img src="<%= note.cover %>" alt="cover" />
|
||||
</div>
|
||||
<% } %>
|
||||
<h2 class="post-title"><%= note.title %></h2>
|
||||
<div class="excerpt<% if (theme.plugins.heti?.enable) { %> heti<% } %>">
|
||||
<p>
|
||||
<% if (note.excerpt) { %>
|
||||
<%= strip_html(note.excerpt) %>
|
||||
<% } else if (note.description) { %>
|
||||
<%= note.description %>
|
||||
<% } else if (note.content && theme.notebook.auto_excerpt > 0) { %>
|
||||
<%= truncate(strip_html(note.content), { length: theme.notebook.auto_excerpt }) %>
|
||||
<% } %>
|
||||
</p>
|
||||
</div>
|
||||
<div class="meta cap">
|
||||
<% if (note.pin) { %>
|
||||
<span class="pin"><%- icon('solar:pin-linear') %></span>
|
||||
<% } %>
|
||||
<span class="cap" id="post-meta">
|
||||
<%- icon('default:calendar') %>
|
||||
<time datetime="<%= date_xml(note.updated || note.date) %>"><%= date(note.updated || note.date, config.date_format) %></time>
|
||||
</span>
|
||||
<% if (note.tags) { %>
|
||||
<% note.tags.forEach((tag, i) => { %>
|
||||
<span class="cap breadcrumb" %>>
|
||||
<span class="tag"><%= tag %></span>
|
||||
</span>
|
||||
<% }) %>
|
||||
<% } %>
|
||||
</div>
|
||||
</article>
|
|
@ -0,0 +1,9 @@
|
|||
<%# 笔记的所属标签 %>
|
||||
<% if (page.tags) { %>
|
||||
<div class="tag-list">
|
||||
<% for (const t of page.tags) { %>
|
||||
<% const tag = notebook.tagTree.get(t.toLowerCase()) %>
|
||||
<a class="tag" href="<%= url_for(tag.path) %>"><%= tag.name %></a>
|
||||
<% } %>
|
||||
</div>
|
||||
<% } %>
|
|
@ -0,0 +1,10 @@
|
|||
<%# 笔记本信息卡片 %>
|
||||
<article class="md-text">
|
||||
<div class="preview"><img src="<%= notebook.icon || theme.default.project %>" alt="icon"/></div>
|
||||
<div class="excerpt">
|
||||
<h2 class="post-title"><%= notebook.title || notebook.name %></h2>
|
||||
<% if (notebook.description) { %>
|
||||
<p><%= notebook.description %></p>
|
||||
<% } %>
|
||||
</div>
|
||||
</article>
|
|
@ -0,0 +1,9 @@
|
|||
<% if ((page.total || 0) > 1) { %>
|
||||
<div class="paginator-wrap dis-select<%= scrollreveal(' ') %>">
|
||||
<%- paginator({
|
||||
prev_text: '',
|
||||
next_text: '',
|
||||
force_prev_next: true,
|
||||
}) %>
|
||||
</div>
|
||||
<% } %>
|
|
@ -18,6 +18,7 @@ function custom_inject() {
|
|||
<%- partial('scripts/defines') %>
|
||||
<%- partial('scripts/utils') %>
|
||||
<%- partial('scripts/sidebar') %>
|
||||
<%- partial('scripts/tagtree') %>
|
||||
|
||||
<!-- required -->
|
||||
<script src="<%- `${theme.stellar.main_js}?v=${stellar_info('version')}` %>" async></script>
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
<script type="text/javascript">
|
||||
(() => {
|
||||
const tagSwitchers = document.querySelectorAll('.tag-subtree.parent-tag > a > .tag-switcher-wrapper')
|
||||
for (const tagSwitcher of tagSwitchers) {
|
||||
tagSwitcher.addEventListener('click', (e) => {
|
||||
const parent = e.target.closest('.tag-subtree.parent-tag')
|
||||
parent.classList.toggle('expanded')
|
||||
e.preventDefault()
|
||||
})
|
||||
}
|
||||
|
||||
// Get active tag from query string, then activate it.
|
||||
const urlParams = new URLSearchParams(window.location.search)
|
||||
const activeTag = urlParams.get('tag')
|
||||
if (activeTag) {
|
||||
let tag = document.querySelector(`.tag-subtree[data-tag="${activeTag}"]`)
|
||||
if (tag) {
|
||||
tag.querySelector('a').classList.add('active')
|
||||
<% if (!theme.widgets.tagtree.expand_active) { %>
|
||||
tag = tag.parentElement.closest('.tag-subtree.parent-tag')
|
||||
<% } %>
|
||||
while (tag) {
|
||||
tag.classList.add('expanded')
|
||||
tag = tag.parentElement.closest('.tag-subtree.parent-tag')
|
||||
}
|
||||
}
|
||||
}
|
||||
})()
|
||||
</script>
|
|
@ -2,12 +2,22 @@
|
|||
|
||||
const wiki = theme.wiki.tree[page.wiki]
|
||||
const topic = theme.topic.tree[page.topic]
|
||||
const notebook = theme.notebooks.tree[page.notebook]
|
||||
|
||||
if (page.leftbar == null) {
|
||||
const { site_tree } = theme
|
||||
var sidebar
|
||||
if (is_home()) {
|
||||
sidebar = site_tree.home.leftbar
|
||||
} else if (page.layout === 'notebooks') {
|
||||
// 笔记本列表页
|
||||
sidebar = site_tree.notebooks.leftbar
|
||||
} else if (page.layout === 'notes') {
|
||||
// 笔记列表页
|
||||
sidebar = notebook ? notebook.leftbar : site_tree.notes.leftbar
|
||||
} else if (notebook) {
|
||||
// 笔记本文章内页
|
||||
sidebar = page.leftbar ?? notebook.note_leftbar
|
||||
} else if (is_category() || is_tag() || is_archive() || ['categories', 'tags', 'archives'].includes(page.layout)) {
|
||||
sidebar = site_tree.index_blog.leftbar
|
||||
} else if (page.layout === 'index_topic') {
|
||||
|
|
|
@ -2,12 +2,22 @@
|
|||
|
||||
const wiki = theme.wiki.tree[page.wiki]
|
||||
const topic = theme.topic.tree[page.topic]
|
||||
const notebook = theme.notebooks.tree[page.notebook]
|
||||
|
||||
if (page.rightbar == null) {
|
||||
const { site_tree } = theme
|
||||
var sidebar
|
||||
if (is_home()) {
|
||||
sidebar = site_tree.home.rightbar
|
||||
} else if (page.layout === 'notebooks') {
|
||||
// 笔记本列表页
|
||||
sidebar = site_tree.notebooks.rightbar
|
||||
} else if (page.layout === 'notes') {
|
||||
// 笔记列表页
|
||||
sidebar = notebook ? notebook.rightbar : site_tree.notes.rightbar
|
||||
} else if (notebook) {
|
||||
// 笔记本文章内页
|
||||
sidebar = page.rightbar ?? notebook.note_rightbar
|
||||
} else if (is_category() || is_tag() || is_archive() || ['categories', 'tags', 'archives'].includes(page.layout)) {
|
||||
sidebar = site_tree.index_blog.rightbar
|
||||
} else if (page.layout === 'index_topic') {
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
<%
|
||||
const notebook = theme.notebooks.tree[page.notebook]
|
||||
if (item.filter == null) {
|
||||
item.filter = 'auto'
|
||||
}
|
||||
if (item.placeholder == null && item.filter == 'auto') {
|
||||
if (theme.wiki.tree[page.wiki]?.name) {
|
||||
item.placeholder = __('search.search_in', theme.wiki.tree[page.wiki]?.name)
|
||||
} else if (notebook) {
|
||||
item.placeholder = __('search.search_in', notebook.name || __('btn.notebook'))
|
||||
item.filter = notebook.base_dir
|
||||
}
|
||||
}
|
||||
function layoutDiv() {
|
||||
|
|
|
@ -21,6 +21,10 @@ function layoutDiv() {
|
|||
return false
|
||||
})
|
||||
arr = arr.sort((p1, p2) => p1.updated > p2.updated ? -1 : 1)
|
||||
} else if (page.layout === 'notebooks') {
|
||||
arr = site.pages.filter(p => p.notebook).sort('-updated')
|
||||
} else if (page.notebook) {
|
||||
arr = site.pages.filter(p => p.notebook === page.notebook).sort('-updated')
|
||||
} else {
|
||||
arr = site.posts.filter( p => p.title && p.title.length > 0)
|
||||
arr = arr.sort("updated", -1)
|
||||
|
@ -38,6 +42,12 @@ function layoutDiv() {
|
|||
if (name) {
|
||||
el += '<strong>' + name + '</strong>' + '<span class="dot"></span>';
|
||||
}
|
||||
} else if (page.layout === 'notebooks') {
|
||||
const notebook = theme.notebooks.tree[post.notebook]
|
||||
const name = notebook?.name || post.notebook
|
||||
if (name) {
|
||||
el += '<strong>' + name + '</strong>' + '<span class="dot"></span>';
|
||||
}
|
||||
}
|
||||
el += post.title + '</span>';
|
||||
el += '</a>';
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
<%# 笔记本的标签树 %>
|
||||
<% const notebook = theme.notebooks.tree[page.notebook] %>
|
||||
<% const tagTree = notebook?.tagTree %>
|
||||
<% const isLeaf = tag => tag.id === '' || tag.children.length === 0 %>
|
||||
<% const getTagcon = tag => {
|
||||
const tagcons = theme.notebook.tagcons || {}
|
||||
return tagcons[tag.name] || tagcons[tag.id] || tagcons[tag.part] || tagcons[tag.part.toLowerCase()] || tagcons['']
|
||||
} %>
|
||||
|
||||
<% function layoutTag(tag, level) { %>
|
||||
<% const active = page.activeTag === tag.id %>
|
||||
<a class="link<%= active ? ' active' : '' %>" href="<%= url_for(tag.path) %>">
|
||||
<span class="toc-text"<% if (level > 0) { %> style="padding-left: <%= level * 0.875 %>rem;"<% } %>>
|
||||
<% if (tag.id === '') { %>
|
||||
<%= __('meta.all_notes') %>
|
||||
<% } else { %>
|
||||
<% const tagcon = item.show_tagcon && getTagcon(tag) %>
|
||||
<% if (tagcon) { %><span class="tagcon"><%- icon(tagcon) %></span><% } %>
|
||||
<%= tag.part %>
|
||||
<% } %>
|
||||
</span>
|
||||
<span class="toc-text tag-switcher-wrapper">
|
||||
<span class="tag-switcher" />
|
||||
</span>
|
||||
</a>
|
||||
<% } %>
|
||||
|
||||
<% function layoutChildTags(tag, level) { %>
|
||||
<% for (const child of tag.children) { %>
|
||||
<%= tagAndSub(tagTree.get(child), level + 1) %>
|
||||
<% } %>
|
||||
<% } %>
|
||||
|
||||
<% function tagAndSub(tag, level) { %>
|
||||
<% if (!tag) return '' %>
|
||||
<% const active = page.activeTag === tag.id %>
|
||||
<% const expanded = item.expand_all || (item.expand_active && active) || page.activeTag?.startsWith(`${tag.id}/`) %>
|
||||
<% const classes = [isLeaf(tag) ? ' leaf-tag' : ' parent-tag', expanded ? ' expanded' : ''] %>
|
||||
<div class="tag-subtree<%= classes.join('') %>" data-tag="<%= tag.id %>">
|
||||
<%= layoutTag(tag, level) %>
|
||||
<% if (!isLeaf(tag)) { %>
|
||||
<%= layoutChildTags(tag, level) %>
|
||||
<% } %>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<% if (tagTree) { %>
|
||||
<widget class="widget-wrapper<%= scrollreveal(' ') %> post-list">
|
||||
<div class="widget-header dis-select">
|
||||
<span class="name"><%= __('meta.tag_tree') %></span>
|
||||
</div>
|
||||
<div class="widget-body fs14 tag-tree">
|
||||
<%= tagAndSub(tagTree.get(''), 0) %>
|
||||
<% for (const child of tagTree.get('').children) { %>
|
||||
<%= tagAndSub(tagTree.get(child), 0) %>
|
||||
<% } %>
|
||||
</div>
|
||||
</widget>
|
||||
<% } %>
|
|
@ -0,0 +1,8 @@
|
|||
<%# 笔记本列表页主体部分 %>
|
||||
<% for (const notebook of Object.values(theme.notebooks.tree)) { %>
|
||||
<div class="post-list wiki">
|
||||
<a class="post-card wiki<%= scrollreveal(' ') %>" href="<%= url_for(notebook.base_dir) %>">
|
||||
<%- include('_partial/main/notebook/notebook_card', { notebook: notebook }) %>
|
||||
</a>
|
||||
</div>
|
||||
<% } %>
|
|
@ -0,0 +1,12 @@
|
|||
<%# 笔记列表页主体部分 %>
|
||||
<div class="post-list post">
|
||||
<% page.posts.each(post => { %>
|
||||
<a
|
||||
class="post-card<%= scrollreveal(' ') %> post"
|
||||
href="<%= url_for(post.link || post.path) %><%= page.activeTag ? `?tag=${page.activeTag}` : '' %>"
|
||||
>
|
||||
<%- include('_partial/main/notebook/note_card', { note: post }) %>
|
||||
</a>
|
||||
<% }) %>
|
||||
</div>
|
||||
<%- include('_partial/main/notebook/paginator') %>
|
|
@ -3,6 +3,13 @@ const { layout } = page
|
|||
// 是否使用 Heti 布局插件
|
||||
const isUsingHeti = theme.plugins.heti?.enable
|
||||
|
||||
const notebook = theme.notebooks.tree[page.notebook]
|
||||
if (notebook) {
|
||||
page.menu_id ??= notebook.menu_id
|
||||
page.license ??= notebook.license
|
||||
page.share ??= notebook.share
|
||||
}
|
||||
|
||||
// 默认的 menu_id
|
||||
if (page.menu_id == null) {
|
||||
if (page.wiki?.length > 0) {
|
||||
|
@ -40,7 +47,10 @@ function layoutDiv() {
|
|||
if (page.content && page.content.length > 0) {
|
||||
el += page.content
|
||||
}
|
||||
if (layout === 'post' || page.wiki) {
|
||||
if (notebook) {
|
||||
el += partial('_partial/main/notebook/note_tags', { notebook: notebook })
|
||||
}
|
||||
if (layout === 'post' || page.wiki || notebook) {
|
||||
el += partial('_partial/main/article/article_footer')
|
||||
}
|
||||
el += `</article>`
|
||||
|
|
|
@ -10,6 +10,7 @@ hexo.on('generateBefore', () => {
|
|||
require('./lib/doc_tree')(hexo);
|
||||
require('./lib/topic_tree')(hexo);
|
||||
require('./lib/utils')(hexo);
|
||||
require('./lib/notebooks')(hexo);
|
||||
});
|
||||
|
||||
hexo.on('generateAfter', () => {
|
||||
|
|
|
@ -0,0 +1,153 @@
|
|||
/**
|
||||
* notebooks.js v1
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
class NotePage {
|
||||
constructor(page) {
|
||||
this.id = page._id
|
||||
this.notebook = page.notebook
|
||||
this.title = page.title
|
||||
this.tags = page.tags
|
||||
this.path = page.path
|
||||
this.path_key = page.path.replace('.html', '')
|
||||
this.layout = page.layout
|
||||
this.date = page.date
|
||||
this.updated = page.updated || page.date
|
||||
|
||||
const pin = page.pin ?? page.sticky ?? 0
|
||||
if (pin === true) {
|
||||
this.pin = 1
|
||||
} else if (pin === false) {
|
||||
this.pin = 0
|
||||
} else {
|
||||
this.pin = pin
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function splitTag(tag) {
|
||||
return tag.split('/').filter(t => t.length > 0)
|
||||
}
|
||||
|
||||
function prepareNotebook(id, info, ctx) {
|
||||
const notebook = info
|
||||
notebook.id = id
|
||||
|
||||
if (notebook.base_dir) {
|
||||
if (notebook.base_dir.startsWith('/')) {
|
||||
notebook.base_dir = notebook.base_dir.substring(1)
|
||||
}
|
||||
if (notebook.base_dir.endsWith('/')) {
|
||||
notebook.base_dir = notebook.base_dir.substring(0, notebook.base_dir.length - 1)
|
||||
}
|
||||
} else {
|
||||
const notebooksBaseDir = ctx.theme.config.site_tree.notebooks.base_dir
|
||||
notebook.base_dir = notebooksBaseDir ? `${notebooksBaseDir}/${id}` : id
|
||||
}
|
||||
|
||||
notebook.sort ||= 0
|
||||
notebook.auto_excerpt ||= ctx.theme.config.notebook.auto_excerpt || 0
|
||||
notebook.per_page ??= ctx.theme.config.notebook.per_page ?? ctx.config.per_page ?? 10
|
||||
notebook.order_by ||= ctx.theme.config.notebook.order_by || '-updated'
|
||||
notebook.menu_id ??= ctx.theme.config.site_tree.notes.menu_id
|
||||
notebook.license ??= ctx.theme.config.notebook.license
|
||||
notebook.share ??= ctx.theme.config.notebook.share
|
||||
|
||||
notebook.leftbar ??= ctx.theme.config.site_tree.notes.leftbar
|
||||
notebook.rightbar ??= ctx.theme.config.site_tree.notes.rightbar
|
||||
notebook.note_leftbar ??= ctx.theme.config.site_tree.note.leftbar
|
||||
notebook.note_rightbar ??= ctx.theme.config.site_tree.note.rightbar
|
||||
|
||||
const tagMap = new Map() // tagId: tagInfo
|
||||
notebook.tagTree = tagMap
|
||||
|
||||
const rootTag = {
|
||||
id: '',
|
||||
name: '',
|
||||
part: '',
|
||||
path: notebook.base_dir,
|
||||
parent: null, // parent tag id
|
||||
childSet: new Set(), // child tag ids
|
||||
noteSet: new Set(), // note ids
|
||||
}
|
||||
tagMap.set(rootTag.id, rootTag)
|
||||
|
||||
// Iterate through all notes in the notebook, build the tag tree.
|
||||
const allPages = ctx.locals.get('pages')
|
||||
const pages = allPages.filter(p => p.notebook === notebook.id)
|
||||
for (const page of pages.data) {
|
||||
rootTag.noteSet.add(page._id)
|
||||
|
||||
if (!page.tags) {
|
||||
continue
|
||||
}
|
||||
|
||||
for (const hierarchyTag of page.tags) {
|
||||
const parts = splitTag(hierarchyTag)
|
||||
let parent = rootTag
|
||||
for (const part of parts) {
|
||||
const tagName = parent.name ? `${parent.name}/${part}` : part
|
||||
const tagId = tagName.toLowerCase()
|
||||
let tag = tagMap.get(tagId)
|
||||
if (tag == null) {
|
||||
tag = {
|
||||
id: tagId,
|
||||
name: tagName,
|
||||
part: part,
|
||||
path: `${notebook.base_dir}/tags/${tagId}`,
|
||||
parent: parent.id,
|
||||
childSet: new Set(),
|
||||
noteSet: new Set(),
|
||||
}
|
||||
tagMap.set(tagId, tag)
|
||||
parent.childSet.add(tagId)
|
||||
}
|
||||
|
||||
tag.noteSet.add(page._id)
|
||||
parent = tag
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
notebook.noteMap = pages.map(p => new NotePage(p)).reduce((map, note) => {
|
||||
map.set(note.id, note)
|
||||
return map
|
||||
}, new Map())
|
||||
|
||||
// Sort child tags for each tag.
|
||||
for (const [_, tag] of tagMap) {
|
||||
tag.children = Array.from(tag.childSet)
|
||||
tag.children.sort()
|
||||
}
|
||||
|
||||
return notebook
|
||||
}
|
||||
|
||||
function getNotebooksObject(ctx) {
|
||||
const notebooks = {
|
||||
tree: {},
|
||||
}
|
||||
|
||||
const data = ctx.locals.get('data')
|
||||
const list = []
|
||||
for (const [key, info] of Object.entries(data)) {
|
||||
if (!key.startsWith('notebooks/') || key.endsWith('.DS_Store')) {
|
||||
continue
|
||||
}
|
||||
const id = key.substring(10)
|
||||
list.push(prepareNotebook(id, info, ctx))
|
||||
}
|
||||
list.sort((a, b) => a.sort - b.sort)
|
||||
for (const info of list) {
|
||||
notebooks.tree[info.id] = info
|
||||
}
|
||||
|
||||
return notebooks
|
||||
}
|
||||
|
||||
module.exports = ctx => {
|
||||
const notebooks = getNotebooksObject(ctx)
|
||||
ctx.theme.config.notebooks = notebooks
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
/**
|
||||
* notebooks v1
|
||||
*/
|
||||
const pagination = require('hexo-pagination')
|
||||
|
||||
function paginationWithEmpty(base, posts, options={}) {
|
||||
const { layout, data = {} } = options
|
||||
if (posts.length === 0) {
|
||||
base = `${base}/`
|
||||
return [{
|
||||
path: base,
|
||||
layout: layout,
|
||||
data: {
|
||||
...data,
|
||||
base: base,
|
||||
total: 1,
|
||||
current: 1,
|
||||
current_url: base,
|
||||
posts: posts,
|
||||
prev: 0,
|
||||
prev_link: '',
|
||||
next: 0,
|
||||
next_link: '',
|
||||
}
|
||||
}]
|
||||
} else {
|
||||
return pagination(base, posts, options)
|
||||
}
|
||||
}
|
||||
|
||||
hexo.extend.generator.register('notebooks', function (locals) {
|
||||
const { site_tree, notebooks } = hexo.theme.config
|
||||
if (notebooks.tree.length === 0) {
|
||||
return []
|
||||
}
|
||||
|
||||
const routes = []
|
||||
|
||||
// The index page of all notebooks.
|
||||
routes.push({
|
||||
path: site_tree.notebooks.base_dir + '/index.html',
|
||||
layout: ['notebooks'],
|
||||
data: {
|
||||
layout: 'notebooks',
|
||||
menu_id: site_tree.notebooks.menu_id,
|
||||
}
|
||||
})
|
||||
|
||||
for (const notebook of Object.values(notebooks.tree)) {
|
||||
const pages = locals.pages.filter(p => notebook.noteMap.has(p._id)).sort(notebook.order_by)
|
||||
pages.data.sort((a, b) => notebook.noteMap.get(b._id).pin - notebook.noteMap.get(a._id).pin)
|
||||
|
||||
// Note list pages (for every tag) of current notebook.
|
||||
for (const [_, tag] of notebook.tagTree) {
|
||||
const notes = pages.filter(p => tag.noteSet.has(p._id))
|
||||
const slices = paginationWithEmpty(tag.path, notes, {
|
||||
perPage: notebook.per_page,
|
||||
layout: ['notes'],
|
||||
data: {
|
||||
layout: 'notes',
|
||||
menu_id: notebook.menu_id,
|
||||
notebook: notebook.id,
|
||||
activeTag: tag.id,
|
||||
}
|
||||
})
|
||||
routes.push(...slices)
|
||||
}
|
||||
}
|
||||
|
||||
return routes
|
||||
})
|
|
@ -0,0 +1,65 @@
|
|||
.md-text .tag-list
|
||||
display: flex
|
||||
flex-wrap: wrap
|
||||
padding: 0
|
||||
margin-top: 2rem
|
||||
a.tag
|
||||
display: inline-flex
|
||||
align-items: center
|
||||
position: relative
|
||||
color: var(--text-p2)
|
||||
margin: 4px
|
||||
padding: .5em .75rem
|
||||
border-radius: 4px
|
||||
background: var(--block)
|
||||
font-size: $fs-13
|
||||
font-weight: 500
|
||||
&:before
|
||||
content: "#"
|
||||
margin-left: -2px
|
||||
margin-right: 2px
|
||||
opacity: .4
|
||||
&:hover
|
||||
&:before
|
||||
color $color-theme
|
||||
opacity: 1
|
||||
color: var(--text)
|
||||
background: var(--block-hover)
|
||||
|
||||
.post-list .post-card .meta.cap .tag
|
||||
&:before
|
||||
content: "#"
|
||||
margin-left: -2px
|
||||
margin-right: 2px
|
||||
opacity: .4
|
||||
|
||||
.widget-body.tag-tree .tag-subtree > a > .tag-switcher-wrapper
|
||||
width: 1.75rem
|
||||
height: 0.875rem
|
||||
display: flex
|
||||
justify-content: end
|
||||
align-items: center
|
||||
&:hover
|
||||
color: $color-theme
|
||||
|
||||
.widget-body.tag-tree .tag-subtree.parent-tag > a .tag-switcher
|
||||
display: inline-block
|
||||
height: 0.5rem
|
||||
width: 0.5rem
|
||||
border-width: 1px
|
||||
border-style: none solid solid none
|
||||
transform: translateX(-25%) rotate(-45deg)
|
||||
|
||||
.widget-body.tag-tree .tag-subtree.parent-tag.expanded > a .tag-switcher
|
||||
transform: translateY(-25%) rotate(45deg)
|
||||
|
||||
.widget-body.tag-tree .tag-subtree.parent-tag > .tag-subtree
|
||||
display: none
|
||||
|
||||
.widget-body.tag-tree .tag-subtree.parent-tag.expanded > .tag-subtree
|
||||
display: block
|
||||
|
||||
.widget-body.tag-tree .tag-subtree .tagcon
|
||||
font-size: smaller
|
||||
opacity: 0.4
|
||||
margin-right: 0.25rem
|
Loading…
Reference in New Issue