Compare commits

..

80 Commits
5.0.0 ... 5.4.1

213 changed files with 16464 additions and 15455 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
.DS_Store
node_modules/

View File

@@ -86,7 +86,7 @@ npm i hexo-theme-butterfly
- [x] Share (Sharejs/Addtoany) - [x] Share (Sharejs/Addtoany)
- [X] Comment (Disqus/Disqusjs/Livere/Gitalk/Valine/Waline/Utterances/Facebook Comments/Twikoo/Giscus/Remark42/artalk) - [X] Comment (Disqus/Disqusjs/Livere/Gitalk/Valine/Waline/Utterances/Facebook Comments/Twikoo/Giscus/Remark42/artalk)
- [x] Multiple Comment System Support - [x] Multiple Comment System Support
- [x] Online Chats (Chatra/Tidio/Daovoice/Crisp) - [x] Online Chats (Chatra/Tidio/Crisp)
- [x] Web analytics - [x] Web analytics
- [x] Google AdSense - [x] Google AdSense
- [x] Webmaster Verification - [x] Webmaster Verification
@@ -98,9 +98,10 @@ npm i hexo-theme-butterfly
- [x] Busuanzi visitor counter - [x] Busuanzi visitor counter
- [x] Medium Zoom/Fancybox - [x] Medium Zoom/Fancybox
- [x] Mermaid - [x] Mermaid
- [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

@@ -86,7 +86,7 @@ theme: butterfly
- [x] 多種分享系統Sharejs/Addtoany - [x] 多種分享系統Sharejs/Addtoany
- [X] 多種評論系統Disqus/Disqusjs/Livere/Gitalk/Valine/Waline/Utterances/Facebook Comments/Twikoo/Giscus/Remark42/artalk - [X] 多種評論系統Disqus/Disqusjs/Livere/Gitalk/Valine/Waline/Utterances/Facebook Comments/Twikoo/Giscus/Remark42/artalk
- [x] 支持雙評論部署 - [x] 支持雙評論部署
- [x] 多種在線聊天Chatra/Tidio/Daovoice/Crisp - [x] 多種在線聊天Chatra/Tidio/Crisp
- [x] 多種分析系統 - [x] 多種分析系統
- [x] 谷歌廣告/手動廣告位置 - [x] 谷歌廣告/手動廣告位置
- [x] 各種站長驗證 - [x] 各種站長驗證
@@ -98,9 +98,10 @@ theme: butterfly
- [x] 不蒜子訪問統計 - [x] 不蒜子訪問統計
- [x] 兩種大圖模式Medium Zoom/Fancybox - [x] 兩種大圖模式Medium Zoom/Fancybox
- [x] Mermaid 圖表顯示 - [x] Mermaid 圖表顯示
- [x] Chart.js 圖表顯示
- [x] 照片牆 - [x] 照片牆
- [x] 圖片懶加載 - [x] 圖片懶加載
- [x] Instantpage/Pangu/Snackbar彈窗/PWA...... - [x] Instantpage/Snackbar彈窗/PWA......
## ✨ 貢獻者 ## ✨ 貢獻者

View File

@@ -13,6 +13,7 @@ nav:
# Navigation bar logo image # Navigation bar logo image
logo: logo:
display_title: true display_title: true
display_post_title: true
# Whether to fix navigation bar # Whether to fix navigation bar
fixed: false fixed: false
@@ -158,7 +159,7 @@ subtitle:
# Choose: false/1/2/3 # Choose: false/1/2/3
# false - disable the function # false - disable the function
# 1 - hitokoto.cn # 1 - hitokoto.cn
# 2 - yijuzhan.com # 2 - https://api.aa1.cn/doc/yiyan.html
# 3 - jinrishici.com # 3 - jinrishici.com
source: false source: false
# If you close the typewriter effect, the subtitle will only show the first line of sub # If you close the typewriter effect, the subtitle will only show the first line of sub
@@ -254,12 +255,15 @@ noticeOutdate:
# Footer Settings # Footer Settings
# -------------------------------------- # --------------------------------------
footer: footer:
nav:
owner: owner:
enable: true enable: true
since: 2019 since: 2025
custom_text:
# Copyright of theme and framework # Copyright of theme and framework
copyright: true copyright:
enable: true
version: true
custom_text:
# -------------------------------------- # --------------------------------------
# Aside Settings # Aside Settings
@@ -399,6 +403,9 @@ rightside_item_order:
# Default: toc,chat,comment # Default: toc,chat,comment
show: show:
# Animation for the bottom right config button
rightside_config_animation: true
# -------------------------------------- # --------------------------------------
# Global Settings # Global Settings
# -------------------------------------- # --------------------------------------
@@ -602,7 +609,7 @@ facebook_comments:
pageSize: 10 pageSize: 10
# Choose: social / time / reverse_time # Choose: social / time / reverse_time
order_by: social order_by: social
lang: zh_TW lang: en_US
# Twikoo # Twikoo
# https://github.com/imaegoo/twikoo # https://github.com/imaegoo/twikoo
@@ -645,7 +652,7 @@ artalk:
# -------------------------------------- # --------------------------------------
chat: chat:
# Choose: chatra/tidio/daovoice/crisp # Choose: chatra/tidio/crisp
# Leave it empty if you don't need chat # Leave it empty if you don't need chat
use: use:
# Chat Button [recommend] # Chat Button [recommend]
@@ -662,10 +669,6 @@ chatra:
tidio: tidio:
public_key: public_key:
# http://dashboard.daovoice.io/app
daovoice:
app_id:
# https://crisp.chat/en/ # https://crisp.chat/en/
crisp: crisp:
website_id: website_id:
@@ -700,6 +703,12 @@ umami_analytics:
# Umami Cloud (API key) / self-hosted Umami (token) # Umami Cloud (API key) / self-hosted Umami (token)
token: token:
# https://www.googletagmanager.com/
google_tag_manager:
tag_id:
# optional
domain:
# -------------------------------------- # --------------------------------------
# Advertisement # Advertisement
# -------------------------------------- # --------------------------------------
@@ -924,6 +933,25 @@ mermaid:
light: default light: default
dark: dark dark: dark
# chartjs
# see https://www.chartjs.org/docs/latest/
chartjs:
enable: false
# Do not modify unless you understand how they work.
# The default settings are only used when the MD syntax is not specified.
# General font color for the chart
fontColor:
light: 'rgba(0, 0, 0, 0.8)'
dark: 'rgba(255, 255, 255, 0.8)'
# General border color for the chart
borderColor:
light: 'rgba(0, 0, 0, 0.1)'
dark: 'rgba(255, 255, 255, 0.2)'
# Background color for scale labels on radar and polar area charts
scale_ticks_backdropColor:
light: 'transparent'
dark: 'transparent'
# Note - Bootstrap Callout # Note - Bootstrap Callout
note: note:
# Note tag style values: # Note tag style values:
@@ -968,17 +996,12 @@ 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:
enable: false enable: false
# Use browser's native lazyload instead of vanilla-lazyload
native: false
# Specify the field to use lazyload (site or post) # Specify the field to use lazyload (site or post)
field: site field: site
placeholder: placeholder:
@@ -1008,6 +1031,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
@@ -1050,6 +1077,7 @@ CDN:
# canvas_fluttering_ribbon: # canvas_fluttering_ribbon:
# canvas_nest: # canvas_nest:
# canvas_ribbon: # canvas_ribbon:
# chartjs:
# click_heart: # click_heart:
# clickShowText: # clickShowText:
# disqusjs: # disqusjs:
@@ -1076,7 +1104,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

@@ -32,6 +32,7 @@ post:
copyright_content: 'All articles on this blog are licensed under <a href="%s">%s</a> unless otherwise stated.' copyright_content: 'All articles on this blog are licensed under <a href="%s">%s</a> unless otherwise stated.'
recommend: Related Articles recommend: Related Articles
edit: Edit edit: Edit
back_to_home: Back to Home
search: search:
title: Search title: Search

View File

@@ -32,6 +32,7 @@ post:
copyright_content: 'All articles on this blog are licensed under <a href="%s">%s</a> unless otherwise stated.' copyright_content: 'All articles on this blog are licensed under <a href="%s">%s</a> unless otherwise stated.'
recommend: Related Articles recommend: Related Articles
edit: Edit edit: Edit
back_to_home: Back to Home
search: search:
title: Search title: Search

View File

@@ -32,6 +32,7 @@ post:
copyright_content: 'このブログのすべての記事は、<a href="%s">%s</a> ライセンスの下で提供されており、特に明記されていない限り、すべての権利を留保します。転載時には出典を明記してください: <a href="%s">%s</a>。' copyright_content: 'このブログのすべての記事は、<a href="%s">%s</a> ライセンスの下で提供されており、特に明記されていない限り、すべての権利を留保します。転載時には出典を明記してください: <a href="%s">%s</a>。'
recommend: 関連記事 recommend: 関連記事
edit: 編集 edit: 編集
back_to_home: ホームに戻る
search: search:
title: 検索 title: 検索

View File

@@ -32,6 +32,7 @@ post:
copyright_content: '이 블로그의 모든 글은 <a href="%s">%s</a> 라이선스를 따르며, 별도로 명시되지 않는 한 모든 권리를 보유합니다. 재배포 시 출처를 명시해 주세요: <a href="%s">%s</a>.' copyright_content: '이 블로그의 모든 글은 <a href="%s">%s</a> 라이선스를 따르며, 별도로 명시되지 않는 한 모든 권리를 보유합니다. 재배포 시 출처를 명시해 주세요: <a href="%s">%s</a>.'
recommend: 관련 글 recommend: 관련 글
edit: 편집 edit: 편집
back_to_home: 홈으로 돌아가기
search: search:
title: 검색 title: 검색

View File

@@ -33,6 +33,7 @@ post:
<a href="%s" target="_blank">%s</a> 许可协议。转载请注明来源 <a href="%s" target="_blank">%s</a>' <a href="%s" target="_blank">%s</a> 许可协议。转载请注明来源 <a href="%s" target="_blank">%s</a>'
recommend: 相关推荐 recommend: 相关推荐
edit: 编辑 edit: 编辑
back_to_home: 返回首页
search: search:
title: 搜索 title: 搜索

View File

@@ -32,6 +32,7 @@ post:
copyright_content: '除特別聲明外,本博客所有文章均採用<a href="%s">%s</a> 授權協議。轉載請註明出處:<a href="%s">%s</a>。' copyright_content: '除特別聲明外,本博客所有文章均採用<a href="%s">%s</a> 授權協議。轉載請註明出處:<a href="%s">%s</a>。'
recommend: 相關文章 recommend: 相關文章
edit: 編輯 edit: 編輯
back_to_home: 返回首頁
search: search:
title: 搜尋 title: 搜尋

View File

@@ -32,6 +32,7 @@ post:
copyright_content: '本部落格所有文章除特別聲明外,均採用<a href="%s" target="_blank">%s</a> 授權協議。轉載請註明來源 <a href="%s" target="_blank">%s</a>' copyright_content: '本部落格所有文章除特別聲明外,均採用<a href="%s" target="_blank">%s</a> 授權協議。轉載請註明來源 <a href="%s" target="_blank">%s</a>'
recommend: 相關推薦 recommend: 相關推薦
edit: 編輯 edit: 編輯
back_to_home: 返回首頁
search: search:
title: 搜尋 title: 搜尋

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)})
@@ -58,3 +55,7 @@ div
script(async data-pjax src= theme.asset.busuanzi || '//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js') script(async data-pjax src= theme.asset.busuanzi || '//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js')
!= partial('includes/third-party/search/index', {}, { cache: true }) != partial('includes/third-party/search/index', {}, { cache: true })
if theme.google_tag_manager && theme.google_tag_manager.tag_id
noscript
iframe(src=`${theme.google_tag_manager.domain ? theme.google_tag_manager.domain : 'https://www.googletagmanager.com'}/ns.html?id=${theme.google_tag_manager.tag_id}` height="0" width="0" style="display:none;visibility:hidden")

View File

@@ -1,18 +1,39 @@
#footer-wrap - const { nav, owner, copyright, custom_text } = theme.footer
if theme.footer.owner.enable
- const currentYear = new Date().getFullYear() if nav
- const sinceYear = theme.footer.owner.since .footer-flex
.copyright for block in nav
if sinceYear && sinceYear != currentYear .footer-flex-items(style=`${ block.width ? 'flex-grow:' + block.width : '' }`)
!= `&copy;${sinceYear} - ${currentYear} By ${config.author}` for blockItem in block.content
else .footer-flex-item
!= `&copy;${currentYear} By ${config.author}` .footer-flex-title= blockItem.title
if theme.footer.copyright .footer-flex-content
.framework-info for subitem in blockItem.item
span= _p('footer.framework') + ' ' if subitem.html
a(href='https://hexo.io')= 'Hexo' div!= subitem.html
span.footer-separator | else if subitem.url
span= _p('footer.theme') + ' ' a(href=url_for(subitem.url), target='_blank' title=subitem.title)= subitem.title
a(href='https://github.com/jerryc127/hexo-theme-butterfly')= 'Butterfly' else if subitem.title
div!= subitem.title
.footer-other
.footer-copyright
if owner.enable
- const currentYear = new Date().getFullYear()
- const sinceYear = owner.since
span.copyright
if sinceYear && sinceYear != currentYear
!= `&copy;${sinceYear} - ${currentYear} By ${config.author}`
else
!= `&copy;${currentYear} By ${config.author}`
if copyright.enable
- const v = copyright.version ? getVersion() : false
span.framework-info
if owner.enable && nav
span.footer-separator |
span= _p('footer.framework') + ' '
a(href='https://hexo.io')= `Hexo${ v ? ' ' + v.hexo : '' }`
span.footer-separator |
span= _p('footer.theme') + ' '
a(href='https://github.com/jerryc127/hexo-theme-butterfly')= `Butterfly${ v ? ' ' + v.theme : '' }`
if theme.footer.custom_text if theme.footer.custom_text
.footer_custom_text!= theme.footer.custom_text .footer_custom_text!= theme.footer.custom_text

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

@@ -32,3 +32,14 @@ if theme.microsoft_clarity
t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i; t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i;
y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y); y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y);
})(window, document, "clarity", "script", "!{theme.microsoft_clarity}"); })(window, document, "clarity", "script", "!{theme.microsoft_clarity}");
if (theme.google_tag_manager && theme.google_tag_manager.tag_id)
script.
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
"!{theme.google_tag_manager.domain ? theme.google_tag_manager.domain : 'https://www.googletagmanager.com'}/gtm.js?id="+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','!{theme.google_tag_manager.tag_id}');
btf.addGlobalFn('pjaxComplete', () => {
dataLayer.push({'event': 'pjaxComplete', 'page_title': document.title, 'page_location': location.href, 'page_path': window.location.pathname})
}, 'google_tag_manager')

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,9 +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}'
} }

