Compare commits

...

17 Commits
5.2.1 ... 5.3.2

Author SHA1 Message Date
myw
1f3ea55890 fix: 文章頁分頁不顯示的 bug
improvement: 優化部分插件導致文章頁分頁樣式錯亂的 bug
2025-01-13 00:21:47 +08:00
myw
3d4bf30948 fix: 修復隨機封面死循環的問題 2025-01-12 15:32:55 +08:00
myw
172a8ee846 更新依赖 2025-01-11 01:26:06 +08:00
myw
5331f4ef9e Merge branch 'dev' of https://github.com/jerryc127/hexo-theme-butterfly into dev 2024-12-10 20:41:14 +08:00
myw
0d0001c808 feat: 更新 plugins.yml 中的依賴版本至最新
feat: 優化 aside_archives ,改進性能和可讀性
feat: 改善 inlineImg 和 timeline 標籤的文檔,優化時間線邏輯
feat: 更新 gallery 標籤以支持額外參數,優化圖片顯示邏輯
improvement: 優化隨機封面過濾器邏輯, 避免連續重複
feat: 最新評論限制顯示 1-10 條之間
fix: artalk 的最新評論顯示待定或者封禁的評論的 bug
2024-12-10 20:35:58 +08:00
Jerry Wong
13db106172 Merge pull request #1619 from Henry-ZHR/dev
fix: 代码字体先 fallback 到 monospace 再到中文和 sans-serif
2024-12-08 00:53:59 +08:00
Henry-ZHR
f42048792e fix: 代码字体先 fallback 到 monospace 再到中文和 sans-serif 2024-12-07 20:43:16 +08:00
myw
247c1b664d feat: 更新 lazyload 配置,支持原生 lazyload 功能
feat: 代碼優化
feat: 優化 pageType 邏輯
fix: 修復解密文章後, chartjs 沒有加載的 bug
2024-11-30 13:38:39 +08:00
myw
f7483d59b5 Merge branch 'dev' of https://github.com/jerryc127/hexo-theme-butterfly into dev 2024-11-15 14:56:56 +08:00
myw
b7771e5938 feat: 移除 pangu.js
feat: js 加载完成才显示聊天按钮
2024-11-15 14:53:40 +08:00
Jerry Wong
74c555fb37 Merge pull request #1602 from DeepChirp/structured_data
feat: 添加结构化数据支持
2024-11-11 18:46:57 +08:00
DeepChirp
648ca6eb4f chore: 简化结构化数据配置,移除不必要的嵌套 2024-11-11 18:42:46 +08:00
DeepChirp
e5a52d5621 fix: 移除结构化数据中不合规范的微数据 2024-11-11 16:29:28 +08:00
Jerry Wong
1045f66a51 Merge pull request #1603 from DeepChirp/image
chore: add `avif` to the list of supported image formats
2024-11-11 15:13:02 +08:00
DeepChirp
5338a2be99 chore: add avif to the list of supported image formats 2024-11-07 18:09:01 +08:00
DeepChirp
3ea138178d feat: 添加结构化数据支持 2024-11-07 17:55:33 +08:00
myw
91c8c5cd4b feat: 過期通知優化,可單獨文章關閉
fix: 修復説説評論 css 受主題影響的 bug
2024-11-05 18:01:36 +08:00
89 changed files with 758 additions and 635 deletions

View File

@@ -101,7 +101,7 @@ npm i hexo-theme-butterfly
- [x] Chart.js - [x] Chart.js
- [x] Justified Gallery - [x] Justified Gallery
- [x] Lazyload images - [x] Lazyload images
- [x] Instantpage/Pangu/Snackbar notification toast/PWA...... - [x] Instantpage/Snackbar notification toast/PWA......
## ✨ Contributors ## ✨ Contributors

View File

@@ -101,7 +101,7 @@ theme: butterfly
- [x] Chart.js 圖表顯示 - [x] Chart.js 圖表顯示
- [x] 照片牆 - [x] 照片牆
- [x] 圖片懶加載 - [x] 圖片懶加載
- [x] Instantpage/Pangu/Snackbar彈窗/PWA...... - [x] Instantpage/Snackbar彈窗/PWA......
## ✨ 貢獻者 ## ✨ 貢獻者

View File

@@ -983,13 +983,6 @@ snackbar:
# https://instant.page/ # https://instant.page/
instantpage: false instantpage: false
# Pangu - Insert a space between Chinese character and English character
# https://github.com/vinta/pangu.js
pangu:
enable: false
# Specify the field to use pangu (site or post)
field: site
# Lazyload # Lazyload
# https://github.com/verlok/vanilla-lazyload # https://github.com/verlok/vanilla-lazyload
lazyload: lazyload:
@@ -1023,6 +1016,10 @@ Open_Graph_meta:
# fb_admins: # fb_admins:
# fb_app_id: # fb_app_id:
# Structured Data
# https://developers.google.com/search/docs/guides/intro-structured-data
structured_data: true
# Add the vendor prefixes to ensure compatibility # Add the vendor prefixes to ensure compatibility
css_prefix: true css_prefix: true
@@ -1092,7 +1089,6 @@ CDN:
# medium_zoom: # medium_zoom:
# mermaid: # mermaid:
# meting_js: # meting_js:
# pangu:
# prismjs_autoloader: # prismjs_autoloader:
# prismjs_js: # prismjs_js:
# prismjs_lineNumber_js: # prismjs_lineNumber_js:

View File

@@ -11,15 +11,12 @@ div
if theme.instantpage if theme.instantpage
script(src=url_for(theme.asset.instantpage), type='module') script(src=url_for(theme.asset.instantpage), type='module')
if theme.lazyload.enable if theme.lazyload.enable && !theme.lazyload.native
script(src=url_for(theme.asset.lazyload)) script(src=url_for(theme.asset.lazyload))
if theme.snackbar.enable if theme.snackbar.enable
script(src=url_for(theme.asset.snackbar)) script(src=url_for(theme.asset.snackbar))
if theme.pangu.enable
!= partial("includes/third-party/pangu.pug", {}, { cache: true })
.js-pjax .js-pjax
if needLoadCountJs if needLoadCountJs
!= partial("includes/third-party/card-post-count/index", {}, { cache: true }) != partial("includes/third-party/card-post-count/index", {}, { cache: true })
@@ -36,7 +33,7 @@ div
!= partial("includes/third-party/prismjs", {}, { cache: true }) != partial("includes/third-party/prismjs", {}, { cache: true })
if theme.aside.enable && theme.aside.card_newest_comments.enable if theme.aside.enable && theme.aside.card_newest_comments.enable
if theme.pjax.enable || (!is_post() && page.aside !== false) if theme.pjax.enable || (globalPageType !== 'post' && page.aside !== false)
!= partial("includes/third-party/newest-comments/index", {}, { cache: true }) != partial("includes/third-party/newest-comments/index", {}, { cache: true })
!= fragment_cache('injectBottom', function(){return injectHtml(theme.inject.bottom)}) != fragment_cache('injectBottom', function(){return injectHtml(theme.inject.bottom)})

View File

@@ -1,12 +1,18 @@
- var pageTitle - var pageTitle
- is_archive() ? page.title = findArchivesTitle(page, theme.menu, date) : '' - globalPageType === 'archive' ? page.title = findArchivesTitle(page, theme.menu, date) : ''
- if (is_tag()) pageTitle = _p('page.tag') + ': ' + page.tag case globalPageType
- else if (is_category()) pageTitle = _p('page.category') + ': ' + page.category when 'tag'
- else if (is_current('/404.html', [strict])) pageTitle = _p('error404') - pageTitle = _p('page.tag') + ': ' + page.tag
- else pageTitle = page.title || config.title || '' when 'category'
- pageTitle = _p('page.category') + ': ' + page.category
when '404'
- pageTitle = _p('error404')
default
- pageTitle = page.title || config.title || ''
- var isSubtitle = config.subtitle ? ' - ' + config.subtitle : '' - var isSubtitle = config.subtitle ? ' - ' + config.subtitle : ''
- var tabTitle = is_home() || !pageTitle ? config.title + isSubtitle : pageTitle + ' | ' + config.title - var tabTitle = globalPageType === 'home' || !pageTitle ? config.title + isSubtitle : pageTitle + ' | ' + config.title
- var pageAuthor = config.email ? config.author + ',' + config.email : config.author - var pageAuthor = config.email ? config.author + ',' + config.email : config.author
- var pageCopyright = config.copyright || config.author - var pageCopyright = config.copyright || config.author
- var themeColorLight = theme.theme_color && theme.theme_color.enable && theme.theme_color.meta_theme_color_light || '#ffffff' - var themeColorLight = theme.theme_color && theme.theme_color.enable && theme.theme_color.meta_theme_color_light || '#ffffff'
@@ -25,6 +31,9 @@ meta(name="theme-color" content=themeColor)
//- Open_Graph //- Open_Graph
include ./head/Open_Graph.pug include ./head/Open_Graph.pug
//- Structured Data
include ./head/structured_data.pug
!=favicon_tag(theme.favicon || config.favicon) !=favicon_tag(theme.favicon || config.favicon)
link(rel="canonical" href=urlNoIndex(null,config.pretty_urls.trailing_index,config.pretty_urls.trailing_html)) link(rel="canonical" href=urlNoIndex(null,config.pretty_urls.trailing_index,config.pretty_urls.trailing_html))

View File