View File

@@ -0,0 +1,55 @@
if theme.structured_data
if page.layout === 'post'
-
// https://developers.google.com/search/docs/appearance/structured-data/article
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 || config.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)
-
else if is_home() && (!page.current || page.current === 1)
-
// https://developers.google.com/search/docs/appearance/site-names#website
const baseUrl = config.url;
const currentPath = url_for('/');
const isRootOrSubdomain = currentPath.split('/').filter(Boolean).length === 0;
if (isRootOrSubdomain) {
const jsonLd = {
"@context": "https://schema.org",
"@type": "WebSite",
"name": config.title,
"url": full_url_for('/'),
}
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
- top_img = page.top_img || page.cover || theme.default_top_img when 'post'
else if is_page() - top_img = page.top_img || page.cover || theme.default_top_img
- top_img = page.top_img || theme.default_top_img when 'page'
else if is_tag() - top_img = page.top_img || theme.default_top_img
- top_img = theme.tag_per_img && theme.tag_per_img[page.tag] when 'tag'
- top_img = top_img || returnTopImg(theme.tag_img) - top_img = theme.tag_per_img && theme.tag_per_img[page.tag] || returnTopImg(theme.tag_img)
else if is_category() when 'category'
- top_img = theme.category_per_img && theme.category_per_img[page.category] - top_img = theme.category_per_img && theme.category_per_img[page.category] || returnTopImg(theme.category_img)
- top_img = top_img || returnTopImg(theme.category_img) when 'home'
else if is_home() - top_img = returnTopImg(theme.index_img)
- top_img = returnTopImg(theme.index_img) when 'archive'
else if is_archive() - top_img = returnTopImg(theme.archive_img)
- top_img = returnTopImg(theme.archive_img) default
else - 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,9 +5,13 @@ 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' && theme.nav.display_post_title
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)
span.site-name
i.fa-solid.fa-circle-arrow-left
span= ' ' + _p('post.back_to_home')
#menus #menus
if theme.search.use if theme.search.use
#search-button #search-button

View File

@@ -60,39 +60,7 @@
if block if block
block block
- const commentUse = comments.use && comments.use[0] mixin otherPV()
if page.comments !== false && commentUse && !comments.lazyload
case commentUse
when 'Valine'
if theme.valine.visitor
+pvBlock(url_for(page.path), 'leancloud_visitors', page.title)
span.leancloud-visitors-count
i.fa-solid.fa-spinner.fa-spin
when 'Waline'
if theme.waline.pageview
+pvBlock('', '', '')
span.waline-pageview-count(data-path=url_for(page.path))
i.fa-solid.fa-spinner.fa-spin
when 'Twikoo'
if theme.twikoo.visitor
+pvBlock('', '', '')
span#twikoo_visitors
i.fa-solid.fa-spinner.fa-spin
when 'Artalk'
if theme.artalk.visitor
+pvBlock('', '', '')
span#ArtalkPV
i.fa-solid.fa-spinner.fa-spin
default
if theme.umami_analytics.enable && theme.umami_analytics.UV_PV.page_pv
+pvBlock('', '', '')
span#umamiPV(data-path=url_for(page.path))
i.fa-solid.fa-spinner.fa-spin
else if theme.busuanzi.page_pv
+pvBlock('', 'post-meta-pv-cv', '')
span#busuanzi_value_page_pv
i.fa-solid.fa-spinner.fa-spin
else
if theme.umami_analytics.enable && theme.umami_analytics.UV_PV.page_pv if theme.umami_analytics.enable && theme.umami_analytics.UV_PV.page_pv
+pvBlock('', '', '') +pvBlock('', '', '')
span#umamiPV(data-path=url_for(page.path)) span#umamiPV(data-path=url_for(page.path))
@@ -102,6 +70,29 @@
span#busuanzi_value_page_pv span#busuanzi_value_page_pv
i.fa-solid.fa-spinner.fa-spin i.fa-solid.fa-spinner.fa-spin
- const commentUse = comments.use && comments.use[0]
if page.comments !== false && commentUse && !comments.lazyload
if commentUse === 'Valine' && theme.valine.visitor
+pvBlock(url_for(page.path), 'leancloud_visitors', page.title)
span.leancloud-visitors-count
i.fa-solid.fa-spinner.fa-spin
else if commentUse === 'Waline' && theme.waline.pageview
+pvBlock('', '', '')
span.waline-pageview-count(data-path=url_for(page.path))
i.fa-solid.fa-spinner.fa-spin
else if commentUse === 'Twikoo' && theme.twikoo.visitor
+pvBlock('', '', '')
span#twikoo_visitors
i.fa-solid.fa-spinner.fa-spin
else if commentUse === 'Artalk' && theme.artalk.visitor
+pvBlock('', '', '')
span#ArtalkPV
i.fa-solid.fa-spinner.fa-spin
else
+otherPV()
else
+otherPV()
if comments.count && !comments.lazyload && page.comments !== false && comments.use if comments.count && !comments.lazyload && page.comments !== false && comments.use
- var whichCount = comments.use[0] - var whichCount = comments.use[0]

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
@@ -105,15 +105,9 @@ mixin indexPostUI()
i.fa-solid.fa-spinner.fa-spin i.fa-solid.fa-spinner.fa-spin
//- Display the article introduction on homepage //- Display the article introduction on homepage
case theme.index_post_content.method - const content = postDesc(article)
when false if content
- break .content!=content
when 1
.content!= article.description
when 2
.content!= article.description || truncate(article.content, theme.index_post_content.length)
default
.content!= truncate(article.content, theme.index_post_content.length)
if theme.ad && theme.ad.index if theme.ad && theme.ad.index
if (index + 1) % 3 === 0 if (index + 1) % 3 === 0

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