@@ -2,7 +2,7 @@ if theme.Open_Graph_meta.enable
- -
const coverVal = page.cover_type === 'img' ? page.cover : theme.avatar.img const coverVal = page.cover_type === 'img' ? page.cover : theme.avatar.img
let ogOption = Object.assign({ let ogOption = Object.assign({
type: is_post() ? 'article' : 'website', type: globalPageType === 'post' ? 'article' : 'website',
image: coverVal ? full_url_for(coverVal) : '', image: coverVal ? full_url_for(coverVal) : '',
fb_admins: theme.facebook_comments.user_id || '', fb_admins: theme.facebook_comments.user_id || '',
fb_app_id: theme.facebook_comments.app_id || '', fb_app_id: theme.facebook_comments.app_id || '',

View File

@@ -69,16 +69,6 @@
}) })
} }
let noticeOutdate = 'undefined'
if (theme.noticeOutdate && theme.noticeOutdate.enable) {
noticeOutdate = JSON.stringify({
limitDay: theme.noticeOutdate.limit_day,
position: theme.noticeOutdate.position,
messagePrev: theme.noticeOutdate.message_prev,
messageNext: theme.noticeOutdate.message_next,
})
}
let highlight = 'undefined' let highlight = 'undefined'
let syntaxHighlighter = config.syntax_highlighter let syntaxHighlighter = config.syntax_highlighter
let highlightEnable = syntaxHighlighter ? ['highlight.js', 'prismjs'].includes(syntaxHighlighter) : (config.highlight.enable || config.prismjs.enable) let highlightEnable = syntaxHighlighter ? ['highlight.js', 'prismjs'].includes(syntaxHighlighter) : (config.highlight.enable || config.prismjs.enable)
@@ -100,7 +90,6 @@ script.
algolia: !{algolia}, algolia: !{algolia},
localSearch: !{localSearch}, localSearch: !{localSearch},
translate: !{translate}, translate: !{translate},
noticeOutdate: !{noticeOutdate},
highlight: !{highlight}, highlight: !{highlight},
copy: { copy: {
success: '!{_p("copy.success")}', success: '!{_p("copy.success")}',
@@ -127,7 +116,7 @@ script.
buttonText: '!{_p("load_more")}' buttonText: '!{_p("load_more")}'
}, },
isPhotoFigcaption: !{theme.photofigcaption}, isPhotoFigcaption: !{theme.photofigcaption},
islazyload: !{theme.lazyload.enable}, islazyloadPlugin: !{theme.lazyload.enable && !theme.lazyload.native},
isAnchor: !{theme.anchor.auto_update || false}, isAnchor: !{theme.anchor.auto_update || false},
percent: { percent: {
toc: !{theme.toc.scroll_percent}, toc: !{theme.toc.scroll_percent},

View File

@@ -9,8 +9,8 @@
var showToc = false var showToc = false
if (theme.aside.enable && page.aside !== false) { if (theme.aside.enable && page.aside !== false) {
let tocEnable = false let tocEnable = false
if (is_post() && theme.toc.post) tocEnable = true if (globalPageType === 'post' && theme.toc.post) tocEnable = true
else if (is_page() && theme.toc.page) tocEnable = true else if (globalPageType === 'page' && theme.toc.page) tocEnable = true
const pageToc = typeof page.toc === 'boolean' ? page.toc : tocEnable const pageToc = typeof page.toc === 'boolean' ? page.toc : tocEnable
showToc = pageToc && (toc(page.content) !== '' || page.encrypt === true) showToc = pageToc && (toc(page.content) !== '' || page.encrypt === true)
} }
@@ -19,10 +19,7 @@
script#config-diff. script#config-diff.
var GLOBAL_CONFIG_SITE = { var GLOBAL_CONFIG_SITE = {
title: '!{titleVal}', title: '!{titleVal}',
isPost: !{is_post()},
isHome: !{is_home()},
isHighlightShrink: !{isHighlightShrink}, isHighlightShrink: !{isHighlightShrink},
isToc: !{showToc}, isToc: !{showToc},
postUpdate: '!{full_date(page.updated)}', pageType: '!{page.type == 'shuoshuo' ? 'shuoshuo' : globalPageType}'
isShuoshuo: !{page.type == 'shuoshuo'}
} }

View File

@@ -0,0 +1,34 @@
if theme.structured_data && page.layout === 'post'
-
// use json-ld to add structured data
const title = page.title
const url = page.permalink
const imageVal = page.cover_type === 'img' ? page.cover : theme.avatar.img
const image = imageVal ? full_url_for(imageVal) : ''
const datePublished = page.date.toISOString()
const dateModified = (page.updated || page.date).toISOString()
const author = page.copyright_author || config.author
const authorHrefVal = page.copyright_author_href || theme.post_copyright.author_href || site.url;
const authorHref = full_url_for(authorHrefVal);
const jsonLd = {
"@context": "https://schema.org",
"@type": "BlogPosting",
"headline": title,
"url": url,
"image": image,
"datePublished": datePublished,
"dateModified": dateModified,
"author": [{
"@type": "Person",
"name": author,
"url": authorHref
}]
};
jsonLdScript = JSON.stringify(jsonLd, null, 2);
-
script(type="application/ld+json").
!{jsonLdScript}

View File

@@ -6,33 +6,32 @@
var bg_img = '' var bg_img = ''
if !theme.disable_top_img && page.top_img !== false if !theme.disable_top_img && page.top_img !== false
if is_post() case globalPageType
when 'post'
- top_img = page.top_img || page.cover || theme.default_top_img - top_img = page.top_img || page.cover || theme.default_top_img
else if is_page() when 'page'
- top_img = page.top_img || theme.default_top_img - top_img = page.top_img || theme.default_top_img
else if is_tag() when 'tag'
- top_img = theme.tag_per_img && theme.tag_per_img[page.tag] - top_img = theme.tag_per_img && theme.tag_per_img[page.tag] || returnTopImg(theme.tag_img)
- top_img = top_img || returnTopImg(theme.tag_img) when 'category'
else if is_category() - top_img = theme.category_per_img && theme.category_per_img[page.category] || returnTopImg(theme.category_img)
- top_img = theme.category_per_img && theme.category_per_img[page.category] when 'home'
- top_img = top_img || returnTopImg(theme.category_img)
else if is_home()
- top_img = returnTopImg(theme.index_img) - top_img = returnTopImg(theme.index_img)
else if is_archive() when 'archive'
- top_img = returnTopImg(theme.archive_img) - top_img = returnTopImg(theme.archive_img)
else default
- top_img = page.top_img || theme.default_top_img - top_img = page.top_img || theme.default_top_img
if top_img !== false if top_img !== false
- bg_img = getBgPath(top_img) - bg_img = getBgPath(top_img)
- headerClassName = is_home() ? 'full_page' : is_post() ? 'post-bg' : 'not-home-page' - headerClassName = globalPageType === 'home' ? 'full_page' : globalPageType === 'post' ? 'post-bg' : 'not-home-page'
header#page-header(class=`${headerClassName + isFixedClass}` style=bg_img) header#page-header(class=`${headerClassName + isFixedClass}` style=bg_img)
include ./nav.pug include ./nav.pug
if top_img !== false if top_img !== false
if is_post() if globalPageType === 'post'
include ./post-info.pug include ./post-info.pug
else if is_home() else if globalPageType === 'home'
#site-info #site-info
h1#site-title=config.title h1#site-title=config.title
if theme.subtitle.enable if theme.subtitle.enable
@@ -48,6 +47,6 @@ header#page-header(class=`${headerClassName + isFixedClass}` style=bg_img)
#page-site-info #page-site-info
h1#site-title=page.title || page.tag || page.category h1#site-title=page.title || page.tag || page.category
else else
//- improvement seo //- improve seo
if !is_post() if globalPageType !== 'post'
h1.title-seo=page.title || page.tag || page.category || config.title h1.title-seo=page.title || page.tag || page.category || config.title

View File

@@ -5,7 +5,7 @@ nav#nav
img.site-icon(src=url_for(theme.nav.logo) alt='Logo') img.site-icon(src=url_for(theme.nav.logo) alt='Logo')
if theme.nav.display_title if theme.nav.display_title
span.site-name=config.title span.site-name=config.title
if is_post() if globalPageType === 'post'
a.nav-page-title(href=url_for('/')) a.nav-page-title(href=url_for('/'))
span.site-name=(page.title || config.title) span.site-name=(page.title || config.title)
#menus #menus

View File

@@ -1,7 +1,8 @@
- var globalPageType = getPageType(page, is_home)
- var htmlClassHideAside = theme.aside.enable && theme.aside.hide ? 'hide-aside' : '' - var htmlClassHideAside = theme.aside.enable && theme.aside.hide ? 'hide-aside' : ''
- page.aside = is_archive() ? theme.aside.display.archive: is_category() ? theme.aside.display.category : is_tag() ? theme.aside.display.tag : page.aside - page.aside = globalPageType === 'archive' ? theme.aside.display.archive: globalPageType === 'category' ? theme.aside.display.category : globalPageType === 'tag' ? theme.aside.display.tag : page.aside
- var hideAside = !theme.aside.enable || page.aside === false ? 'hide-aside' : '' - var hideAside = !theme.aside.enable || page.aside === false ? 'hide-aside' : ''
- var pageType = is_post() ? 'post' : 'page' - var pageType = globalPageType === 'post' ? 'post' : 'page'
- pageType = page.type ? pageType + ' type-' + page.type : pageType - pageType = page.type ? pageType + ' type-' + page.type : pageType
doctype html doctype html

View File

@@ -20,7 +20,7 @@ mixin indexPostUI()
div.post-bg(style=`background: ${post_cover}`) div.post-bg(style=`background: ${post_cover}`)
.recent-post-info(class=no_cover) .recent-post-info(class=no_cover)
a.article-title(href=url_for(link) title=title) a.article-title(href=url_for(link) title=title)
if is_home() && (article.top || article.sticky > 0) if globalPageType === 'home' && (article.top || article.sticky > 0)
i.fas.fa-thumbtack.sticky i.fas.fa-thumbtack.sticky
= title = title
.article-meta-wrap .article-meta-wrap

View File

@@ -1,2 +1,2 @@
#article-container #article-container.container
!= page.content != page.content

View File

@@ -1,4 +1,4 @@
#article-container #article-container.container
.flink .flink
- let { content, random, flink_url } = page - let { content, random, flink_url } = page
- let pageContent = content - let pageContent = content

View File

@@ -28,7 +28,7 @@
const btn = e.target.closest('.shuoshuo-comment-btn') const btn = e.target.closest('.shuoshuo-comment-btn')
if (!btn) return if (!btn) return
const ele = btn.parentNode.nextElementSibling const ele = btn.closest('.container').nextElementSibling
const { shuoshuoComment } = window const { shuoshuoComment } = window
const isInclude = ele.classList.contains('no-comment') const isInclude = ele.classList.contains('no-comment')
runDestroy(shuoshuoComment) runDestroy(shuoshuoComment)
@@ -103,6 +103,7 @@
return ` return `
<div class="shuoshuo-item"> <div class="shuoshuo-item">
<div class="container">
<div class="shuoshuo-item-header"> <div class="shuoshuo-item-header">
<div class="shuoshuo-avatar"> <div class="shuoshuo-avatar">
<img class="no-lightbox" src="${item.avatar || '!{url_for(theme.avatar.img)}'}"> <img class="no-lightbox" src="${item.avatar || '!{url_for(theme.avatar.img)}'}">
@@ -119,6 +120,7 @@
${tags ? `<div class="shuoshuo-tags">${tags}</div>` : ''} ${tags ? `<div class="shuoshuo-tags">${tags}</div>` : ''}
${commentButton} ${commentButton}
</div> </div>
</div>
${commentContainer} ${commentContainer}
</div>` </div>`
}).join('') }).join('')
@@ -165,6 +167,7 @@
if site.data.shuoshuo if site.data.shuoshuo
each i in shuoshuoFN(site.data.shuoshuo, page) each i in shuoshuoFN(site.data.shuoshuo, page)
.shuoshuo-item .shuoshuo-item
.container
.shuoshuo-item-header .shuoshuo-item-header
.shuoshuo-avatar .shuoshuo-avatar
img.no-lightbox(src=i.avatar || url_for(theme.avatar.img)) img.no-lightbox(src=i.avatar || url_for(theme.avatar.img))

View File

@@ -6,7 +6,7 @@
escape: false escape: false
} }
if is_post() if globalPageType === 'post'
- let paginationOrder = theme.post_pagination === 1 ? { prev: page.prev, next: page.next } : { prev: page.next, next: page.prev } - let paginationOrder = theme.post_pagination === 1 ? { prev: page.prev, next: page.next } : { prev: page.next, next: page.prev }
nav#pagination.pagination-post nav#pagination.pagination-post
@@ -32,6 +32,6 @@ if is_post()
else else
nav#pagination nav#pagination
.pagination .pagination
if is_home() if globalPageType === 'home'
- options.format = 'page/%d/#content-inner' - options.format = 'page/%d/#content-inner'
!=paginator(options) !=paginator(options)

View File

@@ -0,0 +1,8 @@
- const { limit_day, message_prev, message_next, position} = theme.noticeOutdate
- const notice_data = { limitDay: limit_day, messagePrev: message_prev, messageNext: message_next, postUpdate: full_date(page.updated)}
if position === 'top'
#post-outdate-notice(data=notice_data hidden)
!=page.content
else
!=page.content
#post-outdate-notice(data=notice_data hidden)

View File

@@ -3,7 +3,7 @@ mixin rightsideItem(array)
each item in array each item in array
case item case item
when 'readmode' when 'readmode'
if is_post() && readmode if globalPageType === 'post' && readmode
button#readmode(type="button" title=_p('rightside.readmode_title')) button#readmode(type="button" title=_p('rightside.readmode_title'))
i.fas.fa-book-open i.fas.fa-book-open
when 'translate' when 'translate'
@@ -23,7 +23,7 @@ mixin rightsideItem(array)
i.fas.fa-list-ul i.fas.fa-list-ul
when 'chat' when 'chat'
if chat.rightside_button && chat.use if chat.rightside_button && chat.use
button#chat-btn(type="button" title=_p("rightside.chat")) button#chat-btn(type="button" title=_p("rightside.chat") style="display:none")
i.fas.fa-message i.fas.fa-message
when 'comment' when 'comment'
if commentsJsLoad if commentsJsLoad
@@ -45,7 +45,7 @@ mixin rightsideItem(array)
button#rightside-config(type="button" title=_p("rightside.setting")) button#rightside-config(type="button" title=_p("rightside.setting"))
i.fas.fa-cog.fa-spin i.fas.fa-cog.fa-spin
else else
if is_post() if globalPageType === 'post'
if (readmode || translate.enable || (darkmode.enable && darkmode.button)) if (readmode || translate.enable || (darkmode.enable && darkmode.button))
button#rightside-config(type="button" title=_p("rightside.setting")) button#rightside-config(type="button" title=_p("rightside.setting"))
i.fas.fa-cog.fa-spin i.fas.fa-cog.fa-spin