@@ -6,98 +6,183 @@
//- - tag1 //- - tag1
//- - tag2 //- - tag2
- page.comments = false
- page.toc = false - page.toc = false
#article-container #article-container
if page.comments !== false && theme.comments.use
- commentsJsLoad = true
script.
(() => {
const commentDiv = `!{partial('includes/third-party/comments/index', {}, {cache: true})}`
const runDestroy = (shuoshuoComment) => {
if (!shuoshuoComment) return
for (const [key, fn] of Object.entries(shuoshuoComment)) {
if (key.startsWith('destroy')) fn()
}
}
window.addCommentToShuoshuo = e => {
const btn = e.target.closest('.shuoshuo-comment-btn')
if (!btn) return
const ele = btn.closest('.container').nextElementSibling
const { shuoshuoComment } = window
const isInclude = ele.classList.contains('no-comment')
runDestroy(shuoshuoComment)
if (isInclude) {
ele.classList.remove('no-comment')
ele.innerHTML = commentDiv
const key = `${location.pathname.replace(/\/$/, '')}?key=${ele.getAttribute('data-key')}`
btf.switchComments(ele, key)
shuoshuoComment.loadComment && shuoshuoComment.loadComment(ele, key)
}
}
})()
if page.shuoshuo_url if page.shuoshuo_url
script. script.
(() => { (() => {
const limitConfig = !{ JSON.stringify(page.limit || {}) }
const sortDataByDate = data => data.sort((a, b) => new Date(b.date) - new Date(a.date))
const filterDataByLimit = (data, limit) => {
if (!limit || !limit.type) return data
if (limit.type === 'num') return data.slice(0, limit.value)
if (limit.type === 'date') {
const limitDate = new Date(limit.value)
return data.filter(item => new Date(item.date) >= limitDate)
}
return data
};
const formatToTimeZone = (date) => {
const fullDate = date.length === 10 ? `${date} 00:00:00` : date
const visitorTimeZone = '#{config.timezone}' || Intl.DateTimeFormat().resolvedOptions().timeZone
const options = {
timeZone: visitorTimeZone,
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
}
const [day, month, year, hour, minute, second] = new Intl.DateTimeFormat('en-GB', options)
.format(new Date(fullDate))
.match(/\d+/g)
return `${year}-${month}-${day} ${hour}:${minute}:${second}`
}
const loadShuoshuo = async () => { const loadShuoshuo = async () => {
try { try {
const fetchContent = await fetch('!{url_for(page.shuoshuo_url)}') const response = await fetch('!{url_for(page.shuoshuo_url)}')
const shuoshuo = await fetchContent.json() let data = await response.json()
data = filterDataByLimit(sortDataByDate(data), limitConfig)
let start = 0
const container = document.getElementById('article-container') const container = document.getElementById('article-container')
let start = 0
const addData = data => { const renderData = (dataSlice) => {
const cLength = data.length const content = dataSlice.map(item => {
const end = start + 10 > cLength ? cLength : start + 10 const formattedDate = formatToTimeZone(item.date)
let result = '' const tags = item.tags && item.tags.map(tag => `<span class="shuoshuo-tag">${tag}</span>`).join('') || ''
data.slice(start, end).forEach((item) => { const commentButton = item.key && !{commentsJsLoad}
result += ` ? `<div class="shuoshuo-comment-btn" onclick="addCommentToShuoshuo(event)">
<div class="shuoshuo-item"> <i class="fa-solid fa-comments"></i>
<div class="shuoshuo-item-header"> </div>`
<div class="shuoshuo-avatar"> : ''
<img class="no-lightbox" src="${item.avatar || '!{url_for(theme.avatar.img)}'}"> const commentContainer = item.key
</div> ? `<div class="shuoshuo-comment no-comment" data-key="${item.key}"></div>`
<div class="shuoshuo-info"> : ''
<div class="shuoshuo-author">${item.author || '!{config.author}'}</div>
<div class="shuoshuo-date">${btf.diffDate(item.date, true)}</div> return `
</div> <div class="shuoshuo-item">
<div class="container">
<div class="shuoshuo-item-header">
<div class="shuoshuo-avatar">
<img class="no-lightbox" src="${item.avatar || '!{url_for(theme.avatar.img)}'}">
</div> </div>
<div class="shuoshuo-content"> <div class="shuoshuo-info">
${item.content} <div class="shuoshuo-author">${item.author || '!{config.author}'}</div>
</div> <time class="shuoshuo-date" title="${formattedDate}">
<div class="shuoshuo-footer"> ${btf.diffDate(formattedDate, true)}
<div class="shuoshuo-tags"> </time>
${item.tags.map(tag => `<span class="shuoshuo-tag">${tag}</span>`).join('')}
</div>
</div> </div>
</div> </div>
` <div class="shuoshuo-content">${item.content}</div>
}) <div class="shuoshuo-footer ${tags ? 'flex-between' : 'flex-end'}">
${tags ? `<div class="shuoshuo-tags">${tags}</div>` : ''}
${commentButton}
</div>
</div>
${commentContainer}
</div>`
}).join('')
start = end container.insertAdjacentHTML('beforeend', content)
container.insertAdjacentHTML('beforeend', result)
if (start >= cLength) {
observer.disconnect()
} else {
setTimeout(() => observer.observe(container.lastElementChild), 100)
}
window.lazyLoadInstance.update() window.lazyLoadInstance.update()
btf.loadLightbox(document.querySelectorAll('#article-container img:not(.no-lightbox)')) btf.loadLightbox(document.querySelectorAll('#article-container img:not(.no-lightbox)'))
} }
addData(shuoshuo) const handleIntersection = (entries) => {
if (!entries[0].isIntersecting) return
observer.unobserve(entries[0].target)
const observer = new IntersectionObserver((entries) => { const slice = data.slice(start, start + 10)
if (entries[0].isIntersecting) { renderData(slice)
observer.unobserve(entries[0].target) start += 10
addData(shuoshuo)
if (start < data.length) {
setTimeout(() => observer.observe(container.lastElementChild), 100)
} else {
observer.disconnect()
} }
}, { };
const observer = new IntersectionObserver(handleIntersection, {
root: null, root: null,
rootMargin: '0px', rootMargin: '0px',
threshold: 1.0 threshold: 1.0
}) })
if (container.lastElementChild) { renderData(data.slice(start, 10))
observer.observe(container.lastElementChild) start += 10
}
} catch (e) { if (container.lastElementChild) observer.observe(container.lastElementChild)
console.error(e) } catch (error) {
console.error(error)
} }
} };
window.pjax ? loadShuoshuo() : window.addEventListener('load', loadShuoshuo) window.pjax ? loadShuoshuo() : window.addEventListener('load', loadShuoshuo)
})() })()
else else
if site.data.shuoshuo if site.data.shuoshuo
each i in site.data.shuoshuo each i in shuoshuoFN(site.data.shuoshuo, page)
.shuoshuo-item .shuoshuo-item
.shuoshuo-item-header .container
.shuoshuo-avatar .shuoshuo-item-header
img.no-lightbox(src=i.avatar || url_for(theme.avatar.img)) .shuoshuo-avatar
.shuoshuo-info img.no-lightbox(src=i.avatar || url_for(theme.avatar.img))
.shuoshuo-author=i.author || config.author .shuoshuo-info
.shuoshuo-date=relative_date(i.date) .shuoshuo-author=i.author || config.author
.shuoshuo-content time.shuoshuo-date(title=i.date)=i.date
!=markdown(i.content) .shuoshuo-content
.shuoshuo-footer !=markdown(i.content)
.shuoshuo-tags .shuoshuo-footer(class=i.tags && i.tags.length ? 'flex-between' : 'flex-end')
each tag in i.tags if i.tags
span.shuoshuo-tag=tag .shuoshuo-tags
each tag in i.tags
span.shuoshuo-tag=tag
if i.key && commentsJsLoad
.shuoshuo-comment-btn(onclick='addCommentToShuoshuo(event)')
i.fa-solid.fa-comments
if i.key && commentsJsLoad
.shuoshuo-comment.no-comment(data-key=i.key)

View File

@@ -1,2 +1,2 @@
.tag-cloud-list.is-center .tag-cloud-list.text-center
!=cloudTags({source: site.tags, orderby: page.orderby || 'random', order: page.order || 1, minfontsize: 1.2, maxfontsize: 1.5, limit: 0, unit: 'em'}) !=cloudTags({source: site.tags, orderby: page.orderby || 'random', order: page.order || 1, minfontsize: 1.2, maxfontsize: 1.5, limit: 0, unit: 'em'})

View File

@@ -1,39 +1,38 @@
- if page.total !== 1
var options = { -
prev_text: '<i class="fas fa-chevron-left fa-fw"></i>', var options = {
next_text: '<i class="fas fa-chevron-right fa-fw"></i>', prev_text: '<i class="fas fa-chevron-left fa-fw"></i>',
mid_size: 1, next_text: '<i class="fas fa-chevron-right fa-fw"></i>',
escape: false mid_size: 1,
} escape: false
}
if is_post() if globalPageType === 'post'
- let prev = theme.post_pagination === 1 ? page.prev : page.next - let paginationOrder = theme.post_pagination === 2 ? { prev: page.prev, next: page.next } : { prev: page.next, next: page.prev }
- let next = theme.post_pagination === 1 ? page.next : page.prev
nav#pagination.pagination-post
if(prev)
- var hasPageNext = next ? 'pull-left' : 'pull-full'
a.prev-post(class=hasPageNext href=url_for(prev.path) title=prev.title)
if prev.cover_type === 'img'
img.cover(src=url_for(prev.cover) onerror=`onerror=null;src='${url_for(theme.error_img.post_page)}'` alt='cover of previous post')
else
.cover(style=`background: ${prev.cover || 'var(--default-bg-color)'}`)
.pagination-info
.label=_p('pagination.prev')
.prev_info=prev.title
if(next) nav#pagination.pagination-post
- var hasPagePrev = prev ? 'pull-right' : 'pull-full' each direction, key in paginationOrder
a.next-post(class=hasPagePrev href=url_for(next.path) title=next.title) if direction
if next.cover_type === 'img' - const getPostDesc = direction.postDesc || postDesc(direction)
img.cover(src=url_for(next.cover) onerror=`onerror=null;src='${url_for(theme.error_img.post_page)}'` alt='cover of next post') - let className = key === 'prev' ? (paginationOrder.next ? '' : 'full-width') : (paginationOrder.prev ? '' : 'full-width')
else - className = getPostDesc ? className : className + ' no-desc'
.cover(style=`background: ${next.cover || 'var(--default-bg-color)'}`)
.pagination-info a.pagination-related(class=className href=url_for(direction.path) title=direction.title)
.label=_p('pagination.next') if direction.cover_type === 'img'
.next_info=next.title img.cover(src=url_for(direction.cover) onerror=`onerror=null;src='${url_for(theme.error_img.post_page)}'` alt=`cover of ${key === 'prev' ? 'previous' : 'next'} post`)
else else
nav#pagination .cover(style=`background: ${direction.cover || 'var(--default-bg-color)'}`)
.pagination
if is_home() .info(class=key === 'prev' ? '' : 'text-right')
- options.format = 'page/%d/#content-inner' .info-1
!=paginator(options) .info-item-1=_p(`pagination.${key}`)
.info-item-2!=direction.title
if getPostDesc
.info-2
.info-item-1!=getPostDesc
else
nav#pagination
.pagination
if globalPageType === 'home'
- options.format = 'page/%d/#content-inner'
!=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

@@ -1,9 +1,10 @@
- const { readmode, translate, darkmode, aside, chat } = theme - const { readmode, translate, darkmode, aside, chat } = theme
mixin rightsideItem(array) 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,37 +24,29 @@ 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
a#to_comment(href="#post-comment" title=_p("rightside.scroll_to_comment")) a#to_comment(href="#post-comment" title=_p("rightside.scroll_to_comment"))
i.fas.fa-comments i.fas.fa-comments
- const { enable, hide, show } = theme.rightside_item_order
- const hideArray = enable && hide ? hide.split(',') : ['readmode','translate','darkmode','hideAside']
- const showArray = enable && show ? show.split(',') : ['toc','chat','comment']
- const needCogBtn = (enable && hide) || (!enable && ((globalPageType === 'post' && (readmode || translate.enable || (darkmode.enable && darkmode.button))) || (translate.enable || (darkmode.enable && darkmode.button))))
#rightside #rightside
- const { enable, hide, show } = theme.rightside_item_order
- const hideArray = enable ? hide && hide.split(',') : ['readmode','translate','darkmode','hideAside']
- const showArray = enable ? show && show.split(',') : ['toc','chat','comment']
#rightside-config-hide #rightside-config-hide
if hideArray if hideArray.length
+rightsideItem(hideArray) +rightsideItem(hideArray)
#rightside-config-show
if enable
if hide
button#rightside-config(type="button" title=_p("rightside.setting"))
i.fas.fa-cog.fa-spin
else
if is_post()
if (readmode || translate.enable || (darkmode.enable && darkmode.button))
button#rightside-config(type="button" title=_p("rightside.setting"))
i.fas.fa-cog.fa-spin
else if translate.enable || (darkmode.enable && darkmode.button)
button#rightside-config(type="button" title=_p("rightside.setting"))
i.fas.fa-cog.fa-spin
if showArray #rightside-config-show
if needCogBtn
button#rightside-config(type="button" title=_p("rightside.setting"))
i.fas.fa-cog(class=theme.rightside_config_animation ? 'fa-spin' : '')
if showArray.length
+rightsideItem(showArray) +rightsideItem(showArray)
button#go-up(type="button" title=_p("rightside.back_to_top")) button#go-up(type="button" title=_p("rightside.back_to_top"))

View File

@@ -2,17 +2,17 @@ if theme.menu
#sidebar #sidebar
#menu-mask #menu-mask
#sidebar-menus #sidebar-menus
.avatar-img.is-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.is-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
!=partial('includes/header/menu_item', {}, {cache: true}) != partial('includes/header/menu_item', {}, { cache: true })

View File

@@ -1,17 +1,46 @@
script. script.
(() => { (() = {
const abcjsInit = () => { const abcjsInit = () => {
const abcjsFn = () => { const abcjsFn = () => {
document.querySelectorAll(".abc-music-sheet").forEach(ele => { setTimeout(() => {
if (ele.children.length > 0) return const sheets = document.querySelectorAll(".abc-music-sheet")
ABCJS.renderAbc(ele, ele.innerHTML, {responsive: 'resize'}) for (let i = 0; i < sheets.length; i++) {
}) const ele = sheets[i]
if (ele.children.length > 0) continue
// Parse parameters from data-params attribute
let params = {}
const dp = ele.getAttribute("data-params")
if (dp) {
try {
params = JSON.parse(dp)
} catch (e) {
console.error("Failed to parse data-params:", e)
}
}
// Merge parsed parameters with the responsive option
// Ensures params content appears before responsive
const options = { ...params, responsive: "resize" }
// Render the music score using ABCJS.renderAbc
ABCJS.renderAbc(ele, ele.innerHTML, options)
}
}, 100)
} }
typeof ABCJS === 'object' ? abcjsFn() if (typeof ABCJS === "object") {
: btf.getScript('!{url_for(theme.asset.abcjs_basic_js)}').then(abcjsFn) abcjsFn()
} else {
btf.getScript("!{url_for(theme.asset.abcjs_basic_js)}").then(abcjsFn)
}
} }
window.pjax ? abcjsInit() : window.addEventListener('load', abcjsInit) if (window.pjax) {
btf.addGlobalFn('encrypt', abcjsInit, 'abcjs') abcjsInit()
} else {
window.addEventListener("load", abcjsInit)
}
btf.addGlobalFn("encrypt", abcjsInit, "abcjs")
})() })()

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

View File

@@ -1,37 +1,32 @@
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])
const isChatBtn = !{theme.chat.rightside_button} btf.getScript('https://client.crisp.chat/l.js').then(() => {
const isChatHideShow = !{theme.chat.button_hide_show} const isChatBtn = !{theme.chat.rightside_button}
const isChatHideShow = !{theme.chat.button_hide_show}
if (isChatBtn) { if (isChatBtn) {
const open = () => { const open = () => {
$crisp.push(["do", "chat:show"]) $crisp.push(["do", "chat:show"])
$crisp.push(["do", "chat:open"]) $crisp.push(["do", "chat:open"])
}
const close = () => $crisp.push(["do", "chat:hide"])
close()
$crisp.push(["on", "chat:closed", close])
window.chatBtnFn = () => $crisp.is("chat:visible") ? close() : open()
document.getElementById('chat-btn').style.display = 'block'
} else if (isChatHideShow) {
window.chatBtn = {
hide: () => $crisp.push(["do", "chat:hide"]),
show: () => $crisp.push(["do", "chat:show"])
}
} }
})
const close = () => $crisp.push(["do", "chat:hide"])
close()
$crisp.push(["on", "chat:closed", close])
window.chatBtnFn = () => $crisp.is("chat:visible") ? close() : open()
} else if (isChatHideShow) {
window.chatBtn = {
hide: () => $crisp.push(["do", "chat:hide"]),
show: () => $crisp.push(["do", "chat:show"])
}
}
})() })()

View File