View File

@@ -3,15 +3,15 @@ if theme.menu
#menu-mask #menu-mask
#sidebar-menus #sidebar-menus
.avatar-img.text-center .avatar-img.text-center
img(src=url_for(theme.avatar.img) onerror=`onerror=null;src='${theme.error_img.flink}'` alt="avatar") img(src=url_for(theme.avatar.img) onerror=`this.onerror=null;this.src='${url_for(theme.error_img.flink)}'` alt="avatar")
.site-data.text-center .site-data.text-center
a(href=url_for(config.archive_dir) + '/') a(href=`${url_for(config.archive_dir)}/`)
.headline= _p('aside.articles') .headline= _p('aside.articles')
.length-num= site.posts.length .length-num= site.posts.length
a(href=url_for(config.tag_dir) + '/' ) a(href=`${url_for(config.tag_dir)}/`)
.headline= _p('aside.tags') .headline= _p('aside.tags')
.length-num= site.tags.length .length-num= site.tags.length
a(href=url_for(config.category_dir) + '/') a(href=`${url_for(config.category_dir)}/`)
.headline= _p('aside.categories') .headline= _p('aside.categories')
.length-num= site.categories.length .length-num= site.categories.length

View File

@@ -1,6 +1,3 @@
if theme.abcjs.enable if theme.abcjs.enable
if theme.abcjs.per_page if theme.abcjs.per_page && (['post','page'].includes(globalPageType)) || page.abcjs
if is_post() || is_page()
include ./abcjs.pug
else if page.abcjs
include ./abcjs.pug include ./abcjs.pug

View File

@@ -1,6 +1,12 @@
//- https://chatra.io/help/api/ //- https://chatra.io/help/api/
script. script.
(() => { (() => {
window.ChatraID = '#{theme.chatra.id}'
window.Chatra = window.Chatra || function() {
(window.Chatra.q = window.Chatra.q || []).push(arguments)
}
btf.getScript('https://call.chatra.io/chatra.js').then(() => {
const isChatBtn = !{theme.chat.rightside_button} const isChatBtn = !{theme.chat.rightside_button}
const isChatHideShow = !{theme.chat.button_hide_show} const isChatHideShow = !{theme.chat.button_hide_show}
@@ -17,26 +23,16 @@ script.
window.ChatraSetup = { startHidden: true } window.ChatraSetup = { startHidden: true }
window.chatBtnFn = () => { window.chatBtnFn = () => document.getElementById('chatra').classList.contains('chatra--expanded') ? close() : open()
document.getElementById('chatra').classList.contains('chatra--expanded') ? close() : open()
} document.getElementById('chat-btn').style.display = 'block'
} else if (isChatHideShow) { } else if (isChatHideShow) {
window.chatBtn = { window.chatBtn = {
hide: () => Chatra('hide'), hide: () => Chatra('hide'),
show: () => Chatra('show') show: () => Chatra('show')
} }
} }
})
(function(d, w, c) {
w.ChatraID = '#{theme.chatra.id}'
var s = d.createElement('script')
w[c] = w[c] || function() {
(w[c].q = w[c].q || []).push(arguments)
}
s.async = true
s.src = 'https://call.chatra.io/chatra.js'
if (d.head) d.head.appendChild(s)
})(document, window, 'Chatra')
})() })()

View File

@@ -1,16 +1,9 @@
script. script.
(() => { (() => {
window.$crisp = []; window.$crisp = ['safe', true]
window.CRISP_WEBSITE_ID = "!{theme.crisp.website_id}"; window.CRISP_WEBSITE_ID = "!{theme.crisp.website_id}"
(function () {
d = document;
s = d.createElement("script");
s.src = "https://client.crisp.chat/l.js";
s.async = 1;
d.getElementsByTagName("head")[0].appendChild(s);
})();
$crisp.push(["safe", true])
btf.getScript('https://client.crisp.chat/l.js').then(() => {
const isChatBtn = !{theme.chat.rightside_button} const isChatBtn = !{theme.chat.rightside_button}
const isChatHideShow = !{theme.chat.button_hide_show} const isChatHideShow = !{theme.chat.button_hide_show}
@@ -28,10 +21,12 @@ script.
window.chatBtnFn = () => $crisp.is("chat:visible") ? close() : open() window.chatBtnFn = () => $crisp.is("chat:visible") ? close() : open()
document.getElementById('chat-btn').style.display = 'block'
} else if (isChatHideShow) { } else if (isChatHideShow) {
window.chatBtn = { window.chatBtn = {
hide: () => $crisp.push(["do", "chat:hide"]), hide: () => $crisp.push(["do", "chat:hide"]),
show: () => $crisp.push(["do", "chat:show"]) show: () => $crisp.push(["do", "chat:show"])
} }
} }
})
})() })()

View File

@@ -1,6 +1,6 @@
script(src=`//code.tidio.co/${theme.tidio.public_key}.js` async)
script. script.
(() => { (() => {
btf.getScript('//code.tidio.co/!{theme.tidio.public_key}.js').then(() => {
const isChatBtn = !{theme.chat.rightside_button} const isChatBtn = !{theme.chat.rightside_button}
const isChatHideShow = !{theme.chat.button_hide_show} const isChatHideShow = !{theme.chat.button_hide_show}
@@ -31,11 +31,15 @@ script.
if (!window.tidioChatApi) return if (!window.tidioChatApi) return
isShow ? close() : open() isShow ? close() : open()
} }
document.getElementById('chat-btn').style.display = 'block'
} else if (isChatHideShow) { } else if (isChatHideShow) {
window.chatBtn = { window.chatBtn = {
hide: () => window.tidioChatApi && window.tidioChatApi.hide(), hide: () => window.tidioChatApi && window.tidioChatApi.hide(),
show: () => window.tidioChatApi && window.tidioChatApi.show() show: () => window.tidioChatApi && window.tidioChatApi.show()
} }
} }
})
})() })()

View File

@@ -5,7 +5,7 @@ script.
(() => { (() => {
let artalkItem = null let artalkItem = null
const option = !{JSON.stringify(option)} const option = !{JSON.stringify(option)}
const isShuoshuo = GLOBAL_CONFIG_SITE.isShuoshuo const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'
const destroyArtalk = () => { const destroyArtalk = () => {
if (artalkItem) { if (artalkItem) {

View File

@@ -4,7 +4,7 @@
script. script.
(() => { (() => {
const isShuoshuo = GLOBAL_CONFIG_SITE.isShuoshuo const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'
const disqusReset = conf => { const disqusReset = conf => {
window.DISQUS && window.DISQUS.reset({ window.DISQUS && window.DISQUS.reset({
@@ -72,7 +72,7 @@ script.
if (!{lazyload}) btf.loadComment(document.getElementById('disqus_thread'), loadDisqus) if (!{lazyload}) btf.loadComment(document.getElementById('disqus_thread'), loadDisqus)
else { else {
loadDisqus() loadDisqus()
!{ count ? 'GLOBAL_CONFIG_SITE.isPost && getCount()' : '' } !{ count ? `GLOBAL_CONFIG_SITE.pageType === 'post' && getCount()` : '' }
} }
} else { } else {
window.loadOtherComment = loadDisqus window.loadOtherComment = loadDisqus

View File

@@ -3,7 +3,7 @@
script. script.
(() => { (() => {
const isShuoshuo = GLOBAL_CONFIG_SITE.isShuoshuo const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'== 'shuoshuo'
const dqOption = !{JSON.stringify(dqOption)} const dqOption = !{JSON.stringify(dqOption)}
const destroyDisqusjs = () => { const destroyDisqusjs = () => {
@@ -79,7 +79,7 @@ script.
if (!{theme.comments.lazyload}) btf.loadComment(document.getElementById('disqusjs-wrap'), loadDisqusjs) if (!{theme.comments.lazyload}) btf.loadComment(document.getElementById('disqusjs-wrap'), loadDisqusjs)
else { else {
loadDisqusjs() loadDisqusjs()
!{ theme.comments.count ? 'GLOBAL_CONFIG_SITE.isPost && getCount()' : '' } !{ theme.comments.count ? `GLOBAL_CONFIG_SITE.pageType === 'post' && getCount()` : '' }
} }
} else { } else {
window.loadOtherComment = loadDisqusjs window.loadOtherComment = loadDisqusjs

View File

@@ -3,7 +3,7 @@
script. script.
(()=>{ (()=>{
const isShuoshuo = GLOBAL_CONFIG_SITE.isShuoshuo const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'== 'shuoshuo'
const loadFBComment = (el = document, path) => { const loadFBComment = (el = document, path) => {
if (isShuoshuo) { if (isShuoshuo) {

View File

@@ -5,7 +5,7 @@
script. script.
(() => { (() => {
const isShuoshuo = GLOBAL_CONFIG_SITE.isShuoshuo const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'
const option = !{JSON.stringify(option)} const option = !{JSON.stringify(option)}
const getGiscusTheme = theme => theme === 'dark' ? '!{dark_theme}' : '!{light_theme}' const getGiscusTheme = theme => theme === 'dark' ? '!{dark_theme}' : '!{light_theme}'

View File

@@ -2,7 +2,7 @@
script. script.
(() => { (() => {
const isShuoshuo = GLOBAL_CONFIG_SITE.isShuoshuo const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'
const option = !{JSON.stringify(option)} const option = !{JSON.stringify(option)}
const commentCount = n => { const commentCount = n => {

View File

@@ -2,7 +2,7 @@
script. script.
(() => { (() => {
const isShuoshuo = GLOBAL_CONFIG_SITE.isShuoshuo const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'
const loadLivere = (el, path) => { const loadLivere = (el, path) => {
window.livereOptions = { window.livereOptions = {

View File

@@ -2,7 +2,7 @@
script. script.
(() => { (() => {
const isShuoshuo = GLOBAL_CONFIG_SITE.isShuoshuo const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'
const option = !{JSON.stringify(option)} const option = !{JSON.stringify(option)}
const loadScript = src => { const loadScript = src => {

View File

@@ -3,7 +3,7 @@
script. script.
(() => { (() => {
const isShuoshuo = GLOBAL_CONFIG_SITE.isShuoshuo const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'
const option = !{JSON.stringify(option)} const option = !{JSON.stringify(option)}
const getCount = () => { const getCount = () => {
@@ -33,7 +33,7 @@ script.
path: isShuoshuo ? path : (option && option.path) || path path: isShuoshuo ? path : (option && option.path) || path
}) })
!{count ? 'GLOBAL_CONFIG_SITE.isPost && getCount()' : ''} !{count ? `GLOBAL_CONFIG_SITE.pageType === 'post' && getCount()` : ''}
isShuoshuo && (window.shuoshuoComment.destroyTwikoo = () => { isShuoshuo && (window.shuoshuoComment.destroyTwikoo = () => {
if (el.children.length) { if (el.children.length) {

View File

@@ -5,7 +5,7 @@
script. script.
(() => { (() => {
const isShuoshuo = GLOBAL_CONFIG_SITE.isShuoshuo const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'
const option = !{JSON.stringify(option)} const option = !{JSON.stringify(option)}
const getUtterancesTheme = theme => theme === 'dark' ? '#{dark_theme}' : '#{light_theme}' const getUtterancesTheme = theme => theme === 'dark' ? '#{dark_theme}' : '#{light_theme}'

View File

@@ -7,7 +7,7 @@ if site.data.valine
script. script.
(() => { (() => {
const isShuoshuo = GLOBAL_CONFIG_SITE.isShuoshuo const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'
const option = !{JSON.stringify(option)} const option = !{JSON.stringify(option)}
const initValine = (el, path) => { const initValine = (el, path) => {

View File

@@ -4,7 +4,7 @@
script. script.
(() => { (() => {
let initFn = window.walineFn || null let initFn = window.walineFn || null
const isShuoshuo = GLOBAL_CONFIG_SITE.isShuoshuo const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'
const option = !{JSON.stringify(option)} const option = !{JSON.stringify(option)}
const destroyWaline = ele => ele.destroy() const destroyWaline = ele => ele.destroy()

View File

@@ -2,9 +2,6 @@
script. script.
(() => { (() => {
const $chartjs = document.querySelectorAll('#article-container .chartjs-container')
if ($chartjs.length === 0) return
const applyThemeDefaultsConfig = theme => { const applyThemeDefaultsConfig = theme => {
if (theme === 'dark-mode') { if (theme === 'dark-mode') {
Chart.defaults.color = "!{fontColor.dark}" Chart.defaults.color = "!{fontColor.dark}"
@@ -35,10 +32,10 @@ script.
}) })
} }
const runChartJS = () => { const runChartJS = ele => {
window.loadChartJS = true window.loadChartJS = true
Array.from($chartjs).forEach((item, index) => { Array.from(ele).forEach((item, index) => {
const chartSrc = item.firstElementChild const chartSrc = item.firstElementChild
const chartID = item.getAttribute('data-chartjs-id') || ('chartjs-' + index) // Use custom ID or default ID const chartID = item.getAttribute('data-chartjs-id') || ('chartjs-' + index) // Use custom ID or default ID
const width = item.getAttribute('data-width') const width = item.getAttribute('data-width')
@@ -80,12 +77,15 @@ script.
} }
const loadChartJS = () => { const loadChartJS = () => {
window.loadChartJS ? runChartJS() : btf.getScript('!{url_for(theme.asset.chartjs)}').then(runChartJS) const chartJSEle = document.querySelectorAll('#article-container .chartjs-container')
if (chartJSEle.length === 0) return
window.loadChartJS ? runChartJS(chartJSEle) : btf.getScript('!{url_for(theme.asset.chartjs)}').then(() => runChartJS(chartJSEle))
} }
// Listen for theme change events // Listen for theme change events
btf.addGlobalFn('themeChange', runChartJS, 'chartjs') btf.addGlobalFn('themeChange', loadChartJS, 'chartjs')
btf.addGlobalFn('encrypt', runChartJS, 'chartjs') btf.addGlobalFn('encrypt', loadChartJS, 'chartjs')
window.pjax ? loadChartJS() : document.addEventListener('DOMContentLoaded', loadChartJS) window.pjax ? loadChartJS() : document.addEventListener('DOMContentLoaded', loadChartJS)
})() })()

View File

@@ -1,10 +1,10 @@
case theme.math.use case theme.math.use
when 'mathjax' when 'mathjax'
if (theme.math.per_page && (is_post() || is_page())) || page.mathjax if (theme.math.per_page && (['post','page'].includes(globalPageType))) || page.mathjax
include ./mathjax.pug include ./mathjax.pug
when 'katex' when 'katex'
if (theme.math.per_page && (is_post() || is_page())) || page.katex if (theme.math.per_page && (['post','page'].includes(globalPageType))) || page.katex
include ./katex.pug include ./katex.pug
if theme.mermaid.enable if theme.mermaid.enable

View File

@@ -34,7 +34,7 @@ script.
const searchParams = new URLSearchParams({ const searchParams = new URLSearchParams({
'site_name': '!{site}', 'site_name': '!{site}',
'limit': '!{theme.aside.card_newest_comments.limit}', 'limit': '!{newestCommentsLimit * 2}', // Fetch more comments to filter pending comments
}) })
const getComment = async (ele) => { const getComment = async (ele) => {
@@ -42,7 +42,10 @@ script.
const res = await fetch(`!{server}/api/v2/stats/latest_comments?${searchParams}`) const res = await fetch(`!{server}/api/v2/stats/latest_comments?${searchParams}`)
const result = await res.json() const result = await res.json()
const { avatarCdn, avatarDefault } = await getAvatarValue() const { avatarCdn, avatarDefault } = await getAvatarValue()
const artalk = result.data.map(e => { const artalk = result.data
.filter(e => !e.is_pending) // Filter pending comments
.slice(0, !{newestCommentsLimit}) // Limit the number of comments
.map(e => {
const avatar = avatarCdn && e.email_encrypted ? `${avatarCdn}${e.email_encrypted}?${avatarDefault}` : '' const avatar = avatarCdn && e.email_encrypted ? `${avatarCdn}${e.email_encrypted}?${avatarDefault}` : ''
return { return {
'avatar': avatar, 'avatar': avatar,

View File

@@ -23,8 +23,9 @@ script.
result += '<div class="aside-list-item">' result += '<div class="aside-list-item">'
if (!{theme.aside.card_newest_comments.avatar} && array[i].avatar) { if (!{theme.aside.card_newest_comments.avatar} && array[i].avatar) {
const imgAttr = '!{theme.lazyload.enable ? "data-lazy-src" : "src"}' const imgAttr = '!{theme.lazyload.enable && !theme.lazyload.native ? "data-lazy-src" : "src"}'
result += `<a href="${array[i].url}" class="thumbnail"><img ${imgAttr}="${array[i].avatar}" alt="${array[i].nick}"></a>` const lazyloadNative = '!{theme.lazyload.enable && theme.lazyload.native ? "loading=\"lazy\"" : ""}'
result += `<a href="${array[i].url}" class="thumbnail"><img ${imgAttr}="${array[i].avatar}" alt="${array[i].nick}" ${lazyloadNative}></a>`
} }
result += `<div class="content"> result += `<div class="content">

View File

@@ -6,7 +6,7 @@ script.
const { changeContent, generateHtml, run } = window.newestComments const { changeContent, generateHtml, run } = window.newestComments
const getComment = ele => { const getComment = ele => {
fetch('https://disqus.com/api/3.0/forums/listPosts.json?forum=!{forum}&related=thread&limit=!{theme.aside.card_newest_comments.limit}&api_key=!{apiKey}') fetch('https://disqus.com/api/3.0/forums/listPosts.json?forum=!{forum}&related=thread&limit=!{newestCommentsLimit}&api_key=!{apiKey}')
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
const disqusArray = data.response.map(item => { const disqusArray = data.response.map(item => {

View File

@@ -32,7 +32,7 @@ script.
} }
const getComment = ele => { const getComment = ele => {
fetch('https://api.github.com/repos/!{userRepo}/issues/comments?sort=updated&direction=desc&per_page=!{theme.aside.card_newest_comments.limit}&page=1',{ fetch('https://api.github.com/repos/!{userRepo}/issues/comments?sort=updated&direction=desc&per_page=!{newestCommentsLimit}&page=1',{
"headers": { "headers": {
Accept: 'application/vnd.github.v3.html+json' Accept: 'application/vnd.github.v3.html+json'
} }

View File

@@ -1,7 +1,11 @@
- let { use } = theme.comments - let { use } = theme.comments
if use if use
- let forum,apiKey,userRepo -
let forum,apiKey,userRepo
let { limit:newestCommentsLimit } = theme.aside.card_newest_comments
if (newestCommentsLimit > 10 || newestCommentsLimit < 1) newestCommentsLimit = 6
case use[0] case use[0]
when 'Valine' when 'Valine'
include ./valine.pug include ./valine.pug

View File

@@ -7,7 +7,7 @@ script.
const { changeContent, generateHtml, run } = window.newestComments const { changeContent, generateHtml, run } = window.newestComments
const getComment = ele => { const getComment = ele => {
fetch('!{host}/api/v1/last/!{theme.aside.card_newest_comments.limit}?site=!{siteId}') fetch('!{host}/api/v1/last/!{newestCommentsLimit}?site=!{siteId}')
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
const remark42 = data.map(e => { const remark42 = data.map(e => {

View File

@@ -10,7 +10,7 @@ script.
twikoo.getRecentComments({ twikoo.getRecentComments({
envId: '!{theme.twikoo.envId}', envId: '!{theme.twikoo.envId}',
region: '!{theme.twikoo.region}', region: '!{theme.twikoo.region}',
pageSize: !{theme.aside.card_newest_comments.limit}, pageSize: !{newestCommentsLimit},
includeReply: true includeReply: true
}).then(res => { }).then(res => {
const twikooArray = res.map(e => { const twikooArray = res.map(e => {

View File

@@ -27,7 +27,7 @@ script.
}, },
} }
fetch(`${serverURL}/1.1/classes/Comment?limit=!{theme.aside.card_newest_comments.limit}&order=-createdAt`,settings) fetch(`${serverURL}/1.1/classes/Comment?limit=!{newestCommentsLimit}&order=-createdAt`,settings)
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
const valineArray = data.results.map(e => { const valineArray = data.results.map(e => {

View File

@@ -9,7 +9,7 @@ script.
const getComment = async (ele) => { const getComment = async (ele) => {
try { try {
const res = await fetch('!{serverURL}/api/comment?type=recent&count=!{theme.aside.card_newest_comments.limit}', { method: 'GET' }) const res = await fetch('!{serverURL}/api/comment?type=recent&count=!{newestCommentsLimit}')
const result = await res.json() const result = await res.json()
const walineArray = result.data.map(e => { const walineArray = result.data.map(e => {
return { return {

View File

@@ -1,23 +0,0 @@
script.
(() => {
const panguFn = () => {
if (typeof pangu === 'object') pangu.autoSpacingPage()
else {
btf.getScript('!{url_for(theme.asset.pangu)}')
.then(() => {
pangu.autoSpacingPage()
})
}
}
const panguInit = () => {
if (!{theme.pangu.field === 'post'}){
GLOBAL_CONFIG_SITE.isPost && panguFn()
} else {
panguFn()
}
}
btf.addGlobalFn('pjaxComplete', panguInit, 'pangu')
document.addEventListener('DOMContentLoaded', panguInit)
})()

View File

@@ -35,7 +35,7 @@ script.
const insertData = async () => { const insertData = async () => {
try { try {
if (GLOBAL_CONFIG_SITE.isPost && config.page_pv) { if (GLOBAL_CONFIG_SITE.pageType === 'post' && config.page_pv) {
const pagePV = document.getElementById('umamiPV') const pagePV = document.getElementById('umamiPV')
if (pagePV) { if (pagePV) {
const data = await getData(true) const data = await getData(true)

View File

@@ -12,4 +12,3 @@
.toc-content.toc-div-class(class=tocExpandClass style="display:none")!=toc(page.origin, {list_number: tocNumber}) .toc-content.toc-div-class(class=tocExpandClass style="display:none")!=toc(page.origin, {list_number: tocNumber})
else else
.toc-content(class=tocExpandClass)!=toc(page.content, {list_number: tocNumber}) .toc-content(class=tocExpandClass)!=toc(page.content, {list_number: tocNumber})

View File

@@ -6,40 +6,39 @@ if theme.aside.card_webinfo.enable
.webinfo .webinfo
if theme.aside.card_webinfo.post_count if theme.aside.card_webinfo.post_count
.webinfo-item .webinfo-item
.item-name= _p('aside.card_webinfo.article_name') + " :" .item-name= `${_p('aside.card_webinfo.article_name')} :`
.item-count= site.posts.length .item-count= site.posts.length
if theme.aside.card_webinfo.runtime_date if theme.aside.card_webinfo.runtime_date
.webinfo-item .webinfo-item
.item-name= _p('aside.card_webinfo.runtime.name') + " :" .item-name= `${_p('aside.card_webinfo.runtime.name')} :`
.item-count#runtimeshow(data-publishDate=date_xml(theme.aside.card_webinfo.runtime_date)) .item-count#runtimeshow(data-publishDate=date_xml(theme.aside.card_webinfo.runtime_date))
i.fa-solid.fa-spinner.fa-spin i.fa-solid.fa-spinner.fa-spin
if theme.wordcount.enable && theme.wordcount.total_wordcount if theme.wordcount.enable && theme.wordcount.total_wordcount
.webinfo-item .webinfo-item
.item-name=_p('aside.card_webinfo.site_wordcount') + " :" .item-name= `${_p('aside.card_webinfo.site_wordcount')} :`
.item-count= totalcount(site) .item-count= totalcount(site)
if theme.umami_analytics.enable && theme.umami_analytics.UV_PV.site_uv if theme.umami_analytics.enable && theme.umami_analytics.UV_PV.site_uv
.webinfo-item .webinfo-item
.item-name= _p('aside.card_webinfo.site_uv_name') + " :" .item-name= `${_p('aside.card_webinfo.site_uv_name')} :`
.item-count#umami-site-uv .item-count#umami-site-uv
i.fa-solid.fa-spinner.fa-spin i.fa-solid.fa-spinner.fa-spin
else if theme.busuanzi.site_uv else if theme.busuanzi.site_uv
.webinfo-item .webinfo-item
.item-name= _p('aside.card_webinfo.site_uv_name') + " :" .item-name= `${_p('aside.card_webinfo.site_uv_name')} :`
.item-count#busuanzi_value_site_uv .item-count#busuanzi_value_site_uv
i.fa-solid.fa-spinner.fa-spin i.fa-solid.fa-spinner.fa-spin
if theme.umami_analytics.enable && theme.umami_analytics.UV_PV.site_pv if theme.umami_analytics.enable && theme.umami_analytics.UV_PV.site_pv
.webinfo-item .webinfo-item
.item-name= _p('aside.card_webinfo.site_pv_name') + " :" .item-name= `${_p('aside.card_webinfo.site_pv_name')} :`
.item-count#umami-site-pv .item-count#umami-site-pv
i.fa-solid.fa-spinner.fa-spin i.fa-solid.fa-spinner.fa-spin
else if theme.busuanzi.site_pv else if theme.busuanzi.site_pv
.webinfo-item .webinfo-item
.item-name= _p('aside.card_webinfo.site_pv_name') + " :" .item-name= `${_p('aside.card_webinfo.site_pv_name')} :`
.item-count#busuanzi_value_site_pv .item-count#busuanzi_value_site_pv
i.fa-solid.fa-spinner.fa-spin i.fa-solid.fa-spinner.fa-spin
if theme.aside.card_webinfo.last_push_date if theme.aside.card_webinfo.last_push_date
.webinfo-item .webinfo-item
.item-name= _p('aside.card_webinfo.last_push_date.name') + " :" .item-name= `${_p('aside.card_webinfo.last_push_date.name')} :`
.item-count#last-push-date(data-lastPushDate=date_xml(Date.now())) .item-count#last-push-date(data-lastPushDate=date_xml(Date.now()))
i.fa-solid.fa-spinner.fa-spin i.fa-solid.fa-spinner.fa-spin

View File

@@ -1,6 +1,6 @@
#aside-content.aside-content #aside-content.aside-content
//- post //- post
if is_post() if globalPageType === 'post'
- const tocStyle = page.toc_style_simple - const tocStyle = page.toc_style_simple
- const tocStyleVal = tocStyle === true || tocStyle === false ? tocStyle : theme.toc.style_simple - const tocStyleVal = tocStyle === true || tocStyle === false ? tocStyle : theme.toc.style_simple
if showToc && tocStyleVal if showToc && tocStyleVal

View File

@@ -5,7 +5,11 @@ block content
if top_img === false if top_img === false
include includes/header/post-info.pug include includes/header/post-info.pug
article#article-container.post-content!=page.content article#article-container.container.post-content
if theme.noticeOutdate.enable && page.noticeOutdate !== false
include includes/post/outdate-notice.pug
else
!=page.content
include includes/post/post-copyright.pug include includes/post/post-copyright.pug
.tag_share .tag_share
if (page.tags.length > 0 && theme.post_meta.post.tags) if (page.tags.length > 0 && theme.post_meta.post.tags)
@@ -29,4 +33,3 @@ block content
if page.comments !== false && theme.comments.use if page.comments !== false && theme.comments.use
- var commentsJsLoad = true - var commentsJsLoad = true
!=partial('includes/third-party/comments/index', {}, {cache: true}) !=partial('includes/third-party/comments/index', {}, {cache: true})

View File

@@ -1,6 +1,6 @@
{ {
"name": "hexo-theme-butterfly", "name": "hexo-theme-butterfly",
"version": "5.2.0", "version": "5.3.2",
"description": "A Simple and Card UI Design theme for Hexo", "description": "A Simple and Card UI Design theme for Hexo",
"main": "package.json", "main": "package.json",
"scripts": { "scripts": {

View File

@@ -9,7 +9,7 @@ activate_power_mode:
algolia_search: algolia_search:
name: algoliasearch name: algoliasearch
file: dist/lite/builds/browser.umd.js file: dist/lite/builds/browser.umd.js
version: 5.12.0 version: 5.19.0
aplayer_css: aplayer_css:
name: aplayer name: aplayer
file: dist/APlayer.min.css file: dist/APlayer.min.css
@@ -45,7 +45,7 @@ canvas_ribbon:
chartjs: chartjs:
name: chart.js name: chart.js
file: dist/chart.umd.js file: dist/chart.umd.js
version: 4.4.6 version: 4.4.7
clickShowText: clickShowText:
name: butterfly-extsrc name: butterfly-extsrc
file: dist/click-show-text.min.js file: dist/click-show-text.min.js
@@ -66,12 +66,12 @@ docsearch_css:
name: '@docsearch/css' name: '@docsearch/css'
other_name: docsearch-css other_name: docsearch-css
file: dist/style.css file: dist/style.css
version: 3.6.3 version: 3.8.2
docsearch_js: docsearch_js:
name: '@docsearch/js' name: '@docsearch/js'
other_name: docsearch-js other_name: docsearch-js
file: dist/umd/index.js file: dist/umd/index.js
version: 3.6.3 version: 3.8.2
egjs_infinitegrid: egjs_infinitegrid:
name: '@egjs/infinitegrid' name: '@egjs/infinitegrid'
other_name: egjs-infinitegrid other_name: egjs-infinitegrid
@@ -95,7 +95,7 @@ fontawesome:
name: '@fortawesome/fontawesome-free' name: '@fortawesome/fontawesome-free'
file: css/all.min.css file: css/all.min.css
other_name: font-awesome other_name: font-awesome
version: 6.6.0 version: 6.7.2
gitalk: gitalk:
name: gitalk name: gitalk
file: dist/gitalk.min.js file: dist/gitalk.min.js
@@ -111,17 +111,17 @@ instantpage:
instantsearch: instantsearch:
name: instantsearch.js name: instantsearch.js
file: dist/instantsearch.production.min.js file: dist/instantsearch.production.min.js
version: 4.75.3 version: 4.75.7
katex: katex:
name: katex name: katex
file: dist/katex.min.css file: dist/katex.min.css
other_name: KaTeX other_name: KaTeX
version: 0.16.11 version: 0.16.19
katex_copytex: katex_copytex:
name: katex name: katex
file: dist/contrib/copy-tex.min.js file: dist/contrib/copy-tex.min.js
other_name: KaTeX other_name: KaTeX
version: 0.16.11 version: 0.16.19
lazyload: lazyload:
name: vanilla-lazyload name: vanilla-lazyload
file: dist/lazyload.iife.min.js file: dist/lazyload.iife.min.js
@@ -137,7 +137,7 @@ medium_zoom:
mermaid: mermaid:
name: mermaid name: mermaid
file: dist/mermaid.min.js file: dist/mermaid.min.js
version: 11.4.0 version: 11.4.1
meting_js: meting_js:
name: butterfly-extsrc name: butterfly-extsrc
file: metingjs/dist/Meting.min.js file: metingjs/dist/Meting.min.js
@@ -152,10 +152,6 @@ pace_js:
other_name: pace other_name: pace
file: pace.min.js file: pace.min.js
version: 1.2.4 version: 1.2.4
pangu:
name: pangu
file: dist/browser/pangu.min.js
version: 4.0.7
pjax: pjax:
name: pjax name: pjax
file: pjax.min.js file: pjax.min.js
@@ -194,7 +190,7 @@ snackbar_css:
twikoo: twikoo:
name: twikoo name: twikoo
file: dist/twikoo.all.min.js file: dist/twikoo.all.min.js
version: 1.6.39 version: 1.6.41
typed: typed:
name: typed.js name: typed.js
file: dist/typed.umd.js file: dist/typed.umd.js
@@ -202,14 +198,14 @@ typed:
valine: valine:
name: valine name: valine
file: dist/Valine.min.js file: dist/Valine.min.js
version: 1.5.2 version: 1.5.3
waline_css: waline_css:
name: '@waline/client' name: '@waline/client'
file: dist/waline.css file: dist/waline.css
other_name: waline other_name: waline
version: 3.3.2 version: 3.4.3
waline_js: waline_js:
name: '@waline/client' name: '@waline/client'
file: dist/waline.js file: dist/waline.js
other_name: waline other_name: waline
version: 3.3.2 version: 3.4.3

View File

@@ -545,12 +545,9 @@ hexo.extend.filter.register('before_generate', () => {
bg_dark: '#1f1f1f' bg_dark: '#1f1f1f'
}, },
instantpage: false, instantpage: false,
pangu: {
enable: false,
field: 'site'
},
lazyload: { lazyload: {
enable: false, enable: false,
native: false,
field: 'site', field: 'site',
placeholder: null, placeholder: null,
blur: false blur: false
@@ -567,6 +564,7 @@ hexo.extend.filter.register('before_generate', () => {
enable: true, enable: true,
option: null option: null
}, },
structured_data: true,
css_prefix: true, css_prefix: true,
inject: { inject: {
head: null, head: null,

View File

@@ -9,6 +9,10 @@
const urlFor = require('hexo-util').url_for.bind(hexo) const urlFor = require('hexo-util').url_for.bind(hexo)
const lazyload = htmlContent => { const lazyload = htmlContent => {
if (hexo.theme.config.lazyload.native) {
return htmlContent.replace(/(<img.*?)(>)/ig, '$1 loading=\'lazy\'$2')
}
const bg = hexo.theme.config.lazyload.placeholder ? urlFor(hexo.theme.config.lazyload.placeholder) : 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7' const bg = hexo.theme.config.lazyload.placeholder ? urlFor(hexo.theme.config.lazyload.placeholder) : 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'
return htmlContent.replace(/(<img.*? src=)/ig, `$1 "${bg}" data-lazy-src=`) return htmlContent.replace(/(<img.*? src=)/ig, `$1 "${bg}" data-lazy-src=`)
} }

View File

@@ -1,33 +1,57 @@
/** /**
* Butterfly * Random cover for posts
* ramdom cover
*/ */
'use strict' 'use strict'
hexo.extend.filter.register('before_post_render', data => { hexo.extend.generator.register('post', locals => {
const imgTestReg = /\.(png|jpe?g|gif|svg|webp)(\?.*)?$/i const previousIndexes = []
const getRandomCover = defaultCover => {
if (!defaultCover) return false
if (!Array.isArray(defaultCover)) return defaultCover
const coverCount = defaultCover.length
if (coverCount === 1) {
return defaultCover[0]
}
const maxPreviousIndexes = coverCount === 2 ? 1 : (coverCount === 3 ? 2 : 3)
let index
do {
index = Math.floor(Math.random() * coverCount)
} while (previousIndexes.includes(index) && previousIndexes.length < coverCount)
previousIndexes.push(index)
if (previousIndexes.length > maxPreviousIndexes) {
previousIndexes.shift()
}
return defaultCover[index]
}
const handleImg = data => {
const imgTestReg = /\.(png|jpe?g|gif|svg|webp|avif)(\?.*)?$/i
let { cover: coverVal, top_img: topImg } = data let { cover: coverVal, top_img: topImg } = data
// Add path to top_img and cover if post_asset_folder is enabled // Add path to top_img and cover if post_asset_folder is enabled
if (hexo.config.post_asset_folder) { if (hexo.config.post_asset_folder) {
if (topImg && topImg.indexOf('/') === -1 && imgTestReg.test(topImg)) data.top_img = `${data.path}${topImg}` if (topImg && topImg.indexOf('/') === -1 && imgTestReg.test(topImg)) {
if (coverVal && coverVal.indexOf('/') === -1 && imgTestReg.test(coverVal)) data.cover = `${data.path}${coverVal}` data.top_img = `${data.path}${topImg}`
}
if (coverVal && coverVal.indexOf('/') === -1 && imgTestReg.test(coverVal)) {
data.cover = `${data.path}${coverVal}`
} }
const randomCoverFn = () => {
const { cover: { default_cover: defaultCover } } = hexo.theme.config
if (!defaultCover) return false
if (!Array.isArray(defaultCover)) return defaultCover
const num = Math.floor(Math.random() * defaultCover.length)
return defaultCover[num]
} }
if (coverVal === false) return data if (coverVal === false) return data
// If cover is not set, use random cover // If cover is not set, use random cover
if (!coverVal) { if (!coverVal) {
const randomCover = randomCoverFn() const { cover: { default_cover: defaultCover } } = hexo.theme.config
const randomCover = getRandomCover(defaultCover)
data.cover = randomCover data.cover = randomCover
coverVal = randomCover // update coverVal coverVal = randomCover // update coverVal
} }
@@ -37,4 +61,22 @@ hexo.extend.filter.register('before_post_render', data => {
} }
return data return data
}
// https://github.com/hexojs/hexo/blob/master/lib%2Fplugins%2Fgenerator%2Fpost.ts
const posts = locals.posts.sort('date').toArray()
const { length } = posts
return posts.map((post, i) => {
if (i) post.prev = posts[i - 1]
if (i < length - 1) post.next = posts[i + 1]
post.__post = true
return {
data: handleImg(post),
layout: 'post',
path: post.path
}
})
}) })

View File

@@ -2,38 +2,68 @@
hexo.extend.helper.register('aside_archives', function (options = {}) { hexo.extend.helper.register('aside_archives', function (options = {}) {
const { config, page, site, url_for, _p } = this const { config, page, site, url_for, _p } = this
const archiveDir = config.archive_dir const {
const { timezone } = config archive_dir: archiveDir,
const lang = toMomentLocale(page.lang || page.language || config.language) timezone,
const type = options.type || 'monthly' language
const format = options.format || (type === 'monthly' ? 'MMMM YYYY' : 'YYYY') } = config
const showCount = Object.prototype.hasOwnProperty.call(options, 'show_count') ? options.show_count : true
const order = options.order || -1 // Destructure and set default options with object destructuring
const limit = options.limit const {
type = 'monthly',
format = type === 'monthly' ? 'MMMM YYYY' : 'YYYY',
show_count: showCount = true,
order = -1,
limit,
transform
} = options
// Optimize locale handling
const lang = toMomentLocale(page.lang || page.language || language)
// Memoize comparison function to improve performance
const compareFunc = type === 'monthly' const compareFunc = type === 'monthly'
? (yearA, monthA, yearB, monthB) => yearA === yearB && monthA === monthB ? (yearA, monthA, yearB, monthB) => yearA === yearB && monthA === monthB
: (yearA, monthA, yearB, monthB) => yearA === yearB : (yearA, yearB) => yearA === yearB
const posts = site.posts.sort('date', order) // Early return if no posts
if (!posts.length) return '' if (!site.posts.length) return ''
const data = [] // Use reduce for more efficient data processing
posts.forEach(post => { const data = site.posts
.sort('date', order)
.reduce((acc, post) => {
let date = post.date.clone() let date = post.date.clone()
if (timezone) date = date.tz(timezone) if (timezone) date = date.tz(timezone)
const year = date.year() const year = date.year()
const month = date.month() + 1 const month = date.month() + 1
if (!data.length || !compareFunc(data[data.length - 1].year, data[data.length - 1].month, year, month)) {
if (lang) date = date.locale(lang) if (lang) date = date.locale(lang)
data.push({ name: date.format(format), year, month, count: 1 })
} else {
data[data.length - 1].count++
}
})
const link = item => { // Find or create archive entry
const lastEntry = acc[acc.length - 1]
if (!lastEntry || !compareFunc(
lastEntry.year,
lastEntry.month,
year,
month
)) {
acc.push({
name: date.format(format),
year,
month,
count: 1
})
} else {
lastEntry.count++
}
return acc
}, [])
// Create link generator function
const createArchiveLink = item => {
let url = `${archiveDir}/${item.year}/` let url = `${archiveDir}/${item.year}/`
if (type === 'monthly') { if (type === 'monthly') {
url += item.month < 10 ? `0${item.month}/` : `${item.month}/` url += item.month < 10 ? `0${item.month}/` : `${item.month}/`
@@ -41,37 +71,48 @@ hexo.extend.helper.register('aside_archives', function (options = {}) {
return url_for(url) return url_for(url)
} }
const len = data.length // Limit results efficiently
const limitLength = limit === 0 ? len : Math.min(len, limit) const limitedData = limit > 0
? data.slice(0, Math.min(data.length, limit))
: data
let result = ` // Use template literal for better readability
const archiveHeader = `
<div class="item-headline"> <div class="item-headline">
<i class="fas fa-archive"></i> <i class="fas fa-archive"></i>
<span>${_p('aside.card_archives')}</span> <span>${_p('aside.card_archives')}</span>
${len > limitLength ? `<a class="card-more-btn" href="${url_for(archiveDir)}/" title="${_p('aside.more_button')}"><i class="fas fa-angle-right"></i></a>` : ''} ${data.length > limitedData.length
? `<a class="card-more-btn" href="${url_for(archiveDir)}/"
title="${_p('aside.more_button')}">
<i class="fas fa-angle-right"></i>
</a>`
: ''}
</div> </div>
<ul class="card-archive-list">
` `
for (let i = 0; i < limitLength; i++) { // Use map for generating list items, join for performance
const item = data[i] const archiveList = `
result += ` <ul class="card-archive-list">
${limitedData.map(item => `
<li class="card-archive-list-item"> <li class="card-archive-list-item">
<a class="card-archive-list-link" href="${link(item)}"> <a class="card-archive-list-link" href="${createArchiveLink(item)}">
<span class="card-archive-list-date">${options.transform ? options.transform(item.name) : item.name}</span> <span class="card-archive-list-date">
${showCount ? `<span class="card-archive-list-count">${item.count}</span>` : ''} ${transform ? transform(item.name) : item.name}
</span>
${showCount
? `<span class="card-archive-list-count">${item.count}</span>`
: ''}
</a> </a>
</li> </li>
`).join('')}
</ul>
` `
}
result += '</ul>' return archiveHeader + archiveList
return result
}) })
const toMomentLocale = function (lang) { // Improved locale conversion function
if (!lang || lang === 'en' || lang === 'default') { const toMomentLocale = lang => {
return 'en' if (!lang || ['en', 'default'].includes(lang)) return 'en'
}
return lang.toLowerCase().replace('_', '-') return lang.toLowerCase().replace('_', '-')
} }

View File

@@ -131,3 +131,17 @@ hexo.extend.helper.register('shuoshuoFN', (data, page) => {
return finalResult return finalResult
}) })
hexo.extend.helper.register('getPageType', (page, isHome) => {
const { layout, tag, category, type, archive } = page
if (layout) return layout
if (tag) return 'tag'
if (category) return 'category'
if (archive) return 'archive'
if (type) {
if (type === 'tags' || type === 'categories') return type
else return 'page'
}
if (isHome) return 'home'
return 'post'
})

View File

@@ -3,7 +3,7 @@
* galleryGroup and gallery * galleryGroup and gallery
* {% galleryGroup [name] [descr] [url] [img] %} * {% galleryGroup [name] [descr] [url] [img] %}
* *
* {% gallery [button],%} * {% gallery [button],[limit],[firstLimit] %}
* {% gallery url,[url],[button] %} * {% gallery url,[url],[button] %}
*/ */
@@ -11,54 +11,66 @@
const urlFor = require('hexo-util').url_for.bind(hexo) const urlFor = require('hexo-util').url_for.bind(hexo)
const gallery = (args, content) => { const DEFAULT_LIMIT = 10
args = args.join(' ').split(',') const DEFAULT_FIRST_LIMIT = 10
let button = false const IMAGE_REGEX = /!\[(.*?)\]\(([^\s]*)\s*(?:["'](.*?)["']?)?\s*\)/g
let type = 'data'
let dataStr = ''
if (args[0] === 'url') { // Helper functions
[type, dataStr, button] = args // url,[link],[lazyload] const parseGalleryArgs = args => {
dataStr = urlFor(dataStr) const [type, ...rest] = args.join(' ').split(',').map(arg => arg.trim())
} else { return {
[button] = args // [lazyload] isUrl: type === 'url',
const regex = /!\[(.*?)\]\(([^\s]*)\s*(?:["'](.*?)["']?)?\s*\)/g params: type === 'url' ? rest : [type, ...rest]
let m
const arr = []
while ((m = regex.exec(content)) !== null) {
if (m.index === regex.lastIndex) {
regex.lastIndex++
} }
arr.push({ }
url: m[2],
alt: m[1], const parseImageContent = content => {
title: m[3] const images = []
let match
while ((match = IMAGE_REGEX.exec(content)) !== null) {
images.push({
url: match[2],
alt: match[1] || '',
title: match[3] || ''
}) })
} }
dataStr = JSON.stringify(arr) return images
} }
return `<div class="gallery-container" data-type="${type}" data-button="${button}"> const createGalleryHTML = (type, dataStr, button, limit, firstLimit) => {
return `<div class="gallery-container" data-type="${type}" data-button="${button}" data-limit="${limit}" data-first="${firstLimit}">
<div class="gallery-items">${dataStr}</div> <div class="gallery-items">${dataStr}</div>
</div>` </div>`
} }
const gallery = (args, content) => {
const { isUrl, params } = parseGalleryArgs(args)
if (isUrl) {
const [dataStr, button = false, limit = DEFAULT_LIMIT, firstLimit = DEFAULT_FIRST_LIMIT] = params
return createGalleryHTML('url', urlFor(dataStr), button, limit, firstLimit)
}
const [button = false, limit = DEFAULT_LIMIT, firstLimit = DEFAULT_FIRST_LIMIT] = params
const images = parseImageContent(content)
return createGalleryHTML('data', JSON.stringify(images), button, limit, firstLimit)
}
const galleryGroup = args => { const galleryGroup = args => {
const [name, descr, url, img] = args const [name = '', descr = '', url = '', img = ''] = args.map(arg => arg.trim())
const imgUrl = urlFor(img)
const urlLink = urlFor(url)
return `<figure class="gallery-group"> return `<figure class="gallery-group">
<img class="gallery-group-img no-lightbox" src='${imgUrl}' alt="Group Image Gallery"> <img class="gallery-group-img no-lightbox" src='${urlFor(img)}' alt="Group Image Gallery">
<figcaption> <figcaption>
<div class="gallery-group-name">${name}</div> <div class="gallery-group-name">${name}</div>
<p>${descr}</p> <p>${descr}</p>
<a href='${urlLink}'></a> <a href='${urlFor(url)}'></a>
</figcaption> </figcaption>
</figure> </figure>`
`
} }
// Register tags
hexo.extend.tag.register('gallery', gallery, { ends: true }) hexo.extend.tag.register('gallery', gallery, { ends: true })
hexo.extend.tag.register('galleryGroup', galleryGroup) hexo.extend.tag.register('galleryGroup', galleryGroup)

View File

@@ -16,48 +16,35 @@
'use strict' 'use strict'
const parseArgs = args => { const parseArgs = args => args.join(' ').split(',')
return args.join(' ').split(',')
}
const generateStyle = (bg, color) => { const generateStyle = (bg, color) => {
let style = 'style="' let style = 'style="'
if (bg) { if (bg) style += `background-color: ${bg};`
style += `background-color: ${bg};` if (color) style += `color: ${color}`
}
if (color) {
style += `color: ${color}`
}
style += '"' style += '"'
return style return style
} }
const hideInline = args => { const hideInline = args => {
const [content, display = 'Click', bg = false, color = false] = parseArgs(args) const [content, display = 'Click', bg = false, color = false] = parseArgs(args)
const group = generateStyle(bg, color) const style = generateStyle(bg, color)
return `<span class="hide-inline"><button type="button" class="hide-button" ${style}>${display}</button><span class="hide-content">${content}</span></span>`
return `<span class="hide-inline"><button type="button" class="hide-button" ${group}>${display}
</button><span class="hide-content">${content}</span></span>`
} }
const hideBlock = (args, content) => { const hideBlock = (args, content) => {
const [display = 'Click', bg = false, color = false] = parseArgs(args) const [display = 'Click', bg = false, color = false] = parseArgs(args)
const group = generateStyle(bg, color) const style = generateStyle(bg, color)
const renderedContent = hexo.render.renderSync({ text: content, engine: 'markdown' })
return `<div class="hide-block"><button type="button" class="hide-button" ${group}>${display} return `<div class="hide-block"><button type="button" class="hide-button" ${style}>${display}</button><div class="hide-content">${renderedContent}</div></div>`
</button><div class="hide-content">${hexo.render.renderSync({ text: content, engine: 'markdown' })}</div></div>`
} }
const hideToggle = (args, content) => { const hideToggle = (args, content) => {
const [display, bg = false, color = false] = parseArgs(args) const [display, bg = false, color = false] = parseArgs(args)
const group = generateStyle(bg, color) const style = generateStyle(bg, color)
let border = '' const border = bg ? `style="border: 1px solid ${bg}"` : ''
const renderedContent = hexo.render.renderSync({ text: content, engine: 'markdown' })
if (bg) { return `<details class="toggle" ${border}><summary class="toggle-button" ${style}>${display}</summary><div class="toggle-content">${renderedContent}</div></details>`
border = `style="border: 1px solid ${bg}"`
}
return `<details class="toggle" ${border}><summary class="toggle-button" ${group}>${display}</summary><div class="toggle-content">${hexo.render.renderSync({ text: content, engine: 'markdown' })}</div></details>`
} }
hexo.extend.tag.register('hideInline', hideInline) hexo.extend.tag.register('hideInline', hideInline)

View File

@@ -1,9 +1,9 @@
/** /**
* inlineImg 圖片 * inlineImg
* @param {Array} args 圖片名稱和高度 * @param {Array} args - Image name and height
* @param {string} args[0] 圖片名稱 * @param {string} args[0] - Image name
* @param {number} args[1] 圖片高度 * @param {number} args[1] - Image height
* @returns {string} 圖片標籤 * @returns {string} - Image tag
*/ */
'use strict' 'use strict'

View File

@@ -1,41 +1,50 @@
/** /**
* timeline * Timeline tag for Hexo
* by Jerry * Syntax:
* {% timeline [headline],[color] %}
* <!-- timeline [title] -->
* [content]
* <!-- endtimeline -->
* <!-- timeline [title] -->
* [content]
* <!-- endtimeline -->
* {% endtimeline %}
*/ */
'use strict' 'use strict'
const timeLineFn = (args, content) => { const timeLineFn = (args, content) => {
const tlBlock = /<!--\s*timeline (.*?)\s*-->\n([\w\W\s\S]*?)<!--\s*endtimeline\s*-->/g // Use named capture groups for better readability
const tlBlock = /<!--\s*timeline\s*(?<title>.*?)\s*-->\n(?<content>[\s\S]*?)<!--\s*endtimeline\s*-->/g
let result = '' // Pre-compile markdown render function
let color = '' const renderMd = text => hexo.render.renderSync({ text, engine: 'markdown' })
let text = ''
if (args.length) {
[text, color] = args.join(' ').split(',')
const mdContent = hexo.render.renderSync({ text, engine: 'markdown' })
result += `<div class='timeline-item headline'><div class='timeline-item-title'><div class='item-circle'>${mdContent}</div></div></div>`
}
const matches = [] // Parse arguments more efficiently
let match const [text, color = ''] = args.length ? args.join(' ').split(',') : []
while ((match = tlBlock.exec(content)) !== null) { // Build initial headline if text exists
matches.push(match[1]) const headline = text
matches.push(match[2]) ? `<div class='timeline-item headline'>
} <div class='timeline-item-title'>
<div class='item-circle'>${renderMd(text)}</div>
</div>
</div>`
: ''
for (let i = 0; i < matches.length; i += 2) { // Match all timeline blocks in one pass and transform
const tlChildTitle = hexo.render.renderSync({ text: matches[i], engine: 'markdown' }) const items = Array.from(content.matchAll(tlBlock))
const tlChildContent = hexo.render.renderSync({ text: matches[i + 1], engine: 'markdown' }) .map(({ groups: { title, content } }) =>
`<div class='timeline-item'>
<div class='timeline-item-title'>
<div class='item-circle'>${renderMd(title)}</div>
</div>
<div class='timeline-item-content'>${renderMd(content)}</div>
</div>`
)
.join('')
const tlTitleHtml = `<div class='timeline-item-title'><div class='item-circle'>${tlChildTitle}</div></div>` return `<div class="timeline ${color}">${headline}${items}</div>`
const tlContentHtml = `<div class='timeline-item-content'>${tlChildContent}</div>`
result += `<div class='timeline-item'>${tlTitleHtml + tlContentHtml}</div>`
}
return `<div class="timeline ${color || ''}">${result}</div>`
} }
hexo.extend.tag.register('timeline', timeLineFn, { ends: true }) hexo.extend.tag.register('timeline', timeLineFn, { ends: true })

View File

@@ -42,7 +42,7 @@ $code-block
counter-reset: line counter-reset: line
white-space: pre-wrap white-space: pre-wrap
#article-container .container
pre, pre,
code code
font-size: $code-font-size font-size: $code-font-size
@@ -164,7 +164,7 @@ $code-block
border: none border: none
if $highlight_macstyle if $highlight_macstyle
#article-container .container
figure.highlight figure.highlight
margin: 0 0 24px margin: 0 0 24px
border-radius: 7px border-radius: 7px
@@ -202,7 +202,7 @@ if $highlight_macstyle
transform: rotate(90deg) transform: rotate(90deg)
if hexo-config('code_blocks.height_limit') if hexo-config('code_blocks.height_limit')
#article-container .container
.code-expand-btn .code-expand-btn
position: absolute position: absolute
bottom: 0 bottom: 0
@@ -243,7 +243,7 @@ if hexo-config('code_blocks.height_limit')
opacity: .6 opacity: .6
if hexo-config('code_blocks.fullpage') if hexo-config('code_blocks.fullpage')
#article-container .container
figure.highlight.code-fullpage figure.highlight.code-fullpage
position: fixed position: fixed
top: 0 top: 0

View File

@@ -1,7 +1,7 @@
if $highlight_theme != false if $highlight_theme != false
@require 'diff' @require 'diff'
#article-container .container
figure.highlight figure.highlight
.line .line
if wordWrap if wordWrap

View File

@@ -4,7 +4,7 @@ if $prismjs_line_number
if $highlight_theme != false if $highlight_theme != false
@require 'diff' @require 'diff'
#article-container .container
pre[class*='language-'] pre[class*='language-']
// scrollbar - firefox // scrollbar - firefox
@-moz-document url-prefix() @-moz-document url-prefix()

View File

@@ -1,4 +1,4 @@
#article-container .container
pre[class*='language-'] pre[class*='language-']
&.line-numbers &.line-numbers
position: relative position: relative

View File

@@ -12,11 +12,10 @@
width: 100% !important width: 100% !important
.pagination-related .pagination-related
width: 50%
height: 150px height: 150px
+maxWidth768() +minWidth768()
width: 100% flex: 1
.info-1 .info-1
.info-item-2 .info-item-2
@@ -31,6 +30,10 @@
margin-top: 40px margin-top: 40px
width: 100% width: 100%
addBorderRadius() addBorderRadius()
display: flex
+maxWidth768()
flex-direction: column
.layout .layout
.pagination .pagination

View File

@@ -69,7 +69,7 @@ beautify()
hr hr
@extend .custom-hr @extend .custom-hr
#article-container .container
word-wrap: break-word word-wrap: break-word
overflow-wrap: break-word overflow-wrap: break-word
@@ -236,7 +236,7 @@ beautify()
&:hover &:hover
text-decoration: none text-decoration: none
.post-outdate-notice #post-outdate-notice
position: relative position: relative
margin: 0 0 20px margin: 0 0 20px
padding: .5em 1.2em padding: .5em 1.2em
@@ -244,6 +244,9 @@ beautify()
color: $noticeOutdate-color color: $noticeOutdate-color
addBorderRadius(3) addBorderRadius(3)
.num
padding: 0 4px
if hexo-config('noticeOutdate.style') == 'flat' if hexo-config('noticeOutdate.style') == 'flat'
padding: .5em 1em .5em 2.6em padding: .5em 1em .5em 2.6em
border-left: 5px solid $noticeOutdate-border border-left: 5px solid $noticeOutdate-border

View File

@@ -153,7 +153,7 @@ if hexo-config('math.use')
.aplayer .aplayer
color: $font-black color: $font-black
#article-container .container
.aplayer .aplayer
margin: 0 0 20px margin: 0 0 20px

View File

@@ -41,7 +41,7 @@ if hexo-config('darkmode.enable') || hexo-config('display_mode') == 'dark'
background-color: alpha($dark-black, .7) background-color: alpha($dark-black, .7)
content: '' content: ''
#article-container .container
code code
background: #2c2c2c background: #2c2c2c
@@ -87,9 +87,9 @@ if hexo-config('darkmode.enable') || hexo-config('display_mode') == 'dark'
.hide-button, .hide-button,
.btn-beautify, .btn-beautify,
.hl-label, .hl-label,
.post-outdate-notice, #post-outdate-notice,
.error-img, .error-img,
#article-container iframe, .container iframe,
.gist, .gist,
.ads-wrap .ads-wrap
filter: brightness(.8) filter: brightness(.8)

View File

@@ -77,13 +77,13 @@ if hexo-config('readmode')
#footer, #footer,
#post > *:not(#post-info):not(.post-content), #post > *:not(#post-info):not(.post-content),
#nav, #nav,
.post-outdate-notice, #post-outdate-notice,
#web_bg, #web_bg,
#rightside, #rightside,
.not-top-img .not-top-img
display: none !important display: none !important
#article-container .container
a a
color: #99a9bf color: #99a9bf

View File

@@ -1,4 +1,4 @@
#article-container .container
.flink .flink
margin-bottom: 20px margin-bottom: 20px

View File

@@ -1,4 +1,4 @@
#article-container .container
.btn-center .btn-center
margin: 0 0 20px margin: 0 0 20px
text-align: center text-align: center

View File

@@ -1,4 +1,4 @@
#article-container .container
figure.gallery-group figure.gallery-group
position: relative position: relative
float: left float: left

View File

@@ -1,4 +1,4 @@
#article-container .container
.inline-img .inline-img
display: inline display: inline
margin: 0 3px margin: 0 3px

View File

@@ -1,4 +1,4 @@
#article-container .container
.series-items .series-items
a a
&:hover &:hover

View File

@@ -1,5 +1,5 @@
#article-container .container
.tabs .tabs
position: relative position: relative
margin: 0 0 20px margin: 0 0 20px

View File

@@ -1,4 +1,4 @@
#article-container .container
.timeline .timeline
margin: 0 10px 20px margin: 0 10px 20px
padding: 14px 0 5px 20px padding: 14px 0 5px 20px

View File

@@ -16,7 +16,7 @@ $theme-toc-color = $themeColorEnable && hexo-config('theme_color.toc_color') ? c
$chinseFont = $language == 'zh-CN' ? 'Microsoft YaHei' : 'Microsoft JhengHei' $chinseFont = $language == 'zh-CN' ? 'Microsoft YaHei' : 'Microsoft JhengHei'
$dafault-font-family = -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue', Lato, Roboto, 'PingFang SC', $chinseFont, sans-serif $dafault-font-family = -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue', Lato, Roboto, 'PingFang SC', $chinseFont, sans-serif
$dafault-code-font = consolas, Menlo, 'PingFang SC', $chinseFont, sans-serif $dafault-code-font = consolas, Menlo, monospace, 'PingFang SC', $chinseFont, sans-serif
$font-family = hexo-config('font.font_family') ? unquote(hexo-config('font.font_family')) : $dafault-font-family $font-family = hexo-config('font.font_family') ? unquote(hexo-config('font.font_family')) : $dafault-font-family
$code-font-family = hexo-config('font.code_font_family') ? unquote(hexo-config('font.code_font_family')) : $dafault-code-font $code-font-family = hexo-config('font.code_font_family') ? unquote(hexo-config('font.code_font_family')) : $dafault-code-font

View File

@@ -224,14 +224,23 @@ document.addEventListener('DOMContentLoaded', () => {
*/ */
const fetchUrl = async url => { const fetchUrl = async url => {
try {
const response = await fetch(url) const response = await fetch(url)
return await response.json() return await response.json()
} catch (error) {
console.error('Failed to fetch URL:', error)
return []
}
} }
const runJustifiedGallery = (item, data, isButton = false, tabs) => { const runJustifiedGallery = (container, data, config) => {
const dataLength = data.length const { isButton, limit, firstLimit, tabs } = config
const ig = new InfiniteGrid.JustifiedInfiniteGrid(item, { const dataLength = data.length
const maxGroupKey = Math.ceil((dataLength - firstLimit) / limit + 1)
// Gallery configuration
const igConfig = {
gap: 5, gap: 5,
isConstantSize: true, isConstantSize: true,
sizeRange: [150, 600], sizeRange: [150, 600],
@@ -239,132 +248,130 @@ document.addEventListener('DOMContentLoaded', () => {
// observeChildren: true, // observeChildren: true,
useTransform: true useTransform: true
// useRecycle: false // useRecycle: false
})
const replaceDq = str => str.replace(/"/g, '&quot;') // replace double quotes to &quot;
const getItems = (nextGroupKey, count) => {
const nextItems = []
const startCount = (nextGroupKey - 1) * count
for (let i = 0; i < count; ++i) {
const num = startCount + i
if (num >= dataLength) {
break
} }
const item = data[num] const ig = new InfiniteGrid.JustifiedInfiniteGrid(container, igConfig)
const alt = item.alt ? `alt="${replaceDq(item.alt)}"` : ''
const title = item.title ? `title="${replaceDq(item.title)}"` : ''
nextItems.push(`<div class="item">
<img src="${item.url}" data-grid-maintained-target="true" ${alt + title} />
</div>`)
}
return nextItems
}
const buttonText = GLOBAL_CONFIG.infinitegrid.buttonText
const addButton = item => {
const button = document.createElement('button')
button.innerHTML = buttonText + '<i class="fa-solid fa-arrow-down"></i>'
button.addEventListener('click', e => {
e.target.closest('button').remove()
btf.setLoading.add(item)
appendItem(ig.getGroups().length + 1, 10)
}, { once: true })
item.insertAdjacentElement('afterend', button)
}
const appendItem = (nextGroupKey, count) => {
ig.append(getItems(nextGroupKey, count), nextGroupKey)
}
const maxGroupKey = Math.ceil(dataLength / 10)
let isLayoutHidden = false let isLayoutHidden = false
const completeFn = e => { // Utility functions
if (tabs) { const sanitizeString = str => (str && str.replace(/"/g, '&quot;')) || ''
const parentNode = item.parentNode
const createImageItem = item => {
const alt = item.alt ? `alt="${sanitizeString(item.alt)}"` : ''
const title = item.title ? `title="${sanitizeString(item.title)}"` : ''
return `<div class="item">
<img src="${item.url}" data-grid-maintained-target="true" ${alt} ${title} />
</div>`
}
const getItems = (nextGroupKey, count, isFirst = false) => {
const startIndex = isFirst ? (nextGroupKey - 1) * count : (nextGroupKey - 2) * count + firstLimit
return data.slice(startIndex, startIndex + count).map(createImageItem)
}
// Load more button
const addLoadMoreButton = container => {
const button = document.createElement('button')
button.innerHTML = `${GLOBAL_CONFIG.infinitegrid.buttonText}<i class="fa-solid fa-arrow-down"></i>`
button.addEventListener('click', () => {
button.remove()
btf.setLoading.add(container)
appendItems(ig.getGroups().length + 1, limit)
}, { once: true })
container.insertAdjacentElement('afterend', button)
}
const appendItems = (nextGroupKey, count, isFirst) => {
ig.append(getItems(nextGroupKey, count, isFirst), nextGroupKey)
}
// Event handlers
const handleRenderComplete = e => {
if (tabs) {
const parentNode = container.parentNode
if (isLayoutHidden) { if (isLayoutHidden) {
parentNode.style.visibility = 'visible' parentNode.style.visibility = 'visible'
} }
if (container.offsetHeight === 0) {
if (item.offsetHeight === 0) {
parentNode.style.visibility = 'hidden' parentNode.style.visibility = 'hidden'
isLayoutHidden = true isLayoutHidden = true
} }
} }
const { updated, isResize, mounted } = e const { updated, isResize, mounted } = e
if (!updated.length || !mounted.length || isResize) { if (!updated.length || !mounted.length || isResize) return
return
}
btf.loadLightbox(item.querySelectorAll('img:not(.medium-zoom-image)')) btf.loadLightbox(container.querySelectorAll('img:not(.medium-zoom-image)'))
if (ig.getGroups().length === maxGroupKey) { if (ig.getGroups().length === maxGroupKey) {
btf.setLoading.remove(item) btf.setLoading.remove(container)
!tabs && ig.off('renderComplete', completeFn) !tabs && ig.off('renderComplete', handleRenderComplete)
return return
} }
if (isButton) { if (isButton) {
btf.setLoading.remove(item) btf.setLoading.remove(container)
addButton(item) addLoadMoreButton(container)
} }
} }
const requestAppendFn = btf.debounce(e => { const handleRequestAppend = btf.debounce(e => {
const nextGroupKey = (+e.groupKey || 0) + 1 const nextGroupKey = (+e.groupKey || 0) + 1
appendItem(nextGroupKey, 10)
if (nextGroupKey === maxGroupKey) { if (nextGroupKey === 1) appendItems(nextGroupKey, firstLimit, true)
ig.off('requestAppend', requestAppendFn) else appendItems(nextGroupKey, limit)
}
if (nextGroupKey === maxGroupKey) ig.off('requestAppend', handleRequestAppend)
}, 300) }, 300)
btf.setLoading.add(item) btf.setLoading.add(container)
ig.on('renderComplete', completeFn) ig.on('renderComplete', handleRenderComplete)
if (isButton) { if (isButton) {
appendItem(1, 10) appendItems(1, firstLimit, true)
} else { } else {
ig.on('requestAppend', requestAppendFn) ig.on('requestAppend', handleRequestAppend)
ig.renderItems() ig.renderItems()
} }
btf.addGlobalFn('pjaxSendOnce', () => { ig.destroy() }) btf.addGlobalFn('pjaxSendOnce', () => ig.destroy())
} }
const addJustifiedGallery = async (ele, tabs = false) => { const addJustifiedGallery = async (elements, tabs = false) => {
if (!ele.length) return if (!elements.length) return
const init = async () => {
for (const item of ele) { const initGallery = async () => {
if (btf.isHidden(item) || item.classList.contains('loaded')) continue for (const element of elements) {
if (btf.isHidden(element) || element.classList.contains('loaded')) continue
const config = {
isButton: element.getAttribute('data-button') === 'true',
limit: parseInt(element.getAttribute('data-limit'), 10),
firstLimit: parseInt(element.getAttribute('data-first'), 10),
tabs
}
const container = element.firstElementChild
const content = container.textContent
container.textContent = ''
element.classList.add('loaded')
const isButton = item.getAttribute('data-button') === 'true'
const children = item.firstElementChild
const text = children.textContent
children.textContent = ''
item.classList.add('loaded')
try { try {
const content = item.getAttribute('data-type') === 'url' ? await fetchUrl(text) : JSON.parse(text) const data = element.getAttribute('data-type') === 'url' ? await fetchUrl(content) : JSON.parse(content)
runJustifiedGallery(children, content, isButton, tabs) runJustifiedGallery(container, data, config)
} catch (e) { } catch (error) {
console.error('Gallery data parsing failed:', e) console.error('Gallery data parsing failed:', error)
} }
} }
} }
if (typeof InfiniteGrid === 'function') { if (typeof InfiniteGrid === 'function') {
init() await initGallery()
} else { } else {
await btf.getScript(`${GLOBAL_CONFIG.infinitegrid.js}`) await btf.getScript(GLOBAL_CONFIG.infinitegrid.js)
init() await initGallery()
} }
} }
@@ -807,18 +814,14 @@ document.addEventListener('DOMContentLoaded', () => {
} }
const addPostOutdateNotice = () => { const addPostOutdateNotice = () => {
const { limitDay, messagePrev, messageNext, position } = GLOBAL_CONFIG.noticeOutdate const ele = document.getElementById('post-outdate-notice')
const diffDay = btf.diffDate(GLOBAL_CONFIG_SITE.postUpdate) if (!ele) return
const { limitDay, messagePrev, messageNext, postUpdate } = JSON.parse(ele.getAttribute('data'))
const diffDay = btf.diffDate(postUpdate)
if (diffDay >= limitDay) { if (diffDay >= limitDay) {
const ele = document.createElement('div')
ele.className = 'post-outdate-notice'
ele.textContent = `${messagePrev} ${diffDay} ${messageNext}` ele.textContent = `${messagePrev} ${diffDay} ${messageNext}`
const $targetEle = document.getElementById('article-container') ele.hidden = false
if (position === 'top') {
$targetEle.insertBefore(ele, $targetEle.firstChild)
} else {
$targetEle.appendChild(ele)
}
} }
} }
@@ -868,7 +871,7 @@ document.addEventListener('DOMContentLoaded', () => {
menuMask && menuMask.addEventListener('click', () => { sidebarFn.close() }) menuMask && menuMask.addEventListener('click', () => { sidebarFn.close() })
clickFnOfSubMenu() clickFnOfSubMenu()
GLOBAL_CONFIG.islazyload && lazyloadImg() GLOBAL_CONFIG.islazyloadPlugin && lazyloadImg()
GLOBAL_CONFIG.copyright !== undefined && addCopyright() GLOBAL_CONFIG.copyright !== undefined && addCopyright()
if (GLOBAL_CONFIG.autoDarkmode) { if (GLOBAL_CONFIG.autoDarkmode) {
@@ -894,8 +897,8 @@ document.addEventListener('DOMContentLoaded', () => {
initAdjust() initAdjust()
justifiedIndexPostUI() justifiedIndexPostUI()
if (GLOBAL_CONFIG_SITE.isPost) { if (GLOBAL_CONFIG_SITE.pageType === 'post') {
GLOBAL_CONFIG.noticeOutdate !== undefined && addPostOutdateNotice() addPostOutdateNotice()
GLOBAL_CONFIG.relativeDate.post && relativeDate(document.querySelectorAll('#post-meta time')) GLOBAL_CONFIG.relativeDate.post && relativeDate(document.querySelectorAll('#post-meta time'))
} else { } else {
GLOBAL_CONFIG.relativeDate.homepage && relativeDate(document.querySelectorAll('#recent-posts time')) GLOBAL_CONFIG.relativeDate.homepage && relativeDate(document.querySelectorAll('#recent-posts time'))
@@ -904,11 +907,11 @@ document.addEventListener('DOMContentLoaded', () => {
toggleCardCategory() toggleCardCategory()
} }
GLOBAL_CONFIG_SITE.isHome && scrollDownInIndex() GLOBAL_CONFIG_SITE.pageType === 'home' && scrollDownInIndex()
scrollFn() scrollFn()
forPostFn() forPostFn()
!GLOBAL_CONFIG_SITE.isShuoshuo && btf.switchComments(document) GLOBAL_CONFIG_SITE.pageType !== 'shuoshuo' && btf.switchComments(document)
openMobileMenu() openMobileMenu()
} }