@@ -1,40 +0,0 @@
//- https://guide.daocloud.io/daovoice/javascript-api-5869833.html
script.
(() => {
(function(i,s,o,g,r,a,m){i["DaoVoiceObject"]=r;i[r]=i[r]||function(){(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;a.charset="utf-8";m.parentNode.insertBefore(a,m)})(window,document,"script",('https:' == document.location.protocol ? 'https:' : 'http:') + "//widget.daovoice.io/widget/!{theme.daovoice.app_id}.js","daovoice")
const isChatBtn = !{theme.chat.rightside_button}
const isChatHideShow = !{theme.chat.button_hide_show}
daovoice('init', {
app_id: '!{theme.daovoice.app_id}',},{
launcher: {
disableLauncherIcon: isChatBtn
},
});
daovoice('update');
if (isChatBtn) {
window.chatBtnFn = () => {
const isShow = document.getElementById('daodream-messenger').classList.contains('daodream-messenger-active')
isShow ? daovoice('hide') : daovoice('show')
}
} else if (isChatHideShow) {
window.chatBtn = {
hide: () => {
daovoice('update', {},{
launcher: {
disableLauncherIcon: true
}
})
},
show: () => {
daovoice('update', {}, {
launcher: {
disableLauncherIcon: false
}
})
}
}
}
})()

View File

@@ -3,7 +3,5 @@ case theme.chat.use
include ./chatra.pug include ./chatra.pug
when 'tidio' when 'tidio'
include ./tidio.pug include ./tidio.pug
when 'daovoice'
include ./daovoice.pug
when 'crisp' when 'crisp'
include ./crisp.pug include ./crisp.pug

View File

@@ -1,41 +1,45 @@
script(src=`//code.tidio.co/${theme.tidio.public_key}.js` async)
script. script.
(() => { (() => {
const isChatBtn = !{theme.chat.rightside_button} btf.getScript('//code.tidio.co/!{theme.tidio.public_key}.js').then(() => {
const isChatHideShow = !{theme.chat.button_hide_show} const isChatBtn = !{theme.chat.rightside_button}
const isChatHideShow = !{theme.chat.button_hide_show}
if (isChatBtn) { if (isChatBtn) {
let isShow = false let isShow = false
const close = () => { const close = () => {
window.tidioChatApi.hide() window.tidioChatApi.hide()
isShow = false isShow = false
} }
const open = () => { const open = () => {
window.tidioChatApi.open() window.tidioChatApi.open()
window.tidioChatApi.show() window.tidioChatApi.show()
isShow = true isShow = true
} }
const onTidioChatApiReady = () => { const onTidioChatApiReady = () => {
window.tidioChatApi.hide() window.tidioChatApi.hide()
window.tidioChatApi.on("close", close) window.tidioChatApi.on("close", close)
} }
if (window.tidioChatApi) { if (window.tidioChatApi) {
window.tidioChatApi.on("ready", onTidioChatApiReady) window.tidioChatApi.on("ready", onTidioChatApiReady)
} else { } else {
document.addEventListener("tidioChat-ready", onTidioChatApiReady) document.addEventListener("tidioChat-ready", onTidioChatApiReady)
} }
window.chatBtnFn = () => { window.chatBtnFn = () => {
if (!window.tidioChatApi) return if (!window.tidioChatApi) return
isShow ? close() : open() isShow ? close() : open()
}
document.getElementById('chat-btn').style.display = 'block'
} else if (isChatHideShow) {
window.chatBtn = {
hide: () => window.tidioChatApi && window.tidioChatApi.hide(),
show: () => window.tidioChatApi && window.tidioChatApi.show()
}
} }
} else if (isChatHideShow) { })
window.chatBtn = {
hide: () => window.tidioChatApi && window.tidioChatApi.hide(),
show: () => window.tidioChatApi && window.tidioChatApi.show()
}
}
})() })()

View File

@@ -4,14 +4,27 @@
script. script.
(() => { (() => {
let artalkItem = null let artalkItem = null
const initArtalk = () => { const option = !{JSON.stringify(option)}
artalkItem = Artalk.init(Object.assign({ const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'
el: '#artalk-wrap',
const destroyArtalk = () => {
if (artalkItem) {
artalkItem.destroy()
artalkItem = null
}
}
const artalkChangeMode = theme => artalkItem && artalkItem.setDarkMode(theme === 'dark')
const initArtalk = (el = document, pageKey = location.pathname) => {
artalkItem = Artalk.init({
el: el.querySelector('#artalk-wrap'),
server: '!{server}', server: '!{server}',
site: '!{site}', site: '!{site}',
pageKey: location.pathname,
darkMode: document.documentElement.getAttribute('data-theme') === 'dark', darkMode: document.documentElement.getAttribute('data-theme') === 'dark',
},!{JSON.stringify(option)})) ...option,
pageKey: isShuoshuo ? pageKey : (option && option.pageKey) || pageKey
})
if (GLOBAL_CONFIG.lightbox === 'null') return if (GLOBAL_CONFIG.lightbox === 'null') return
artalkItem.on('list-loaded', () => { artalkItem.on('list-loaded', () => {
@@ -21,31 +34,36 @@ script.
}) })
}) })
const destroyArtalk = () => { if (isShuoshuo) {
artalkItem.destroy() window.shuoshuoComment.destroyArtalk = () => {
destroyArtalk()
if (el.children.length) {
el.innerHTML = ''
el.classList.add('no-comment')
}
}
} }
btf.addGlobalFn('pjaxSendOnce', destroyArtalk, 'destroyArtalk') btf.addGlobalFn('pjaxSendOnce', destroyArtalk, 'destroyArtalk')
btf.addGlobalFn('themeChange', artalkChangeMode, 'artalk')
} }
const loadArtalk = async () => { const loadArtalk = async (el, pageKey) => {
if (typeof Artalk === 'object') initArtalk() if (typeof Artalk === 'object') initArtalk(el, pageKey)
else { else {
await btf.getCSS('!{theme.asset.artalk_css}') await btf.getCSS('!{theme.asset.artalk_css}')
await btf.getScript('!{theme.asset.artalk_js}') await btf.getScript('!{theme.asset.artalk_js}')
initArtalk() initArtalk(el, pageKey)
} }
} }
const artalkChangeMode = theme => { if (isShuoshuo) {
const artalkWrap = document.getElementById('artalk-wrap') '!{use[0]}' === 'Artalk'
if (!(artalkWrap && artalkWrap.children.length)) return ? window.shuoshuoComment = { loadComment: loadArtalk }
const isDark = theme === 'dark' : window.loadOtherComment = loadArtalk
artalkItem.setDarkMode(isDark) return
} }
btf.addGlobalFn('themeChange', artalkChangeMode, 'artalk')
if ('!{use[0]}' === 'Artalk' || !!{lazyload}) { if ('!{use[0]}' === 'Artalk' || !!{lazyload}) {
if (!{lazyload}) btf.loadComment(document.getElementById('artalk-wrap'), loadArtalk) if (!{lazyload}) btf.loadComment(document.getElementById('artalk-wrap'), loadArtalk)
else setTimeout(loadArtalk, 100) else setTimeout(loadArtalk, 100)

View File

@@ -4,29 +4,43 @@
script. script.
(() => { (() => {
const disqus_config = function () { const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'
this.page.url = '!{ page.permalink }'
this.page.identifier = '!{ url_for(page.path) }'
this.page.title = '!{ disqusPageTitle }'
}
const disqusReset = () => { const disqusReset = conf => {
window.DISQUS && window.DISQUS.reset({ window.DISQUS && window.DISQUS.reset({
reload: true, reload: true,
config: disqus_config config: conf
}) })
} }
btf.addGlobalFn('themeChange', disqusReset, 'disqus') const loadDisqus = (el, path) => {
if (isShuoshuo) {
window.shuoshuoComment.destroyDisqus = () => {
if (el.children.length) {
el.innerHTML = ''
el.classList.add('no-comment')
}
}
}
const loadDisqus = () =>{ window.disqus_identifier = isShuoshuo ? path : '!{ url_for(page.path) }'
if (window.DISQUS) disqusReset() window.disqus_url = isShuoshuo ? location.origin + path : '!{ page.permalink }'
const disqus_config = function () {
this.page.url = disqus_url
this.page.identifier = disqus_identifier
this.page.title = '!{ disqusPageTitle }'
}
if (window.DISQUS) disqusReset(disqus_config)
else { else {
const script = document.createElement('script') const script = document.createElement('script')
script.src = 'https://!{shortname}.disqus.com/embed.js' script.src = 'https://!{shortname}.disqus.com/embed.js'
script.setAttribute('data-timestamp', +new Date()) script.setAttribute('data-timestamp', +new Date())
document.head.appendChild(script) document.head.appendChild(script)
} }
btf.addGlobalFn('themeChange', () => disqusReset(disqus_config), 'disqus')
} }
const getCount = async() => { const getCount = async() => {
@@ -47,11 +61,18 @@ script.
} }
} }
if (isShuoshuo) {
'!{use[0]}' === 'Disqus'
? window.shuoshuoComment = { loadComment: loadDisqus }
: window.loadOtherComment = loadDisqus
return
}
if ('!{use[0]}' === 'Disqus' || !!{lazyload}) { if ('!{use[0]}' === 'Disqus' || !!{lazyload}) {
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

@@ -1,36 +1,52 @@
- let disqusjsPageTitle = page.title.replace(/'/ig,"\\'") - let disqusjsPageTitle = page.title && page.title.replace(/'/ig,"\\'")
- const { shortname:dqShortname, apikey:dqApikey, option:dqOption } = theme.disqusjs - const { shortname:dqShortname, apikey:dqApikey, option:dqOption } = theme.disqusjs
script. script.
(() => { (() => {
const initDisqusjs = () => { const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'== 'shuoshuo'
const dqOption = !{JSON.stringify(dqOption)}
const destroyDisqusjs = () => {
disqusjs.destroy()
window.disqusjs = null window.disqusjs = null
disqusjs = new DisqusJS(Object.assign({ }
const themeChange = (el, path) => {
destroyDisqusjs()
initDisqusjs(el, path)
}
const initDisqusjs = (el = document, path) => {
if (isShuoshuo) {
window.shuoshuoComment.destroyDisqusjs = () => {
destroyDisqusjs()
if (el.children.length) {
el.innerHTML = ''
el.classList.add('no-comment')
}
}
}
disqusjs = new DisqusJS({
shortname: '!{dqShortname}', shortname: '!{dqShortname}',
identifier: '!{ url_for(page.path) }',
url: '!{ page.permalink }',
title: '!{ disqusjsPageTitle }', title: '!{ disqusjsPageTitle }',
apikey: '!{dqApikey}', apikey: '!{dqApikey}',
},!{JSON.stringify(dqOption)})) ...dqOption,
identifier: isShuoshuo ? path : (dqOption && dqOption.identifier) || '!{ url_for(page.path) }',
url: isShuoshuo ? location.origin + path : (dqOption && dqOption.url) || '!{ page.permalink }'
})
disqusjs.render(document.getElementById('disqusjs-wrap')) disqusjs.render(el.querySelector('#disqusjs-wrap'))
btf.addGlobalFn('themeChange', () => themeChange(el, path), 'disqusjs')
} }
const themeChange = () => { const loadDisqusjs = async(el, path) => {
const ele = document.getElementById('disqus_thread') if (window.disqusJsLoad) initDisqusjs(el, path)
if(!ele) return
disqusjs.destroy()
initDisqusjs()
}
btf.addGlobalFn('themeChange', themeChange, 'disqusjs')
const loadDisqusjs = async() => {
if (window.disqusJsLoad) initDisqusjs()
else { else {
await btf.getCSS('!{url_for(theme.asset.disqusjs_css)}') await btf.getCSS('!{url_for(theme.asset.disqusjs_css)}')
await btf.getScript('!{url_for(theme.asset.disqusjs)}') await btf.getScript('!{url_for(theme.asset.disqusjs)}')
initDisqusjs() initDisqusjs(el, path)
window.disqusJsLoad = true window.disqusJsLoad = true
} }
} }
@@ -52,11 +68,18 @@ script.
} }
} }
if (isShuoshuo) {
'!{theme.comments.use[0]}' === 'Disqusjs'
? window.shuoshuoComment = { loadComment: loadDisqusjs }
: window.loadOtherComment = loadDisqusjs
return
}
if ('!{theme.comments.use[0]}' === 'Disqusjs' || !!{theme.comments.lazyload}) { if ('!{theme.comments.use[0]}' === 'Disqusjs' || !!{theme.comments.lazyload}) {
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,17 +3,28 @@
script. script.
(()=>{ (()=>{
const loadFBComment = () => { const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'== 'shuoshuo'
const loadFBComment = (el = document, path) => {
if (isShuoshuo) {
window.shuoshuoComment.destroyFB = () => {
if (el.children.length) {
el.innerHTML = ''
el.classList.add('no-comment')
}
}
}
document.getElementById('fb-root') ? '' : document.body.insertAdjacentHTML('afterend', '<div id="fb-root"></div>') document.getElementById('fb-root') ? '' : document.body.insertAdjacentHTML('afterend', '<div id="fb-root"></div>')
const themeNow = document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'light' const themeNow = document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'light'
const $fbComment = document.getElementsByClassName('fb-comments')[0] const $fbComment = el.getElementsByClassName('fb-comments')[0]
$fbComment.setAttribute('data-colorscheme',themeNow) $fbComment.setAttribute('data-colorscheme',themeNow)
$fbComment.setAttribute('data-href', '!{urlNoIndex(page.permalink)}') $fbComment.setAttribute('data-href', isShuoshuo ? '!{urlNoIndex(page.permalink)}' + '#' + path : '!{urlNoIndex(page.permalink)}')
if (typeof FB === 'object') { if (typeof FB === 'object') {
FB.XFBML.parse(document.getElementsByClassName('post-meta-commentcount')[0]) FB.XFBML.parse(document.getElementsByClassName('post-meta-commentcount')[0])
FB.XFBML.parse(document.getElementById('post-comment')) FB.XFBML.parse(el.querySelector('#post-comment'))
} }
else { else {
let ele = document.createElement('script') let ele = document.createElement('script')
@@ -36,6 +47,13 @@ script.
btf.addGlobalFn('themeChange', fbModeChange, 'facebook_comments') btf.addGlobalFn('themeChange', fbModeChange, 'facebook_comments')
if (isShuoshuo) {
'!{theme.comments.use[0]}' === 'Facebook Comments'
? window.shuoshuoComment = { loadComment: loadFBComment }
: window.loadOtherComment = loadFBComment
return
}
if ('!{theme.comments.use[0]}' === 'Facebook Comments' || !!{theme.comments.lazyload}) { if ('!{theme.comments.use[0]}' === 'Facebook Comments' || !!{theme.comments.lazyload}) {
if (!{theme.comments.lazyload}) btf.loadComment(document.querySelector('#post-comment .fb-comments'), loadFBComment) if (!{theme.comments.lazyload}) btf.loadComment(document.querySelector('#post-comment .fb-comments'), loadFBComment)
else loadFBComment() else loadFBComment()

View File

@@ -4,27 +4,50 @@
- const giscusOriginUrl = new URL(giscusUrl).origin - const giscusOriginUrl = new URL(giscusUrl).origin
script. script.
(()=>{ (() => {
const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'
const option = !{JSON.stringify(option)}
const getGiscusTheme = theme => theme === 'dark' ? '!{dark_theme}' : '!{light_theme}' const getGiscusTheme = theme => theme === 'dark' ? '!{dark_theme}' : '!{light_theme}'
const loadGiscus = () => { const createScriptElement = config => {
const config = Object.assign({ const ele = document.createElement('script')
Object.entries(config).forEach(([key, value]) => {
ele.setAttribute(key, value)
})
return ele
}
const loadGiscus = (el = document, key) => {
const mappingConfig = isShuoshuo
? { 'data-mapping': 'specific', 'data-term': key }
: { 'data-mapping': (option && option['data-mapping']) || 'pathname' }
const giscusConfig = {
src: '!{giscusUrl}', src: '!{giscusUrl}',
'data-repo': '!{repo}', 'data-repo': '!{repo}',
'data-repo-id': '!{repo_id}', 'data-repo-id': '!{repo_id}',
'data-category-id': '!{category_id}', 'data-category-id': '!{category_id}',
'data-mapping': 'pathname',
'data-theme': getGiscusTheme(document.documentElement.getAttribute('data-theme')), 'data-theme': getGiscusTheme(document.documentElement.getAttribute('data-theme')),
'data-reactions-enabled': '1', 'data-reactions-enabled': '1',
crossorigin: 'anonymous', crossorigin: 'anonymous',
async: true async: true,
},!{JSON.stringify(option)}) ...option,
...mappingConfig
const ele = document.createElement('script') }
for (let key in config) {
ele.setAttribute(key, config[key]) const scriptElement = createScriptElement(giscusConfig)
el.querySelector('#giscus-wrap').appendChild(scriptElement)
if (isShuoshuo) {
window.shuoshuoComment.destroyGiscus = () => {
if (el.children.length) {
el.innerHTML = ''
el.classList.add('no-comment')
}
}
} }
document.getElementById('giscus-wrap').appendChild(ele)
} }
const changeGiscusTheme = theme => { const changeGiscusTheme = theme => {
@@ -43,10 +66,17 @@ script.
btf.addGlobalFn('themeChange', changeGiscusTheme, 'giscus') btf.addGlobalFn('themeChange', changeGiscusTheme, 'giscus')
if (isShuoshuo) {
'!{use[0]}' === 'Giscus'
? window.shuoshuoComment = { loadComment: loadGiscus }
: window.loadOtherComment = loadGiscus
return
}
if ('!{use[0]}' === 'Giscus' || !!{lazyload}) { if ('!{use[0]}' === 'Giscus' || !!{lazyload}) {
if (!{lazyload}) btf.loadComment(document.getElementById('giscus-wrap'), loadGiscus) if (!{lazyload}) btf.loadComment(document.getElementById('giscus-wrap'), loadGiscus)
else loadGiscus() else loadGiscus()
} else { } else {
window.loadOtherComment= loadGiscus window.loadOtherComment = loadGiscus
} }
})() })()

View File

@@ -2,28 +2,8 @@
script. script.
(() => { (() => {
const initGitalk = () => { const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'
const gitalk = new Gitalk(Object.assign({ const option = !{JSON.stringify(option)}
clientID: '!{client_id}',
clientSecret: '!{client_secret}',
repo: '!{repo}',
owner: '!{owner}',
admin: ['!{admin}'],
id: '!{md5(page.path)}',
updateCountCallback: commentCount
},!{JSON.stringify(option)}))
gitalk.render('gitalk-container')
}
const loadGitalk = async() => {
if (typeof Gitalk === 'function') initGitalk()
else {
await btf.getCSS('!{url_for(theme.asset.gitalk_css)}')
await btf.getScript('!{url_for(theme.asset.gitalk)}')
initGitalk()
}
}
const commentCount = n => { const commentCount = n => {
const isCommentCount = document.querySelector('#post-meta .gitalk-comment-count') const isCommentCount = document.querySelector('#post-meta .gitalk-comment-count')
@@ -32,6 +12,46 @@ script.
} }
} }
const initGitalk = (el, path) => {
if (isShuoshuo) {
window.shuoshuoComment.destroyGitalk = () => {
if (el.children.length) {
el.innerHTML = ''
el.classList.add('no-comment')
}
}
}
const gitalk = new Gitalk({
clientID: '!{client_id}',
clientSecret: '!{client_secret}',
repo: '!{repo}',
owner: '!{owner}',
admin: ['!{admin}'],
updateCountCallback: commentCount,
...option,
id: isShuoshuo ? path : (option && option.id) || '!{md5(page.path)}'
})
gitalk.render('gitalk-container')
}
const loadGitalk = async(el, path) => {
if (typeof Gitalk === 'function') initGitalk(el, path)
else {
await btf.getCSS('!{url_for(theme.asset.gitalk_css)}')
await btf.getScript('!{url_for(theme.asset.gitalk)}')
initGitalk(el, path)
}
}
if (isShuoshuo) {
'!{theme.comments.use[0]}' === 'Gitalk'
? window.shuoshuoComment = { loadComment: loadGitalk }
: window.loadOtherComment = loadGitalk
return
}
if ('!{theme.comments.use[0]}' === 'Gitalk' || !!{theme.comments.lazyload}) { if ('!{theme.comments.use[0]}' === 'Gitalk' || !!{theme.comments.lazyload}) {
if (!{theme.comments.lazyload}) btf.loadComment(document.getElementById('gitalk-container'), loadGitalk) if (!{theme.comments.lazyload}) btf.loadComment(document.getElementById('gitalk-container'), loadGitalk)
else loadGitalk() else loadGitalk()

View File

@@ -1,8 +1,23 @@
- const { use, lazyload } = theme.comments - const { use, lazyload } = theme.comments
script. script.
(()=>{ (() => {
const loadLivere = () => { const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'
const loadLivere = (el, path) => {
window.livereOptions = {
refer: path || location.pathname
}
if (isShuoshuo) {
window.shuoshuoComment.destroyLivere = () => {
if (el.children.length) {
el.innerHTML = ''
el.classList.add('no-comment')
}
}
}
if (typeof LivereTower === 'object') window.LivereTower.init() if (typeof LivereTower === 'object') window.LivereTower.init()
else { else {
(function(d, s) { (function(d, s) {
@@ -16,6 +31,13 @@ script.
} }
} }
if (isShuoshuo) {
'!{use[0]}' === 'Livere'
? window.shuoshuoComment = { loadComment: loadLivere }
: window.loadOtherComment = loadLivere
return
}
if ('!{use[0]}' === 'Livere' || !!{lazyload}) { if ('!{use[0]}' === 'Livere' || !!{lazyload}) {
if (!{lazyload}) btf.loadComment(document.getElementById('lv-container'), loadLivere) if (!{lazyload}) btf.loadComment(document.getElementById('lv-container'), loadLivere)
else loadLivere() else loadLivere()

View File

@@ -1,68 +1,78 @@
- const { host, siteId, option } = theme.remark42 - const { host, siteId, option } = theme.remark42
script. script.
var remark_config = Object.assign({ (() => {
host: '!{host}', const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'
site_id: '!{siteId}', const option = !{JSON.stringify(option)}
components: ['embed'],
theme: document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'light'
},!{JSON.stringify(option)})
function addRemark42(){ const loadScript = src => {
for (let i = 0; i < remark_config.components.length; i++) { const script = document.createElement('script')
const s = document.createElement('script') script.src = src
s.src = remark_config.host + '/web/' + remark_config.components[i] + '.js' script.defer = true
s.defer = true document.head.appendChild(script)
document.head.appendChild(s)
} }
}
function initRemark42() { const addRemark42 = () => loadScript('!{host}/web/embed.js')
if (window.REMARK42) {
if (this.remark42Instance) { const getCount = () => document.querySelector('.remark42__counter') && loadScript('!{host}/web/count.js')
this.remark42Instance.destroy()
const destroyRemark42 = () => window.remark42Instance && window.remark42Instance.destroy()
const initRemark42 = remark_config => {
if (window.REMARK42) {
destroyRemark42()
window.remark42Instance = window.REMARK42.createInstance({
...remark_config
})
}
}
const loadRemark42 = (el, path) => {
if (isShuoshuo) {
window.shuoshuoComment.destroyRemark42 = () => {
destroyRemark42()
if (el.children.length) {
el.innerHTML = ''
el.classList.add('no-comment')
}
}
} }
this.remark42Instance = window.REMARK42.createInstance({ window.remark_config = {
...remark_config host: '!{host}',
}) site_id: '!{siteId}',
} theme: document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'light',
} ...option,
url: isShuoshuo ? window.location.origin + path : (option && option.url) || window.location.origin + window.location.pathname
}
function getCount () { if (window.REMARK42) {
const ele = document.querySelector('.remark42__counter') initRemark42(remark_config)
if (ele) {
const s = document.createElement('script')
s.src = remark_config.host + '/web/counter.js'
s.defer = true
document.head.appendChild(s)
}
}
function loadRemark42 () {
if (window.REMARK42) {
this.initRemark42()
getCount()
} else {
addRemark42()
window.addEventListener('REMARK42::ready', () => {
this.initRemark42()
getCount() getCount()
}) } else {
addRemark42()
window.addEventListener('REMARK42::ready', () => {
initRemark42(remark_config)
getCount()
})
}
} }
}
function remarkChangeMode (theme) { const remarkChangeMode = theme => window.REMARK42 && window.REMARK42.changeTheme(theme)
if (!window.REMARK42) return
window.REMARK42.changeTheme(theme)
}
btf.addGlobalFn('themeChange', remarkChangeMode, 'remark42') btf.addGlobalFn('themeChange', remarkChangeMode, 'remark42')
if ('!{theme.comments.use[0]}' === 'Remark42' || !!{theme.comments.lazyload}) { if (isShuoshuo) {
if (!{theme.comments.lazyload}) btf.loadComment(document.getElementById('remark42'), loadRemark42) '!{theme.comments.use[0]}' === 'Remark42'
else loadRemark42() ? window.shuoshuoComment = { loadComment: loadRemark42 }
} else { : window.loadOtherComment = loadRemark42
function loadOtherComment () { return
loadRemark42()
} }
}
if ('!{theme.comments.use[0]}' === 'Remark42' || !!{theme.comments.lazyload}) {
if (!{theme.comments.lazyload}) btf.loadComment(document.getElementById('remark42'), loadRemark42)
else loadRemark42()
} else {
window.loadOtherComment = loadRemark42
}
})()

View File

@@ -3,6 +3,9 @@
script. script.
(() => { (() => {
const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'
const option = !{JSON.stringify(option)}
const getCount = () => { const getCount = () => {
const countELement = document.getElementById('twikoo-count') const countELement = document.getElementById('twikoo-count')
if(!countELement) return if(!countELement) return
@@ -18,22 +21,38 @@ script.
}) })
} }
const init = () => { const init = (el = document, path = location.pathname) => {
twikoo.init(Object.assign({ twikoo.init({
el: '#twikoo-wrap', el: el.querySelector('#twikoo-wrap'),
envId: '!{envId}', envId: '!{envId}',
region: '!{region}', region: '!{region}',
onCommentLoaded: () => { onCommentLoaded: () => {
btf.loadLightbox(document.querySelectorAll('#twikoo .tk-content img:not(.tk-owo-emotion)')) btf.loadLightbox(document.querySelectorAll('#twikoo .tk-content img:not(.tk-owo-emotion)'))
} },
}, !{JSON.stringify(option)})) ...option,
path: isShuoshuo ? path : (option && option.path) || path
})
!{count ? 'GLOBAL_CONFIG_SITE.isPost && getCount()' : ''} !{count ? `GLOBAL_CONFIG_SITE.pageType === 'post' && getCount()` : ''}
isShuoshuo && (window.shuoshuoComment.destroyTwikoo = () => {
if (el.children.length) {
el.innerHTML = ''
el.classList.add('no-comment')
}
})
} }
const loadTwikoo = () => { const loadTwikoo = (el, path) => {
if (typeof twikoo === 'object') setTimeout(init,0) if (typeof twikoo === 'object') setTimeout(() => init(el, path), 0)
else btf.getScript('!{url_for(theme.asset.twikoo)}').then(init) else btf.getScript('!{url_for(theme.asset.twikoo)}').then(() => init(el, path))
}
if (isShuoshuo) {
'!{use[0]}' === 'Twikoo'
? window.shuoshuoComment = { loadComment: loadTwikoo }
: window.loadOtherComment = loadTwikoo
return
} }
if ('!{use[0]}' === 'Twikoo' || !!{lazyload}) { if ('!{use[0]}' === 'Twikoo' || !!{lazyload}) {

View File

@@ -5,24 +5,33 @@
script. script.
(() => { (() => {
const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'
const option = !{JSON.stringify(option)}
const getUtterancesTheme = theme => theme === 'dark' ? '#{dark_theme}' : '#{light_theme}' const getUtterancesTheme = theme => theme === 'dark' ? '#{dark_theme}' : '#{light_theme}'
const loadUtterances = () => { const loadUtterances = (el = document, key) => {
const config = Object.assign({ if (isShuoshuo) {
id: 'utterances_comment', window.shuoshuoComment.destroyUtterances = () => {
if (el.children.length) {
el.innerHTML = ''
el.classList.add('no-comment')
}
}
}
const config = {
src: '!{utterancesUrl}', src: '!{utterancesUrl}',
repo: '!{repo}', repo: '!{repo}',
'issue-term': '!{issue_term}',
theme: getUtterancesTheme(document.documentElement.getAttribute('data-theme')), theme: getUtterancesTheme(document.documentElement.getAttribute('data-theme')),
crossorigin: 'anonymous', crossorigin: 'anonymous',
async: true async: true,
},!{JSON.stringify(option)}) ...option,
'issue-term': isShuoshuo ? key : (option && option['issue-term']) || '!{issue_term}'
}
const ele = document.createElement('script') const ele = document.createElement('script')
for (let key in config) { Object.entries(config).forEach(([key, value]) => ele.setAttribute(key, value))
ele.setAttribute(key, config[key]) el.querySelector('#utterances-wrap').appendChild(ele)
}
document.getElementById('utterances-wrap').appendChild(ele)
} }
const changeUtterancesTheme = theme => { const changeUtterancesTheme = theme => {
@@ -38,6 +47,13 @@ script.
btf.addGlobalFn('themeChange', changeUtterancesTheme, 'utterances') btf.addGlobalFn('themeChange', changeUtterancesTheme, 'utterances')
if (isShuoshuo) {
'!{use[0]}' === 'Utterances'
? window.shuoshuoComment = { loadComment: loadUtterances }
: window.loadOtherComment = loadUtterances
return
}
if ('!{use[0]}' === 'Utterances' || !!{lazyload}) { if ('!{use[0]}' === 'Utterances' || !!{lazyload}) {
if (!{lazyload}) btf.loadComment(document.getElementById('utterances-wrap'), loadUtterances) if (!{lazyload}) btf.loadComment(document.getElementById('utterances-wrap'), loadUtterances)
else loadUtterances() else loadUtterances()

View File

@@ -7,27 +7,50 @@ if site.data.valine
script. script.
(() => { (() => {
const initValine = () => { const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'
const valine = new Valine(Object.assign({ const option = !{JSON.stringify(option)}
const initValine = (el, path) => {
if (isShuoshuo) {
window.shuoshuoComment.destroyValine = () => {
if (el.children.length) {
el.innerHTML = ''
el.classList.add('no-comment')
}
}
}
const valineConfig = {
el: '#vcomment', el: '#vcomment',
appId: '#{appId}', appId: '#{appId}',
appKey: '#{appKey}', appKey: '#{appKey}',
avatar: '#{avatar}', avatar: '#{avatar}',
serverURLs: '#{serverURLs}', serverURLs: '#{serverURLs}',
emojiMaps: !{emojiMaps}, emojiMaps: !{emojiMaps},
path: window.location.pathname, visitor: #{visitor},
visitor: #{visitor} ...option,
}, !{JSON.stringify(option)})) path: isShuoshuo ? path : (option && option.path) || window.location.pathname
}
new Valine(valineConfig)
} }
const loadValine = async () => { const loadValine = async (el, path) => {
if (typeof Valine === 'function') initValine() if (typeof Valine === 'function') {
else { initValine(el, path)
} else {
await btf.getScript('!{url_for(theme.asset.valine)}') await btf.getScript('!{url_for(theme.asset.valine)}')
initValine() initValine(el, path)
} }
} }
if (isShuoshuo) {
'!{use[0]}' === 'Valine'
? window.shuoshuoComment = { loadComment: loadValine }
: window.loadOtherComment = loadValine
return
}
if ('!{use[0]}' === 'Valine' || !!{lazyload}) { if ('!{use[0]}' === 'Valine' || !!{lazyload}) {
if (!{lazyload}) btf.loadComment(document.getElementById('vcomment'),loadValine) if (!{lazyload}) btf.loadComment(document.getElementById('vcomment'),loadValine)
else setTimeout(loadValine, 0) else setTimeout(loadValine, 0)
@@ -35,4 +58,3 @@ script.
window.loadOtherComment = loadValine window.loadOtherComment = loadValine
} }
})() })()

View File

@@ -4,37 +4,53 @@
script. script.
(() => { (() => {
let initFn = window.walineFn || null let initFn = window.walineFn || null
const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'
const option = !{JSON.stringify(option)}
const initWaline = (Fn) => { const destroyWaline = ele => ele.destroy()
const waline = Fn(Object.assign({
el: '#waline-wrap', const initWaline = (Fn, el = document, path = window.location.pathname) => {
const waline = Fn({
el: el.querySelector('#waline-wrap'),
serverURL: '!{serverURL}', serverURL: '!{serverURL}',
pageview: !{lazyload ? false : pageview}, pageview: !{lazyload ? false : pageview},
dark: 'html[data-theme="dark"]', dark: 'html[data-theme="dark"]',
path: window.location.pathname,
comment: !{lazyload ? false : count}, comment: !{lazyload ? false : count},
}, !{JSON.stringify(option)})) ...option,
path: isShuoshuo ? path : (option && option.path) || path
})
const destroyWaline = () => { if (isShuoshuo) {
waline.destroy() window.shuoshuoComment.destroyWaline = () => {
destroyWaline(waline)
if (el.children.length) {
el.innerHTML = ''
el.classList.add('no-comment')
}
}
} }
btf.addGlobalFn('pjaxSendOnce', destroyWaline, 'destroyWaline')
} }
const loadWaline = () => { const loadWaline = (el, path) => {
if (initFn) initWaline(initFn) if (initFn) initWaline(initFn, el, path)
else { else {
btf.getCSS('!{url_for(theme.asset.waline_css)}') btf.getCSS('!{url_for(theme.asset.waline_css)}')
.then(() => import('!{url_for(theme.asset.waline_js)}')) .then(() => import('!{url_for(theme.asset.waline_js)}'))
.then(({ init }) => { .then(({ init }) => {
initFn = init || Waline.init initFn = init || Waline.init
initWaline(initFn) initWaline(initFn, el, path)
window.walineFn = initFn window.walineFn = initFn
}) })
} }
} }
if (isShuoshuo) {
'!{use[0]}' === 'Waline'
? window.shuoshuoComment = { loadComment: loadWaline }
: window.loadOtherComment = loadWaline
return
}
if ('!{use[0]}' === 'Waline' || !!{lazyload}) { if ('!{use[0]}' === 'Waline' || !!{lazyload}) {
if (!{lazyload}) btf.loadComment(document.getElementById('waline-wrap'),loadWaline) if (!{lazyload}) btf.loadComment(document.getElementById('waline-wrap'),loadWaline)
else setTimeout(loadWaline, 0) else setTimeout(loadWaline, 0)

View File

@@ -0,0 +1,91 @@
- const { fontColor, borderColor, scale_ticks_backdropColor } = theme.chartjs
script.
(() => {
const applyThemeDefaultsConfig = theme => {
if (theme === 'dark-mode') {
Chart.defaults.color = "!{fontColor.dark}"
Chart.defaults.borderColor = "!{borderColor.dark}"
Chart.defaults.scale.ticks.backdropColor = "!{scale_ticks_backdropColor.dark}"
} else {
Chart.defaults.color = "!{fontColor.light}"
Chart.defaults.borderColor = "!{borderColor.light}"
Chart.defaults.scale.ticks.backdropColor = "!{scale_ticks_backdropColor.light}"
}
}
// Recursively traverse the config object and automatically apply theme-specific color schemes
const applyThemeConfig = (obj, theme) => {
if (typeof obj !== 'object' || obj === null) return
Object.keys(obj).forEach(key => {
const value = obj[key]
// If the property is an object and has theme-specific options, apply them
if (typeof value === 'object' && value !== null) {
if (value[theme]) {
obj[key] = value[theme] // Apply the value for the current theme
} else {
// Recursively process child objects
applyThemeConfig(value, theme)
}
}
})
}
const runChartJS = ele => {
window.loadChartJS = true
Array.from(ele).forEach((item, index) => {
const chartSrc = item.firstElementChild
const chartID = item.getAttribute('data-chartjs-id') || ('chartjs-' + index) // Use custom ID or default ID
const width = item.getAttribute('data-width')
const existingCanvas = document.getElementById(chartID)
// If a canvas already exists, remove it to avoid rendering duplicates
if (existingCanvas) {
existingCanvas.parentNode.remove()
}
const chartDefinition = chartSrc.textContent
const canvas = document.createElement('canvas')
canvas.id = chartID
const div = document.createElement('div')
div.className = 'chartjs-wrap'
if (width) {
div.style.width = width
}
div.appendChild(canvas)
chartSrc.insertAdjacentElement('afterend', div)
const ctx = document.getElementById(chartID).getContext('2d')
const config = JSON.parse(chartDefinition)
const theme = document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark-mode' : 'light-mode'
// Set default styles (initial setup)
applyThemeDefaultsConfig(theme)
// Automatically traverse the config and apply dual-mode color schemes
applyThemeConfig(config, theme)
new Chart(ctx, config)
})
}
const loadChartJS = () => {
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
btf.addGlobalFn('themeChange', loadChartJS, 'chartjs')
btf.addGlobalFn('encrypt', loadChartJS, 'chartjs')
window.pjax ? loadChartJS() : document.addEventListener('DOMContentLoaded', loadChartJS)
})()

View File

@@ -1,11 +1,14 @@
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
include ./mermaid.pug include ./mermaid.pug
if theme.chartjs.enable
include ./chartjs.pug

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,16 +42,19 @@ 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
const avatar = avatarCdn && e.email_encrypted ? `${avatarCdn}${e.email_encrypted}?${avatarDefault}` : '' .filter(e => !e.is_pending) // Filter pending comments
return { .slice(0, !{newestCommentsLimit}) // Limit the number of comments
'avatar': avatar, .map(e => {
'content': changeContent(e.content_marked), const avatar = avatarCdn && e.email_encrypted ? `${avatarCdn}${e.email_encrypted}?${avatarDefault}` : ''
'nick': e.nick, return {
'url': e.page_url, 'avatar': avatar,
'date': e.date, 'content': changeContent(e.content_marked),
} 'nick': e.nick,
}) 'url': e.page_url,
'date': e.date,
}
})
btf.saveToLocal.set(keyName, JSON.stringify(artalk), !{theme.aside.card_newest_comments.storage}/(60*24)) btf.saveToLocal.set(keyName, JSON.stringify(artalk), !{theme.aside.card_newest_comments.storage}/(60*24))
generateHtml(artalk, ele) generateHtml(artalk, ele)
} catch (e) { } catch (e) {

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

@@ -1,4 +1,5 @@
- const { host, siteId } = theme.remark42 - const { host, siteId } = theme.remark42
!= partial("includes/third-party/newest-comments/common.pug", {}, { cache: true })
script. script.
window.addEventListener('load', () => { window.addEventListener('load', () => {
@@ -6,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

@@ -7,10 +7,12 @@ if theme.pjax.exclude
- let choose = theme.comments.use - let choose = theme.comments.use
if choose if choose
if theme.Open_Graph_meta.enable && (choose.includes('Livere') || choose.includes('Utterances') || choose.includes('Giscus')) if choose.includes('Livere') || choose.includes('Utterances') || choose.includes('Giscus')
- pjaxSelectors.unshift('meta[property="og:image"]', 'meta[property="og:title"]', 'meta[property="og:url"]')
if choose.includes('Utterances') || choose.includes('Giscus')
- pjaxSelectors.unshift('link[rel="canonical"]') - pjaxSelectors.unshift('link[rel="canonical"]')
if theme.Open_Graph_meta.enable
- pjaxSelectors.unshift('meta[property="og:image"]', 'meta[property="og:title"]', 'meta[property="og:url"]', 'meta[property="og:description"]')
else
- pjaxSelectors.unshift('meta[name="description"]')
script(src=url_for(theme.asset.pjax)) script(src=url_for(theme.asset.pjax))
script. script.
@@ -57,7 +59,10 @@ script.
document.addEventListener('pjax:error', e => { document.addEventListener('pjax:error', e => {
if (e.request.status === 404) { if (e.request.status === 404) {
pjax.loadUrl('!{url_for("/404.html")}') const usePjax = !{theme.pjax && theme.pjax.enable}
!{theme.error_404 && theme.error_404.enable}
? (usePjax ? pjax.loadUrl('!{url_for("/404.html")}') : window.location.href = '!{url_for("/404.html")}')
: window.location.href = e.request.responseURL
} }
}) })
})() })()

View File

@@ -6,7 +6,7 @@
button.search-close-button button.search-close-button
i.fas.fa-times i.fas.fa-times
#loading-database.is-center #loading-database.text-center
i.fas.fa-spinner.fa-pulse i.fas.fa-spinner.fa-pulse
span= ' ' + _p("search.load_data") span= ' ' + _p("search.load_data")

View File

@@ -1,6 +1,6 @@
.addtoany .addtoany
.a2a_kit.a2a_kit_size_32.a2a_default_style .a2a_kit.a2a_kit_size_32.a2a_default_style
- let addtoanyItem = theme.addtoany.item.split(',') - let addtoanyItem = theme.share.addtoany.item.split(',')
each name in addtoanyItem each name in addtoanyItem
a(class="a2a_button_" + name) a(class="a2a_button_" + name)

View File

@@ -1,4 +1,4 @@
- const { effect,source,sub,typed_option } = theme.subtitle - const { effect, source, sub, typed_option } = theme.subtitle
- let subContent = sub || new Array() - let subContent = sub || new Array()
script. script.
@@ -22,6 +22,26 @@ script.
} else { } else {
subtitleType() subtitleType()
} }
},
processSubtitle: (content, extraContents = []) => {
if (!{effect}) {
const sub = !{JSON.stringify(subContent)}.slice()
if (extraContents.length > 0) {
sub.unshift(...extraContents)
}
if (typeof content === 'string') {
sub.unshift(content)
} else if (Array.isArray(content)) {
sub.unshift(...content)
}
sub.length > 0 && typedJSFn.init(sub)
} else {
document.getElementById('subtitle').textContent = typeof content === 'string' ? content :
(Array.isArray(content) && content.length > 0 ? content[0] : '')
}
} }
} }
btf.addGlobalFn('pjaxSendOnce', () => { typed.destroy() }, 'typedDestroy') btf.addGlobalFn('pjaxSendOnce', () => { typed.destroy() }, 'typedDestroy')
@@ -33,14 +53,12 @@ case source
fetch('https://v1.hitokoto.cn') fetch('https://v1.hitokoto.cn')
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
if (!{effect}) { const from = '出自 ' + data.from
const from = '出自 ' + data.from typedJSFn.processSubtitle(data.hitokoto, [from])
const sub = !{JSON.stringify(subContent)} })
sub.unshift(data.hitokoto, from) .catch(err => {
typedJSFn.init(sub) console.error('Failed to get the Hitokoto API:', err)
} else { typedJSFn.processSubtitle(!{JSON.stringify(subContent)})
document.getElementById('subtitle').textContent = data.hitokoto
}
}) })
} }
typedJSFn.run(subtitleType) typedJSFn.run(subtitleType)
@@ -48,46 +66,48 @@ case source
when 2 when 2
script. script.
function subtitleType () { function subtitleType () {
btf.getScript('https://yijuzhan.com/api/word.php?m=js').then(() => { fetch('https://v.api.aa1.cn/api/yiyan/index.php')
const con = str[0] .then(response => response.text())
if (!{effect}) { .then(data => {
const from = '出自 ' + str[1] const reg = /<p>(.*?)<\/p>/g
const sub = !{JSON.stringify(subContent)} const result = reg.exec(data)
sub.unshift(con, from) if (result && result[1]) {
typedJSFn.init(sub) typedJSFn.processSubtitle(result[1])
} else { } else {
document.getElementById('subtitle').textContent = con throw new Error('Failed to parse the return value of the Yiyan API')
} }
}) })
.catch(err => {
console.error('Failed to get the Yiyan API:', err)
typedJSFn.processSubtitle(!{JSON.stringify(subContent.length)})
})
} }
typedJSFn.run(subtitleType) typedJSFn.run(subtitleType)
when 3 when 3
script. script.
function subtitleType () { function subtitleType () {
btf.getScript('https://sdk.jinrishici.com/v2/browser/jinrishici.js').then(() => { btf.getScript('https://sdk.jinrishici.com/v2/browser/jinrishici.js')
jinrishici.load(result =>{ .then(() => {
if (!{effect}) { jinrishici.load(result => {
const sub = !{JSON.stringify(subContent)} if (result && result.data && result.data.content) {
const content = result.data.content typedJSFn.processSubtitle(result.data.content)
sub.unshift(content) } else {
typedJSFn.init(sub) throw new Error('Failed to parse the return value of Jinrishici API')
} else { }
document.getElementById('subtitle').textContent = result.data.content })
} })
.catch(err => {
console.error('Failed to get the Jinrishici API:', err)
typedJSFn.processSubtitle(!{JSON.stringify(subContent.length)})
}) })
})
} }
typedJSFn.run(subtitleType) typedJSFn.run(subtitleType)
default default
- subContent = subContent.length ? subContent : new Array(config.subtitle) if subContent.length > 0
script. script.
function subtitleType () { function subtitleType () {
if (!{effect}) { typedJSFn.processSubtitle(!{JSON.stringify(subContent)})
typedJSFn.init(!{JSON.stringify(subContent)})
} else {
document.getElementById("subtitle").textContent = !{JSON.stringify(subContent[0])}
} }
} typedJSFn.run(subtitleType)
typedJSFn.run(subtitleType)

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

@@ -1,5 +1,5 @@
if theme.aside.card_author.enable if theme.aside.card_author.enable
.card-widget.card-info.is-center .card-widget.card-info.text-center
.avatar-img .avatar-img
img(src=url_for(theme.avatar.img) onerror=`this.onerror=null;this.src='` + url_for(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")
.author-info-name= config.author .author-info-name= config.author

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.0.0", "version": "5.4.1",
"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": {
@@ -15,16 +15,18 @@
"hexo-theme-butterfly" "hexo-theme-butterfly"
], ],
"repository": { "repository": {
"type" : "git", "type": "git",
"url" : "https://github.com/jerryc127/hexo-theme-butterfly.git" "url": "https://github.com/jerryc127/hexo-theme-butterfly.git"
}, },
"bugs": { "bugs": {
"url": "https://github.com/jerryc127/hexo-theme-butterfly/issues", "url": "https://github.com/jerryc127/hexo-theme-butterfly/issues",
"email": "my@crazywong.com" "email": "my@crazywong.com"
}, },
"dependencies": { "dependencies": {
"hexo-renderer-pug": "^3.0.0",
"hexo-renderer-stylus": "^3.0.1", "hexo-renderer-stylus": "^3.0.1",
"hexo-renderer-pug": "^3.0.0" "hexo-util": "^3.3.0",
"moment-timezone": "^0.5.48"
}, },
"homepage": "https://butterfly.js.org/", "homepage": "https://butterfly.js.org/",
"author": "Jerry <my@crazywong.com>", "author": "Jerry <my@crazywong.com>",

View File

@@ -1,15 +1,15 @@
abcjs_basic_js: abcjs_basic_js:
name: abcjs name: abcjs
file: dist/abcjs-basic-min.js file: dist/abcjs-basic-min.js
version: 6.4.3 version: 6.5.1
activate_power_mode: activate_power_mode:
name: butterfly-extsrc name: butterfly-extsrc
file: dist/activate-power-mode.min.js file: dist/activate-power-mode.min.js
version: 1.1.4 version: 1.1.4
algolia_search: algolia_search:
name: algoliasearch name: algoliasearch
file: dist/algoliasearch-lite.umd.js file: dist/lite/builds/browser.umd.js
version: 5.7.0 version: 5.30.0
aplayer_css: aplayer_css:
name: aplayer name: aplayer
file: dist/APlayer.min.css file: dist/APlayer.min.css
@@ -42,6 +42,10 @@ canvas_ribbon:
name: butterfly-extsrc name: butterfly-extsrc
file: dist/canvas-ribbon.min.js file: dist/canvas-ribbon.min.js
version: 1.1.4 version: 1.1.4
chartjs:
name: chart.js
file: dist/chart.umd.js
version: 4.5.0
clickShowText: clickShowText:
name: butterfly-extsrc name: butterfly-extsrc
file: dist/click-show-text.min.js file: dist/click-show-text.min.js
@@ -53,21 +57,21 @@ click_heart:
disqusjs: disqusjs:
name: disqusjs name: disqusjs
file: dist/browser/disqusjs.es2015.umd.min.js file: dist/browser/disqusjs.es2015.umd.min.js
version: 3.0.2 version: 3.1.0
disqusjs_css: disqusjs_css:
name: disqusjs name: disqusjs
file: dist/browser/styles/disqusjs.css file: dist/browser/styles/disqusjs.css
version: 3.0.2 version: 3.1.0
docsearch_css: 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.2 version: 3.9.0
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.2 version: 3.9.0
egjs_infinitegrid: egjs_infinitegrid:
name: '@egjs/infinitegrid' name: '@egjs/infinitegrid'
other_name: egjs-infinitegrid other_name: egjs-infinitegrid
@@ -76,12 +80,12 @@ egjs_infinitegrid:
fancybox: fancybox:
name: '@fancyapps/ui' name: '@fancyapps/ui'
file: dist/fancybox/fancybox.umd.js file: dist/fancybox/fancybox.umd.js
version: 5.0.36 version: 6.0.7
other_name: fancyapps-ui other_name: fancyapps-ui
fancybox_css: fancybox_css:
name: '@fancyapps/ui' name: '@fancyapps/ui'
file: dist/fancybox/fancybox.css file: dist/fancybox/fancybox.css
version: 5.0.36 version: 6.0.7
other_name: fancyapps-ui other_name: fancyapps-ui
fireworks: fireworks:
name: butterfly-extsrc name: butterfly-extsrc
@@ -91,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
@@ -107,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.74.2 version: 4.79.0
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.22
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.22
lazyload: lazyload:
name: vanilla-lazyload name: vanilla-lazyload
file: dist/lazyload.iife.min.js file: dist/lazyload.iife.min.js
@@ -133,7 +137,7 @@ medium_zoom:
mermaid: mermaid:
name: mermaid name: mermaid
file: dist/mermaid.min.js file: dist/mermaid.min.js
version: 11.2.1 version: 11.8.0
meting_js: meting_js:
name: butterfly-extsrc name: butterfly-extsrc
file: metingjs/dist/Meting.min.js file: metingjs/dist/Meting.min.js
@@ -148,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
@@ -160,17 +160,17 @@ prismjs_autoloader:
name: prismjs name: prismjs
file: plugins/autoloader/prism-autoloader.min.js file: plugins/autoloader/prism-autoloader.min.js
other_name: prism other_name: prism
version: 1.29.0 version: 1.30.0
prismjs_js: prismjs_js:
name: prismjs name: prismjs
file: prism.js file: prism.js
other_name: prism other_name: prism
version: 1.29.0 version: 1.30.0
prismjs_lineNumber_js: prismjs_lineNumber_js:
name: prismjs name: prismjs
file: plugins/line-numbers/prism-line-numbers.min.js file: plugins/line-numbers/prism-line-numbers.min.js
other_name: prism other_name: prism
version: 1.29.0 version: 1.30.0
sharejs: sharejs:
name: butterfly-extsrc name: butterfly-extsrc
file: sharejs/dist/js/social-share.min.js file: sharejs/dist/js/social-share.min.js
@@ -190,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.44
typed: typed:
name: typed.js name: typed.js
file: dist/typed.umd.js file: dist/typed.umd.js
@@ -198,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.5.7
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.5.7

View File

@@ -0,0 +1,37 @@
'use strict'
const { stripHTML, truncate } = require('hexo-util')
// Truncates the given content to a specified length, removing HTML tags and replacing newlines with spaces.
const truncateContent = (content, length, encrypt = false) => {
if (!content || encrypt) return ''
return truncate(stripHTML(content).replace(/\n/g, ' '), { length })
}
// Generates a post description based on the provided data and theme configuration.
const postDesc = (data, hexo) => {
const { description, content, postDesc, encrypt } = data
if (postDesc) return postDesc
const { length, method } = hexo.theme.config.index_post_content
if (method === false) return
let result
switch (method) {
case 1:
result = description
break
case 2:
result = description || truncateContent(content, length, encrypt)
break
default:
result = truncateContent(content, length, encrypt)
}
data.postDesc = result
return result
}
module.exports = { truncateContent, postDesc }

View File

@@ -7,11 +7,18 @@ hexo.extend.filter.register('before_generate', () => {
let { use } = themeConfig.comments let { use } = themeConfig.comments
if (!use) return if (!use) return
// 確保 use 是一個陣列 // Make sure use is an array
use = Array.isArray(use) ? use : use.split(',') use = Array.isArray(use) ? use : use.split(',')
// 將每個項目轉換為小寫並將首字母大寫 // Capitalize the first letter of each comment name
themeConfig.comments.use = use.map(item => use = use.map(item =>
item.trim().toLowerCase().replace(/\b[a-z]/g, s => s.toUpperCase()) item.trim().toLowerCase().replace(/\b[a-z]/g, s => s.toUpperCase())
) )
// Disqus and Disqusjs conflict, only keep the first one
if (use.includes('Disqus') && use.includes('Disqusjs')) {
use = [use[0]]
}
themeConfig.comments.use = use
}) })

View File

@@ -5,6 +5,7 @@ hexo.extend.filter.register('before_generate', () => {
nav: { nav: {
logo: null, logo: null,
display_title: true, display_title: true,
display_post_title: true,
fixed: false fixed: false
}, },
menu: null, menu: null,
@@ -119,12 +120,16 @@ hexo.extend.filter.register('before_generate', () => {
message_next: 'days since the last update, the content of the article may be outdated.' message_next: 'days since the last update, the content of the article may be outdated.'
}, },
footer: { footer: {
nav: null,
owner: { owner: {
enable: true, enable: true,
since: 2019 since: 2024
}, },
custom_text: null, copyright: {
copyright: true enable: true,
version: true
},
custom_text: null
}, },
aside: { aside: {
enable: true, enable: true,
@@ -223,6 +228,7 @@ hexo.extend.filter.register('before_generate', () => {
hide: null, hide: null,
show: null show: null
}, },
rightside_config_animation: true,
anchor: { anchor: {
auto_update: false, auto_update: false,
click_to_scroll: false click_to_scroll: false
@@ -341,7 +347,7 @@ hexo.extend.filter.register('before_generate', () => {
user_id: null, user_id: null,
pageSize: 10, pageSize: 10,
order_by: 'social', order_by: 'social',
lang: 'zh_TW' lang: 'en_US'
}, },
twikoo: { twikoo: {
envId: null, envId: null,
@@ -380,12 +386,13 @@ hexo.extend.filter.register('before_generate', () => {
tidio: { tidio: {
public_key: null public_key: null
}, },
daovoice: {
app_id: null
},
crisp: { crisp: {
website_id: null website_id: null
}, },
google_tag_manager: {
tag_id: null,
domain: 'https://www.googletagmanager.com'
},
baidu_analytics: null, baidu_analytics: null,
google_analytics: null, google_analytics: null,
cloudflare_analytics: null, cloudflare_analytics: null,
@@ -512,6 +519,21 @@ hexo.extend.filter.register('before_generate', () => {
dark: 'dark' dark: 'dark'
} }
}, },
chartjs: {
enable: false,
fontColor: {
light: 'rgba(0, 0, 0, 0.8)',
dark: 'rgba(255, 255, 255, 0.8)'
},
borderColor: {
light: 'rgba(0, 0, 0, 0.1)',
dark: 'rgba(255, 255, 255, 0.2)'
},
scale_ticks_backdropColor: {
light: 'transparent',
dark: 'transparent'
}
},
note: { note: {
style: 'flat', style: 'flat',
icons: true, icons: true,
@@ -533,12 +555,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
@@ -555,6 +574,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,40 +1,82 @@
/** /**
* 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 = []
let { cover: coverVal, top_img: topImg } = data
// Add path to top_img and cover if post_asset_folder is enabled const getRandomCover = defaultCover => {
if (hexo.config.post_asset_folder) {
if (topImg && topImg.indexOf('/') === -1 && imgTestReg.test(topImg)) 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 (!defaultCover) return false
if (!Array.isArray(defaultCover)) return defaultCover if (!Array.isArray(defaultCover)) return defaultCover
const num = Math.floor(Math.random() * defaultCover.length)
return defaultCover[num] 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]
} }
if (coverVal === false) return data const handleImg = data => {
const imgTestReg = /\.(png|jpe?g|gif|svg|webp|avif)(\?.*)?$/i
let { cover: coverVal, top_img: topImg } = data
// If cover is not set, use random cover // Add path to top_img and cover if post_asset_folder is enabled
if (!coverVal) { if (hexo.config.post_asset_folder) {
const randomCover = randomCoverFn() if (topImg && topImg.indexOf('/') === -1 && imgTestReg.test(topImg)) {
data.cover = randomCover data.top_img = `${data.path}${topImg}`
coverVal = randomCover // update coverVal }
if (coverVal && coverVal.indexOf('/') === -1 && imgTestReg.test(coverVal)) {
data.cover = `${data.path}${coverVal}`
}
}
if (coverVal === false) return data
// If cover is not set, use random cover
if (!coverVal) {
const { cover: { default_cover: defaultCover } } = hexo.theme.config
const randomCover = getRandomCover(defaultCover)
data.cover = randomCover
coverVal = randomCover // update coverVal
}
if (coverVal && (coverVal.indexOf('//') !== -1 || imgTestReg.test(coverVal))) {
data.cover_type = 'img'
}
return data
} }
if (coverVal && (coverVal.indexOf('//') !== -1 || imgTestReg.test(coverVal))) { // https://github.com/hexojs/hexo/blob/master/lib%2Fplugins%2Fgenerator%2Fpost.ts
data.cover_type = 'img' const posts = locals.posts.sort('date').toArray()
} const { length } = posts
return data 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,74 @@
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 { archive_dir: archiveDir, timezone, language } = config
const { timezone } = config
const lang = toMomentLocale(page.lang || page.language || config.language)
const type = options.type || 'monthly'
const format = options.format || (type === 'monthly' ? 'MMMM YYYY' : 'YYYY')
const showCount = Object.prototype.hasOwnProperty.call(options, 'show_count') ? options.show_count : true
const order = options.order || -1
const limit = options.limit
const compareFunc = type === 'monthly'
? (yearA, monthA, yearB, monthB) => yearA === yearB && monthA === monthB
: (yearA, monthA, yearB, monthB) => yearA === yearB
const posts = site.posts.sort('date', order) // Destructure and set default options with object destructuring
if (!posts.length) return '' const {
type = 'monthly',
format = type === 'monthly' ? 'MMMM YYYY' : 'YYYY',
show_count: showCount = true,
order = -1,
limit,
transform
} = options
const data = [] // Optimize locale handling
posts.forEach(post => { const lang = toMomentLocale(page.lang || page.language || language)
// Memoize comparison function to improve performance
const compareFunc =
type === 'monthly'
? (yearA, monthA, yearB, monthB) => yearA === yearB && monthA === monthB
: (yearA, yearB) => yearA === yearB
// Early return if no posts
if (!site.posts.length) return ''
// Use reduce for more efficient data processing
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 (type === 'yearly') {
const existingYearIndex = acc.findIndex(entry => entry.year === year)
if (existingYearIndex !== -1) {
acc[existingYearIndex].count++
} else {
// 否則創建新條目
acc.push({
name: date.format(format),
year,
month,
count: 1
})
}
} else {
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 +77,50 @@ 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">
<li class="card-archive-list-item"> ${limitedData
<a class="card-archive-list-link" href="${link(item)}"> .map(
<span class="card-archive-list-date">${options.transform ? options.transform(item.name) : item.name}</span> item => `
${showCount ? `<span class="card-archive-list-count">${item.count}</span>` : ''} <li class="card-archive-list-item">
</a> <a class="card-archive-list-link" href="${createArchiveLink(item)}">
</li> <span class="card-archive-list-date">
` ${transform ? transform(item.name) : item.name}
} </span>
${showCount ? `<span class="card-archive-list-count">${item.count}</span>` : ''}
</a>
</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

@@ -55,7 +55,6 @@ hexo.extend.helper.register('inject_head_js', function () {
if (!${pjax.enable} && key.startsWith('pjax')) return if (!${pjax.enable} && key.startsWith('pjax')) return
const globalFn = parent.globalFn || {} const globalFn = parent.globalFn || {}
globalFn[key] = globalFn[key] || {} globalFn[key] = globalFn[key] || {}
if (name && globalFn[key][name]) return
globalFn[key][name || Object.keys(globalFn[key]).length] = fn globalFn[key][name || Object.keys(globalFn[key]).length] = fn
parent.globalFn = globalFn parent.globalFn = globalFn
} }

View File

@@ -1,10 +1,14 @@
'use strict' 'use strict'
const { stripHTML, prettyUrls, truncate } = require('hexo-util') const { truncateContent, postDesc } = require('../common/postDesc')
const { prettyUrls } = require('hexo-util')
const crypto = require('crypto') const crypto = require('crypto')
const moment = require('moment-timezone')
hexo.extend.helper.register('truncate', (content, length) => { hexo.extend.helper.register('truncate', truncateContent)
return truncate(stripHTML(content), { length, separator: ' ' }).replace(/\n/g, ' ')
hexo.extend.helper.register('postDesc', data => {
return postDesc(data, hexo)
}) })
hexo.extend.helper.register('cloudTags', function (options = {}) { hexo.extend.helper.register('cloudTags', function (options = {}) {
@@ -77,18 +81,72 @@ hexo.extend.helper.register('findArchivesTitle', function (page, menu, date) {
return loop(menu) || defaultTitle return loop(menu) || defaultTitle
}) })
hexo.extend.helper.register('getBgPath', path => { hexo.extend.helper.register('getBgPath', function(path) {
if (!path) return '' if (!path) return ''
const absoluteUrlPattern = /^(?:[a-z][a-z\d+.-]*:)?\/\//i const absoluteUrlPattern = /^(?:[a-z][a-z\d+.-]*:)?\/\//i
const relativeUrlPattern = /^(\.\/|\.\.\/|\/|[^/]+\/).*$/ const relativeUrlPattern = /^(\.\/|\.\.\/|\/|[^/]+\/).*$/
const colorPattern = /^(#|rgb|rgba|hsl|hsla|linear-gradient|radial-gradient)/i const colorPattern = /^(#|rgb|rgba|hsl|hsla)/i
if (colorPattern.test(path)) { if (colorPattern.test(path)) {
return `background-color: ${path};` return `background-color: ${path};`
} else if (absoluteUrlPattern.test(path) || relativeUrlPattern.test(path)) { } else if (absoluteUrlPattern.test(path) || relativeUrlPattern.test(path)) {
return `background-image: url(${path});` return `background-image: url(${this.url_for(path)});`
} else { } else {
return `background: ${path};` return `background: ${path};`
} }
}) })
hexo.extend.helper.register('shuoshuoFN', (data, page) => {
const { limit } = page
let finalResult = ''
// Check if limit.value is a valid date
const isValidDate = date => !isNaN(Date.parse(date))
// order by date
const orderByDate = data => data.sort((a, b) => Date.parse(b.date) - Date.parse(a.date))
// Apply number limit or time limit conditionally
const limitData = data => {
if (limit && limit.type === 'num' && limit.value > 0) {
return data.slice(0, limit.value)
} else if (limit && limit.type === 'date' && isValidDate(limit.value)) {
const limitDate = Date.parse(limit.value)
return data.filter(item => Date.parse(item.date) >= limitDate)
}
return data
}
orderByDate(data)
finalResult = limitData(data)
// This is a hack method, because hexo treats time as UTC time
// so you need to manually convert the time zone
finalResult.forEach(item => {
const utcDate = moment.utc(item.date).format('YYYY-MM-DD HH:mm:ss')
item.date = moment.tz(utcDate, hexo.config.timezone).format('YYYY-MM-DD HH:mm:ss')
})
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'
})
hexo.extend.helper.register('getVersion', () => {
const { version } = require('../../package.json')
return { hexo: hexo.version, theme: version }
})

View File

@@ -1,3 +1,4 @@
/* eslint-disable camelcase */
/** /**
* Butterfly * Butterfly
* Related Posts * Related Posts
@@ -6,12 +7,15 @@
'use strict' 'use strict'
const { postDesc } = require('../common/postDesc')
hexo.extend.helper.register('related_posts', function (currentPost, allPosts) { hexo.extend.helper.register('related_posts', function (currentPost, allPosts) {
let relatedPosts = [] let relatedPosts = []
const tagsData = currentPost.tags const tagsData = currentPost.tags
tagsData.length && tagsData.forEach(function (tag) { tagsData.length && tagsData.forEach(function (tag) {
allPosts.forEach(function (post) { allPosts.forEach(function (post) {
if (currentPost.path !== post.path && isTagRelated(tag.name, post.tags)) { if (currentPost.path !== post.path && isTagRelated(tag.name, post.tags)) {
const getPostDesc = post.postDesc || postDesc(post, hexo)
const relatedPost = { const relatedPost = {
title: post.title, title: post.title,
path: post.path, path: post.path,
@@ -19,7 +23,8 @@ hexo.extend.helper.register('related_posts', function (currentPost, allPosts) {
cover_type: post.cover_type, cover_type: post.cover_type,
weight: 1, weight: 1,
updated: post.updated, updated: post.updated,
created: post.date created: post.date,
postDesc: getPostDesc
} }
const index = findItem(relatedPosts, 'path', post.path) const index = findItem(relatedPosts, 'path', post.path)
if (index !== -1) { if (index !== -1) {
@@ -50,20 +55,27 @@ hexo.extend.helper.register('related_posts', function (currentPost, allPosts) {
result += '<div class="relatedPosts-list">' result += '<div class="relatedPosts-list">'
for (let i = 0; i < Math.min(relatedPosts.length, limitNum); i++) { for (let i = 0; i < Math.min(relatedPosts.length, limitNum); i++) {
const cover = relatedPosts[i].cover || 'var(--default-bg-color)' let { cover, title, path, cover_type, created, updated, postDesc } = relatedPosts[i]
const title = this.escape_html(relatedPosts[i].title) const { escape_html, url_for, date } = this
result += `<a href="${this.url_for(relatedPosts[i].path)}" title="${title}">` cover = cover || 'var(--default-bg-color)'
if (relatedPosts[i].cover_type === 'img') { title = escape_html(title)
result += `<img class="cover" src="${this.url_for(cover)}" alt="cover">` const className = postDesc ? 'pagination-related' : 'pagination-related no-desc'
result += `<a class="${className}" href="${url_for(path)}" title="${title}">`
if (cover_type === 'img') {
result += `<img class="cover" src="${url_for(cover)}" alt="cover">`
} else { } else {
result += `<div class="cover" style="background: ${cover}"></div>` result += `<div class="cover" style="background: ${cover}"></div>`
} }
if (dateType === 'created') { if (dateType === 'created') {
result += `<div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> ${this.date(relatedPosts[i].created, hexoConfig.date_format)}</div>` result += `<div class="info text-center"><div class="info-1"><div class="info-item-1"><i class="far fa-calendar-alt fa-fw"></i> ${date(created, hexoConfig.date_format)}</div>`
} else { } else {
result += `<div class="content is-center"><div class="date"><i class="fas fa-history fa-fw"></i> ${this.date(relatedPosts[i].updated, hexoConfig.date_format)}</div>` result += `<div class="info text-center"><div class="info-1"><div class="info-item-1"><i class="fas fa-history fa-fw"></i> ${date(updated, hexoConfig.date_format)}</div>`
}
result += `<div class="info-item-2">${title}</div></div>`
if (postDesc) {
result += `<div class="info-2"><div class="info-item-1">${postDesc}</div></div>`
} }
result += `<div class="title">${title}</div>`
result += '</div></a>' result += '</div></a>'
} }

49
scripts/tag/chartjs.js Normal file
View File

@@ -0,0 +1,49 @@
/**
* Butterfly
* chartjs
* https://www.chartjs.org/
* {% chartjs [width, abreast, chartId] %}
* <!-- chart -->
* <!-- endchart -->
* <!-- desc -->
* <!-- enddesc -->
* {% endchartjs %}
*/
'use strict'
const { escapeHTML } = require('hexo-util')
const chartjs = (args, content) => {
if (!content) return
const chartRegex = /<!--\s*chart\s*-->\n([\w\W\s\S]*?)<!--\s*endchart\s*-->/
const descRegex = /<!--\s*desc\s*-->\n([\w\W\s\S]*?)<!--\s*enddesc\s*-->/
const selfConfig = args.join(' ').trim()
const [width = '', layout = false, chartId = ''] = selfConfig.split(',').map(s => s.trim())
const chartMatch = content.match(chartRegex)
const descMatch = content.match(descRegex)
if (!chartMatch) {
hexo.log.warn('chartjs tag: chart content is required!')
return
}
const chartConfig = chartMatch && chartMatch[1] ? chartMatch[1] : ''
const descContent = descMatch && descMatch[1] ? descMatch[1] : ''
const renderedDesc = descContent ? hexo.render.renderSync({ text: descContent, engine: 'markdown' }).trim() : ''
const descDOM = renderedDesc ? `<div class="chartjs-desc">${renderedDesc}</div>` : ''
const abreastClass = layout ? ' chartjs-abreast' : ''
const widthStyle = width ? `data-width="${width}%"` : ''
return `<div class="chartjs-container${abreastClass}" data-chartjs-id="${chartId}" ${widthStyle}>
<pre class="chartjs-src" hidden>${escapeHTML(chartConfig)}</pre>
${descDOM}
</div>`
}
hexo.extend.tag.register('chartjs', chartjs, { ends: true })

View File

@@ -3,62 +3,74 @@
* 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] %}
*/ */
'use strict' 'use strict'
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],
title: m[3]
})
}
dataStr = JSON.stringify(arr) const parseImageContent = content => {
const images = []
let match
while ((match = IMAGE_REGEX.exec(content)) !== null) {
images.push({
url: match[2],
alt: match[1] || '',
title: match[3] || ''
})
} }
return `<div class="gallery-container" data-type="${type}" data-button="${button}"> return images
<div class="gallery-items">${dataStr}</div> }
</div>`
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>`
}
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

@@ -10,7 +10,7 @@ const { escapeHTML } = require('hexo-util')
const mermaid = (args, content) => { const mermaid = (args, content) => {
return `<div class="mermaid-wrap"><pre class="mermaid-src" hidden> return `<div class="mermaid-wrap"><pre class="mermaid-src" hidden>
${escapeHTML(content)} ${escapeHTML(content)}
</pre></div>` </pre></div>`
} }

View File

@@ -6,17 +6,45 @@
'use strict' 'use strict'
const score = (args, content) => { const score = (args, content) => {
// Escape HTML tags and some special characters, including curly braces
const escapeHtmlTags = s => { const escapeHtmlTags = s => {
const lookup = { const lookup = {
'&': '&amp;', '&': '&amp;',
'"': '&quot;', '"': '&quot;',
'\'': '&apos;', "'": '&apos;',
'<': '&lt;', '<': '&lt;',
'>': '&gt;' '>': '&gt;',
'{': '&#123;',
'}': '&#125;'
} }
return s.replace(/[&"'<>]/g, c => lookup[c]) return s.replace(/[&"'<>{}]/g, c => lookup[c])
} }
return `<div class="abc-music-sheet">${escapeHtmlTags(content)}</div>`
const trimmed = content.trim()
// Split content using six dashes as a delimiter
const parts = trimmed.split('------')
if (parts.length < 2) {
// If no delimiter is found, treat the entire content as the score
return `<div class="abc-music-sheet">${escapeHtmlTags(trimmed)}</div>`
}
// First part is parameters (JSON string), the rest is the score content
const paramPart = parts[0].trim()
const scorePart = parts.slice(1).join('------').trim()
let paramsObj = {}
try {
paramsObj = JSON.parse(paramPart)
} catch (e) {
console.error("Failed to parse JSON in score tag:", e)
}
// Use double quotes for data-params attribute value,
// ensuring JSON internal double quotes are escaped
return `<div class="abc-music-sheet" data-params="${escapeHtmlTags(JSON.stringify(paramsObj))}">
${escapeHtmlTags(scorePart)}
</div>`
} }
hexo.extend.tag.register('score', score, { ends: true }) hexo.extend.tag.register("score", score, { ends: true })

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

@@ -44,7 +44,7 @@ addBorderRadius(x = 6, hide = false)
.postImgHover .postImgHover
&:hover &:hover
.cover .cover
opacity: .7 opacity: .5
transform: scale(1.1) transform: scale(1.1)
.cover .cover

View File

@@ -10,8 +10,8 @@
--preloader-bg: $preloader-bg --preloader-bg: $preloader-bg
--preloader-color: $preloader-word-color --preloader-color: $preloader-word-color
--tab-border-color: $tab-border-color --tab-border-color: $tab-border-color
--tab-botton-bg: $tab-botton-bg --tab-button-bg: $tab-button-bg
--tab-botton-color: $tab-botton-color --tab-button-color: $tab-button-color
--tab-button-hover-bg: $tab-button-hover-bg --tab-button-hover-bg: $tab-button-hover-bg
--tab-button-active-bg: $tab-button-active-bg --tab-button-active-bg: $tab-button-active-bg
--card-bg: $card-bg --card-bg: $card-bg
@@ -179,14 +179,11 @@ if $site-name-font
#aside-content .author-info-description #aside-content .author-info-description
font-family: $site-name-font font-family: $site-name-font
.is-center .text-center
text-align: center text-align: center
.pull-left .text-right
float: left text-align: right
.pull-right
float: right
img img
&[src=''], &[src=''],

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

@@ -159,8 +159,8 @@
a a
display: flex display: flex
flex-direction: row flex-direction: row
padding: 2px 8px
margin: 2px 0 margin: 2px 0
padding: 2px 8px
color: var(--font-color) color: var(--font-color)
transition: all .3s transition: all .3s
addBorderRadius() addBorderRadius()
@@ -354,11 +354,12 @@
.headline .headline
@extend .limit-one-line @extend .limit-one-line
color: var(--font-color) color: var(--font-color)
font-size: .95em
.length-num .length-num
margin-top: -.32em margin-top: -.45em
color: var(--text-highlight-color) color: var(--text-highlight-color)
font-size: 1.4em font-size: 1.2em
@keyframes more-btn-move @keyframes more-btn-move
0%, 0%,

View File

@@ -13,17 +13,16 @@
background-color: var(--mark-bg) background-color: var(--mark-bg)
content: '' content: ''
#footer-wrap & > *
position: relative position: relative
padding: 40px 20px color: var(--light-grey)
color: var(--light-grey)
text-align: center
a a
color: var(--light-grey) color: var(--light-grey)
transition: all .3s ease-in-out
&:hover &:hover
text-decoration: underline color: $light-blue
.footer-separator .footer-separator
margin: 0 4px margin: 0 4px
@@ -33,3 +32,56 @@
max-height: 1.4em max-height: 1.4em
width: auto width: auto
vertical-align: text-bottom vertical-align: text-bottom
.footer-flex
display: flex
flex-direction: row
flex-wrap: wrap
justify-content: space-between
margin: 0 auto
padding: 40px 60px
max-width: 1200px
width: 100%
text-align: left
gap: 13px
+maxWidth768()
padding: 30px
gap: 10px
.footer-flex-items
flex-shrink: 0
min-width: 100px
text-align: left
white-space: nowrap
.footer-flex-title
margin-bottom: 5px
white-space: nowrap
font-weight: 600
font-size: 1.4em
.footer-flex-item
margin: 10px 0
white-space: nowrap
a
display: block
white-space: nowrap
.footer-other
padding: 40px 20px
width: 100%
text-align: center
if hexo-config('footer.nav')
padding: 10px 8px
background-color: rgba(0, 0, 0, .1)
.copyright,
.framework-info,
.footer_custom_text
font-size: .9em
else
.framework-info
display: block

View File

@@ -146,10 +146,11 @@
position: fixed position: fixed
top: -60px top: -60px
z-index: 91 z-index: 91
background: rgba(255, 255, 255, .8) background: rgba(255, 255, 255, .7)
box-shadow: 0 5px 6px -5px alpha($grey, .6) box-shadow: 0 5px 6px -5px alpha($grey, .6)
transition: transform .2s ease-in-out, opacity .2s ease-in-out transition: transform .2s ease-in-out, opacity .2s ease-in-out
will-change: transform will-change: transform
backdrop-filter: blur(7px)
#blog-info #blog-info
color: var(--font-color) color: var(--font-color)
@@ -430,7 +431,33 @@
background-color: lighten($theme-color, 30%) background-color: lighten($theme-color, 30%)
content: '' content: ''
transition: all .3s ease-in-out transition: all .3s ease-in-out
addBorderRadius()
&:hover &:hover
&:after &:after
width: 100% width: 100%
.nav-page-title
position: relative
overflow: hidden
& > :first-child,
& > :last-child
display: inline-block
transition: all .3s ease-in-out
& > :last-child
position: absolute
top: 50%
left: 0
opacity: 0
transform: translateY(-50%) translateY(-10px)
&:hover
& > :last-child
opacity: 1
transform: translateY(-50%) translateY(0)
& > :first-child
opacity: 0
transform: translateY(10px)

Some files were not shown because too many files have changed in this diff Show More