Compare commits

...

60 Commits
5.2.2 ... 5.4.1

Author SHA1 Message Date
Jerry
1de3507843 Merge branch 'dev' 2025-07-04 23:28:11 +08:00
Jerry
d2eacd2d8a feat: 增加導航欄顯示文章標題的選項,並更新懶加載設置 2025-07-04 23:26:29 +08:00
Jerry
5bfc1da03b feat: 更新版本號並升級多個插件的依賴版本,改善功能和相容性 2025-07-04 23:12:47 +08:00
Jerry
21c238e5c1 Merge branch 'dev' of https://github.com/jerryc127/hexo-theme-butterfly into dev 2025-06-25 16:27:06 +08:00
Jerry
60fa703fd3 feat: 增加導覽欄顯示文章標題的選項 feat: 文章頁增加'返回首頁'文字 improvement: 當 per_page 為 0 時,頁面不顯示導覽列 2025-06-25 16:25:46 +08:00
Jerry Wong
7353de6c2e Merge pull request #1695 from DeepChirp/structured_data
feat(structured_data): 为网站首页添加结构化数据
2025-06-25 15:17:08 +08:00
DeepChirp
a9fe9f5332 feat(structured_data): 为网站首页添加结构化数据 2025-06-12 11:43:32 +08:00
Jerry
73de62a6e1 Merge branch 'dev' of https://github.com/jerryc127/hexo-theme-butterfly into dev 2025-05-09 17:57:40 +08:00
Jerry
cf059bd533 fix: 更新 package.json 和 plugins.yml 中的版本號
fix: 修正 truncateContent 函數, 加密文章不显示自动擷取內容
feat: 增加首頁樣式以支持單詞換行
fix: 修正 truncateContent 函數以正確處理自動擷取內容
fix: 修复 card_archives 计数 bug
fix: 修正分頁順序邏輯
2025-05-09 17:52:01 +08:00
Jerry Wong
8cb726ddaa Merge pull request #1683 from SamirLiu127/dev
feat: Add google tag manager
2025-05-09 17:44:32 +08:00
SamirLiu
baee803689 feat: move noscript to additional-js 2025-05-09 13:28:01 +08:00
SamirLiu
023d82820d feat: update merge_config.js 2025-05-09 13:23:17 +08:00
SamirLiu
b7f1610859 update: DataLayer parameter 2025-05-08 14:03:08 +08:00
SamirLiu
5bac44a284 feat: add dataLayer push event pjaxComplete 2025-05-08 13:51:09 +08:00
SamirLiu
9c5294b40c fix: update analytics.pug for google tag manager under pjax 2025-05-07 19:21:39 +08:00
SamirLiu
a7315b6bfc feat: add google tag manager config 2025-05-07 19:17:39 +08:00
SamirLiu
cb3778c686 feat: add google tag manager noscript 2025-05-07 19:17:39 +08:00
Jerry Wong
2ce969023c Merge pull request #1686 from guozhenyi/dev
chore: 补充缺少的依赖库
2025-05-07 18:28:03 +08:00
gzy
7c697c15e8 chore: 补充缺少的依赖库 2025-04-30 16:56:46 +08:00
Jerry
ca030589fb feat: 更新頁腳配置,增加導航欄和版權信息,改進樣式和結構
fix: 修改 getBgPath 函數以使用 this.url_for 獲取圖片的正確路徑
feat: 增加右側配置按鈕的動畫效果
2025-03-23 23:20:12 +08:00
Jerry
46cf1c4e80 Merge branch 'dev' 2025-03-04 16:19:44 +08:00
Jerry Wong
a3f6b625ed Update package.json 2025-03-04 16:14:26 +08:00
Jerry Wong
4a955f0f05 Merge pull request #1659 from LinkinStars/fix-link
fix(subtitle): fix the wrong subtitle link
2025-03-04 16:05:44 +08:00
LinkinStars
525ed7ac82 fix(subtitle): fix the wrong subtitle link 2025-03-03 15:00:27 +08:00
Jerry
b3ba4c9ac4 Merge branch 'dev' 2025-03-02 15:37:03 +08:00
Jerry
628d1bbe52 Merge branch 'dev' of https://github.com/jerryc127/hexo-theme-butterfly into dev 2025-03-02 15:27:34 +08:00
Jerry
ede4f8bfea update 2025-03-02 15:27:16 +08:00
Jerry Wong
feb3346cf6 Merge pull request #1650 from TephrocactusMYC/dev
feat(score tag): support RenderAbc options
2025-03-02 15:13:15 +08:00
Yuchen Mu
576fa5c80e fix: resolve issues from previous commit about feat(score tag) 2025-03-02 10:23:40 +08:00
Yuchen Mu
6019cc8b7c Merge branch 'jerryc127:dev' into dev 2025-03-02 10:12:00 +08:00
Jerry Wong
7f9409553a Merge pull request #1649 from icemyst/patch-1
Update pjax.pug
2025-03-02 01:06:20 +08:00
Yuchen Mu
2de7d34b2b feat(score tag): support RenderAbc options
Modified the score tag to pass custom options to the abcjs RenderAbc interface.
This enhancement enables proper rendering of guitar tablature with custom
settings such as instrument tuning, labels, and formatting options.
2025-02-24 19:03:46 +08:00
冰梦
4b8a610c08 Update pjax.pug
自定义404.html跳转更平滑,使用 window.location.href = e.request.responseURL 加载,自定义404.html加载速度缓慢,可能会出现loading二次加载
2025-02-20 14:10:13 +08:00
Jerry
0dd4645ece Merge branch 'dev' 2025-02-16 21:27:12 +08:00
Jerry
13720bd94d .DS_Store banished! 2025-02-16 21:26:08 +08:00
Jerry
3028b08526 update 2025-02-16 20:53:10 +08:00
Jerry
f605e6dc89 update 2025-02-16 20:38:17 +08:00
Jerry Wong
8ddc25753e Merge pull request #1639 from jerryc127/dev 2025-01-28 18:59:06 +08:00
Jerry Wong
92913a6193 Merge pull request #1635 from cxyfer/dev 2025-01-17 12:16:06 +08:00
cxyfer
1e20234d74 fix: update addtoany item reference for correct sharing functionality 2025-01-17 02:19:21 +08:00
myw
1f3ea55890 fix: 文章頁分頁不顯示的 bug
improvement: 優化部分插件導致文章頁分頁樣式錯亂的 bug
2025-01-13 00:21:47 +08:00
myw
8a0e14b9b8 Merge branch 'dev' 2025-01-12 15:33:52 +08:00
myw
3d4bf30948 fix: 修復隨機封面死循環的問題 2025-01-12 15:32:55 +08:00
myw
0dc6aede35 Merge branch 'dev' 2025-01-11 01:26:37 +08:00
myw
172a8ee846 更新依赖 2025-01-11 01:26:06 +08:00
myw
5331f4ef9e Merge branch 'dev' of https://github.com/jerryc127/hexo-theme-butterfly into dev 2024-12-10 20:41:14 +08:00
myw
0d0001c808 feat: 更新 plugins.yml 中的依賴版本至最新
feat: 優化 aside_archives ,改進性能和可讀性
feat: 改善 inlineImg 和 timeline 標籤的文檔,優化時間線邏輯
feat: 更新 gallery 標籤以支持額外參數,優化圖片顯示邏輯
improvement: 優化隨機封面過濾器邏輯, 避免連續重複
feat: 最新評論限制顯示 1-10 條之間
fix: artalk 的最新評論顯示待定或者封禁的評論的 bug
2024-12-10 20:35:58 +08:00
Jerry Wong
13db106172 Merge pull request #1619 from Henry-ZHR/dev
fix: 代码字体先 fallback 到 monospace 再到中文和 sans-serif
2024-12-08 00:53:59 +08:00
Henry-ZHR
f42048792e fix: 代码字体先 fallback 到 monospace 再到中文和 sans-serif 2024-12-07 20:43:16 +08:00
myw
a1caed17c7 Merge branch 'master' of https://github.com/jerryc127/hexo-theme-butterfly 2024-12-05 15:19:06 +08:00
myw
247c1b664d feat: 更新 lazyload 配置,支持原生 lazyload 功能
feat: 代碼優化
feat: 優化 pageType 邏輯
fix: 修復解密文章後, chartjs 沒有加載的 bug
2024-11-30 13:38:39 +08:00
myw
f7483d59b5 Merge branch 'dev' of https://github.com/jerryc127/hexo-theme-butterfly into dev 2024-11-15 14:56:56 +08:00
myw
b7771e5938 feat: 移除 pangu.js
feat: js 加载完成才显示聊天按钮
2024-11-15 14:53:40 +08:00
myw
a69cb0543f update 2024-11-15 14:52:52 +08:00
Jerry Wong
74c555fb37 Merge pull request #1602 from DeepChirp/structured_data
feat: 添加结构化数据支持
2024-11-11 18:46:57 +08:00
DeepChirp
648ca6eb4f chore: 简化结构化数据配置,移除不必要的嵌套 2024-11-11 18:42:46 +08:00
DeepChirp
e5a52d5621 fix: 移除结构化数据中不合规范的微数据 2024-11-11 16:29:28 +08:00
Jerry Wong
1045f66a51 Merge pull request #1603 from DeepChirp/image
chore: add `avif` to the list of supported image formats
2024-11-11 15:13:02 +08:00
DeepChirp
5338a2be99 chore: add avif to the list of supported image formats 2024-11-07 18:09:01 +08:00
DeepChirp
3ea138178d feat: 添加结构化数据支持 2024-11-07 17:55:33 +08:00
212 changed files with 16456 additions and 16033 deletions

2
.gitignore vendored Normal file
View File

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

View File

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

View File

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

View File

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

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

View File

@@ -5,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

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

View File

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

View File

@@ -1,4 +1,5 @@
- if page.total !== 1
-
var options = { var options = {
prev_text: '<i class="fas fa-chevron-left fa-fw"></i>', prev_text: '<i class="fas fa-chevron-left fa-fw"></i>',
next_text: '<i class="fas fa-chevron-right fa-fw"></i>', next_text: '<i class="fas fa-chevron-right fa-fw"></i>',
@@ -6,8 +7,8 @@
escape: false escape: false
} }
if is_post() if globalPageType === 'post'
- let paginationOrder = theme.post_pagination === 1 ? { prev: page.prev, next: page.next } : { prev: page.next, next: page.prev } - let paginationOrder = theme.post_pagination === 2 ? { prev: page.prev, next: page.next } : { prev: page.next, next: page.prev }
nav#pagination.pagination-post nav#pagination.pagination-post
each direction, key in paginationOrder each direction, key in paginationOrder
@@ -29,9 +30,9 @@ if is_post()
if getPostDesc if getPostDesc
.info-2 .info-2
.info-item-1!=getPostDesc .info-item-1!=getPostDesc
else else
nav#pagination nav#pagination
.pagination .pagination
if is_home() if globalPageType === 'home'
- options.format = 'page/%d/#content-inner' - options.format = 'page/%d/#content-inner'
!=paginator(options) !=paginator(options)

View File

@@ -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

@@ -3,16 +3,16 @@ if theme.menu
#menu-mask #menu-mask
#sidebar-menus #sidebar-menus
.avatar-img.text-center .avatar-img.text-center
img(src=url_for(theme.avatar.img) onerror=`onerror=null;src='${theme.error_img.flink}'` alt="avatar") img(src=url_for(theme.avatar.img) onerror=`this.onerror=null;this.src='${url_for(theme.error_img.flink)}'` alt="avatar")
.site-data.text-center .site-data.text-center
a(href=url_for(config.archive_dir) + '/') a(href=`${url_for(config.archive_dir)}/`)
.headline= _p('aside.articles') .headline= _p('aside.articles')
.length-num= site.posts.length .length-num= site.posts.length
a(href=url_for(config.tag_dir) + '/' ) a(href=`${url_for(config.tag_dir)}/`)
.headline= _p('aside.tags') .headline= _p('aside.tags')
.length-num= site.tags.length .length-num= site.tags.length
a(href=url_for(config.category_dir) + '/') a(href=`${url_for(config.category_dir)}/`)
.headline= _p('aside.categories') .headline= _p('aside.categories')
.length-num= site.categories.length .length-num= site.categories.length
!=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 = () => setTimeout(() => { 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]
}, 100) if (ele.children.length > 0) continue
typeof ABCJS === 'object' ? abcjsFn() // Parse parameters from data-params attribute
: btf.getScript('!{url_for(theme.asset.abcjs_basic_js)}').then(abcjsFn) 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)
}
} }
window.pjax ? abcjsInit() : window.addEventListener('load', abcjsInit) // Merge parsed parameters with the responsive option
btf.addGlobalFn('encrypt', abcjsInit, 'abcjs') // 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)
}
if (typeof ABCJS === "object") {
abcjsFn()
} else {
btf.getScript("!{url_for(theme.asset.abcjs_basic_js)}").then(abcjsFn)
}
}
if (window.pjax) {
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,6 +1,12 @@
//- https://chatra.io/help/api/ //- https://chatra.io/help/api/
script. script.
(() => { (() => {
window.ChatraID = '#{theme.chatra.id}'
window.Chatra = window.Chatra || function() {
(window.Chatra.q = window.Chatra.q || []).push(arguments)
}
btf.getScript('https://call.chatra.io/chatra.js').then(() => {
const isChatBtn = !{theme.chat.rightside_button} const isChatBtn = !{theme.chat.rightside_button}
const isChatHideShow = !{theme.chat.button_hide_show} const isChatHideShow = !{theme.chat.button_hide_show}
@@ -17,26 +23,16 @@ script.
window.ChatraSetup = { startHidden: true } window.ChatraSetup = { startHidden: true }
window.chatBtnFn = () => { window.chatBtnFn = () => document.getElementById('chatra').classList.contains('chatra--expanded') ? close() : open()
document.getElementById('chatra').classList.contains('chatra--expanded') ? close() : open()
} document.getElementById('chat-btn').style.display = 'block'
} else if (isChatHideShow) { } else if (isChatHideShow) {
window.chatBtn = { window.chatBtn = {
hide: () => Chatra('hide'), hide: () => Chatra('hide'),
show: () => Chatra('show') show: () => Chatra('show')
} }
} }
})
(function(d, w, c) {
w.ChatraID = '#{theme.chatra.id}'
var s = d.createElement('script')
w[c] = w[c] || function() {
(w[c].q = w[c].q || []).push(arguments)
}
s.async = true
s.src = 'https://call.chatra.io/chatra.js'
if (d.head) d.head.appendChild(s)
})(document, window, 'Chatra')
})() })()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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

@@ -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
const sub = !{JSON.stringify(subContent)} typedJSFn.processSubtitle(data.hitokoto, [from])
sub.unshift(data.hitokoto, from) })
typedJSFn.init(sub) .catch(err => {
} else { console.error('Failed to get the Hitokoto API:', err)
document.getElementById('subtitle').textContent = data.hitokoto typedJSFn.processSubtitle(!{JSON.stringify(subContent)})
}
}) })
} }
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)
typedJSFn.init(sub)
} else { } else {
document.getElementById('subtitle').textContent = result.data.content throw new Error('Failed to parse the return value of Jinrishici API')
} }
}) })
}) })
.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

@@ -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

@@ -33,4 +33,3 @@ block content
if page.comments !== false && theme.comments.use if page.comments !== false && theme.comments.use
- var commentsJsLoad = true - var commentsJsLoad = true
!=partial('includes/third-party/comments/index', {}, {cache: true}) !=partial('includes/third-party/comments/index', {}, {cache: true})

View File

@@ -1,6 +1,6 @@
{ {
"name": "hexo-theme-butterfly", "name": "hexo-theme-butterfly",
"version": "5.2.2", "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,7 +1,7 @@
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.4 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
@@ -9,7 +9,7 @@ activate_power_mode:
algolia_search: algolia_search:
name: algoliasearch name: algoliasearch
file: dist/lite/builds/browser.umd.js file: dist/lite/builds/browser.umd.js
version: 5.12.0 version: 5.30.0
aplayer_css: aplayer_css:
name: aplayer name: aplayer
file: dist/APlayer.min.css file: dist/APlayer.min.css
@@ -45,7 +45,7 @@ canvas_ribbon:
chartjs: chartjs:
name: chart.js name: chart.js
file: dist/chart.umd.js file: dist/chart.umd.js
version: 4.4.6 version: 4.5.0
clickShowText: clickShowText:
name: butterfly-extsrc name: butterfly-extsrc
file: dist/click-show-text.min.js file: dist/click-show-text.min.js
@@ -57,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.3 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.3 version: 3.9.0
egjs_infinitegrid: egjs_infinitegrid:
name: '@egjs/infinitegrid' name: '@egjs/infinitegrid'
other_name: egjs-infinitegrid other_name: egjs-infinitegrid
@@ -80,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
@@ -95,7 +95,7 @@ fontawesome:
name: '@fortawesome/fontawesome-free' name: '@fortawesome/fontawesome-free'
file: css/all.min.css file: css/all.min.css
other_name: font-awesome other_name: font-awesome
version: 6.6.0 version: 6.7.2
gitalk: gitalk:
name: gitalk name: gitalk
file: dist/gitalk.min.js file: dist/gitalk.min.js
@@ -111,17 +111,17 @@ instantpage:
instantsearch: instantsearch:
name: instantsearch.js name: instantsearch.js
file: dist/instantsearch.production.min.js file: dist/instantsearch.production.min.js
version: 4.75.3 version: 4.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
@@ -137,7 +137,7 @@ medium_zoom:
mermaid: mermaid:
name: mermaid name: mermaid
file: dist/mermaid.min.js file: dist/mermaid.min.js
version: 11.4.0 version: 11.8.0
meting_js: meting_js:
name: butterfly-extsrc name: butterfly-extsrc
file: metingjs/dist/Meting.min.js file: metingjs/dist/Meting.min.js
@@ -152,10 +152,6 @@ pace_js:
other_name: pace other_name: pace
file: pace.min.js file: pace.min.js
version: 1.2.4 version: 1.2.4
pangu:
name: pangu
file: dist/browser/pangu.min.js
version: 4.0.7
pjax: pjax:
name: pjax name: pjax
file: pjax.min.js file: pjax.min.js
@@ -164,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
@@ -194,7 +190,7 @@ snackbar_css:
twikoo: twikoo:
name: twikoo name: twikoo
file: dist/twikoo.all.min.js file: dist/twikoo.all.min.js
version: 1.6.39 version: 1.6.44
typed: typed:
name: typed.js name: typed.js
file: dist/typed.umd.js file: dist/typed.umd.js
@@ -202,14 +198,14 @@ typed:
valine: valine:
name: valine name: valine
file: dist/Valine.min.js file: dist/Valine.min.js
version: 1.5.2 version: 1.5.3
waline_css: waline_css:
name: '@waline/client' name: '@waline/client'
file: dist/waline.css file: dist/waline.css
other_name: waline other_name: waline
version: 3.3.2 version: 3.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

@@ -3,13 +3,14 @@
const { stripHTML, truncate } = require('hexo-util') const { stripHTML, truncate } = require('hexo-util')
// Truncates the given content to a specified length, removing HTML tags and replacing newlines with spaces. // Truncates the given content to a specified length, removing HTML tags and replacing newlines with spaces.
const truncateContent = (content, length) => { const truncateContent = (content, length, encrypt = false) => {
return truncate(stripHTML(content), { length, separator: ' ' }).replace(/\n/g, ' ') if (!content || encrypt) return ''
return truncate(stripHTML(content).replace(/\n/g, ' '), { length })
} }
// Generates a post description based on the provided data and theme configuration. // Generates a post description based on the provided data and theme configuration.
const postDesc = (data, hexo) => { const postDesc = (data, hexo) => {
const { description, content, postDesc } = data const { description, content, postDesc, encrypt } = data
if (postDesc) return postDesc if (postDesc) return postDesc
@@ -23,10 +24,10 @@ const postDesc = (data, hexo) => {
result = description result = description
break break
case 2: case 2:
result = description || truncateContent(content, length) result = description || truncateContent(content, length, encrypt)
break break
default: default:
result = truncateContent(content, length) result = truncateContent(content, length, encrypt)
} }
data.postDesc = result data.postDesc = result

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

View File

@@ -2,38 +2,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) // Destructure and set default options with object destructuring
const type = options.type || 'monthly' const {
const format = options.format || (type === 'monthly' ? 'MMMM YYYY' : 'YYYY') type = 'monthly',
const showCount = Object.prototype.hasOwnProperty.call(options, 'show_count') ? options.show_count : true format = type === 'monthly' ? 'MMMM YYYY' : 'YYYY',
const order = options.order || -1 show_count: showCount = true,
const limit = options.limit order = -1,
const compareFunc = type === 'monthly' limit,
transform
} = options
// Optimize locale handling
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, monthA, yearB, monthB) => yearA === yearB && monthA === monthB
: (yearA, monthA, yearB, monthB) => yearA === yearB : (yearA, yearB) => yearA === yearB
const posts = site.posts.sort('date', order) // Early return if no posts
if (!posts.length) return '' if (!site.posts.length) return ''
const data = [] // Use reduce for more efficient data processing
posts.forEach(post => { const data = site.posts.sort('date', order).reduce((acc, post) => {
let date = post.date.clone() let date = post.date.clone()
if (timezone) date = date.tz(timezone) if (timezone) date = date.tz(timezone)
const year = date.year() const year = date.year()
const month = date.month() + 1 const month = date.month() + 1
if (!data.length || !compareFunc(data[data.length - 1].year, data[data.length - 1].month, year, month)) {
if (lang) date = date.locale(lang) if (lang) date = date.locale(lang)
data.push({ name: date.format(format), year, month, count: 1 })
} else {
data[data.length - 1].count++
}
})
const link = item => { // Find or create archive entry
const lastEntry = acc[acc.length - 1]
if (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">
${limitedData
.map(
item => `
<li class="card-archive-list-item"> <li class="card-archive-list-item">
<a class="card-archive-list-link" href="${link(item)}"> <a class="card-archive-list-link" href="${createArchiveLink(item)}">
<span class="card-archive-list-date">${options.transform ? options.transform(item.name) : item.name}</span> <span class="card-archive-list-date">
${transform ? transform(item.name) : item.name}
</span>
${showCount ? `<span class="card-archive-list-count">${item.count}</span>` : ''} ${showCount ? `<span class="card-archive-list-count">${item.count}</span>` : ''}
</a> </a>
</li> </li>
` `
} )
.join('')}
</ul>
`
result += '</ul>' return archiveHeader + archiveList
return result
}) })
const toMomentLocale = function (lang) { // Improved locale conversion function
if (!lang || lang === 'en' || lang === 'default') { const toMomentLocale = lang => {
return 'en' if (!lang || ['en', 'default'].includes(lang)) return 'en'
}
return lang.toLowerCase().replace('_', '-') return lang.toLowerCase().replace('_', '-')
} }

View File

@@ -81,7 +81,7 @@ 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
@@ -91,7 +91,7 @@ hexo.extend.helper.register('getBgPath', path => {
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};`
} }
@@ -131,3 +131,22 @@ hexo.extend.helper.register('shuoshuoFN', (data, page) => {
return finalResult return finalResult
}) })
hexo.extend.helper.register('getPageType', (page, isHome) => {
const { layout, tag, category, type, archive } = page
if (layout) return layout
if (tag) return 'tag'
if (category) return 'category'
if (archive) return 'archive'
if (type) {
if (type === 'tags' || type === 'categories') return type
else return 'page'
}
if (isHome) return 'home'
return 'post'
})
hexo.extend.helper.register('getVersion', () => {
const { version } = require('../../package.json')
return { hexo: hexo.version, theme: version }
})

View File

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

View File

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

View File

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

View File

@@ -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

@@ -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

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

@@ -436,3 +436,28 @@
&: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)

View File

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

View File

@@ -10,8 +10,8 @@ if hexo-config('darkmode.enable') || hexo-config('display_mode') == 'dark'
--preloader-bg: darken(#121212, 2) --preloader-bg: darken(#121212, 2)
--preloader-color: alpha(#FFFFFF, .7) --preloader-color: alpha(#FFFFFF, .7)
--tab-border-color: #2c2c2c --tab-border-color: #2c2c2c
--tab-botton-bg: #2c2c2c --tab-button-bg: #2c2c2c
--tab-botton-color: alpha(#FFFFFF, .7) --tab-button-color: alpha(#FFFFFF, .7)
--tab-button-hover-bg: lighten(#121212, 15) --tab-button-hover-bg: lighten(#121212, 15)
--tab-button-active-bg: #121212 --tab-button-active-bg: #121212
--card-bg: #121212 --card-bg: #121212

View File

@@ -172,3 +172,4 @@ $indexEnable = hexo-config('cover.index_enable')
& > .content & > .content
@extend .limit-more-line @extend .limit-more-line
-webkit-line-clamp: 2 -webkit-line-clamp: 2
word-break: break-word

View File

@@ -14,14 +14,14 @@
flex-wrap: wrap flex-wrap: wrap
margin: 0 margin: 0
padding: 0 padding: 0
background: var(--tab-botton-bg) background: var(--tab-button-bg)
> .tab > .tab
flex-grow: 1 flex-grow: 1
padding: 8px 18px padding: 8px 18px
border-top: 2px solid var(--tab-border-color) border-top: 2px solid var(--tab-border-color)
background: var(--tab-botton-bg) background: var(--tab-button-bg)
color: var(--tab-botton-color) color: var(--tab-button-color)
line-height: 2 line-height: 2
transition: all .4s transition: all .4s

View File

@@ -14,18 +14,18 @@ $code-background = $themeColorEnable && hexo-config('theme_color.code_background
$theme-toc-color = $themeColorEnable && hexo-config('theme_color.toc_color') ? convert(hexo-config('theme_color.toc_color')) : $strong-cyan $theme-toc-color = $themeColorEnable && hexo-config('theme_color.toc_color') ? convert(hexo-config('theme_color.toc_color')) : $strong-cyan
// font // font
$chinseFont = $language == 'zh-CN' ? 'Microsoft YaHei' : 'Microsoft JhengHei' $chineseFont = $language == 'zh-CN' ? 'Microsoft YaHei' : 'Microsoft JhengHei'
$dafault-font-family = -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue', Lato, Roboto, 'PingFang SC', $chinseFont, sans-serif $default-font-family = -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue', Lato, Roboto, 'PingFang SC', $chineseFont, sans-serif
$dafault-code-font = consolas, Menlo, 'PingFang SC', $chinseFont, sans-serif $default-code-font = consolas, Menlo, monospace, 'PingFang SC', $chineseFont, sans-serif
$font-family = hexo-config('font.font_family') ? unquote(hexo-config('font.font_family')) : $dafault-font-family $font-family = hexo-config('font.font_family') ? unquote(hexo-config('font.font_family')) : $default-font-family
$code-font-family = hexo-config('font.code_font_family') ? unquote(hexo-config('font.code_font_family')) : $dafault-code-font $code-font-family = hexo-config('font.code_font_family') ? unquote(hexo-config('font.code_font_family')) : $default-code-font
$site-name-font = hexo-config('blog_title_font.font_family') && unquote(hexo-config('blog_title_font.font_family')) $site-name-font = hexo-config('blog_title_font.font_family') && unquote(hexo-config('blog_title_font.font_family'))
// hr // hr
$hrEnable = hexo-config('hr_icon') && hexo-config('hr_icon.enable') $hrEnable = hexo-config('hr_icon') && hexo-config('hr_icon.enable')
$hr-icon = $hrEnable && hexo-config('hr_icon.icon') ? hexo-config('hr_icon.icon') : '\f0c4' $hr-icon = $hrEnable && hexo-config('hr_icon.icon') ? hexo-config('hr_icon.icon') : '\f0c4'
$hr-icon-top = $hrEnable && hexo-config('hr_icon.icon_top') ? convert(hexo-config('hr_icon.icon_top')) : -10px $hr-icon-top = $hrEnable && hexo-config('hr_icon.icon_top') ? convert(hexo-config('hr_icon.icon_top')) : -10px
// page beatutify // page beautify
$beautifyEnable = hexo-config('beautify.enable') $beautifyEnable = hexo-config('beautify.enable')
$title-prefix-icon = $beautifyEnable && hexo-config('beautify.title_prefix_icon') ? hexo-config('beautify.title_prefix_icon') : '\f0c1' $title-prefix-icon = $beautifyEnable && hexo-config('beautify.title_prefix_icon') ? hexo-config('beautify.title_prefix_icon') : '\f0c1'
$title-prefix-icon-color = $beautifyEnable && hexo-config('beautify.title_prefix_icon_color') ? convert(hexo-config('beautify.title_prefix_icon_color')) : $light-red $title-prefix-icon-color = $beautifyEnable && hexo-config('beautify.title_prefix_icon_color') ? convert(hexo-config('beautify.title_prefix_icon_color')) : $light-red
@@ -175,8 +175,8 @@ $tagsP-purple-color = #6f42c1
$tagsP-green-color = #5cb85c $tagsP-green-color = #5cb85c
// Tag Plugins - Tab // Tag Plugins - Tab
$tab-border-color = #f0f0f0 $tab-border-color = #f0f0f0
$tab-botton-bg = #f0f0f0 $tab-button-bg = #f0f0f0
$tab-botton-color = $font-color $tab-button-color = $font-color
$tab-button-hover-bg = darken($tab-border-color, 8) $tab-button-hover-bg = darken($tab-border-color, 8)
$tab-active-border-color = $theme-color $tab-active-border-color = $theme-color
$tab-button-active-bg = $card-bg $tab-button-active-bg = $card-bg

View File

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

View File

@@ -186,6 +186,7 @@
if (service === 'medium_zoom') { if (service === 'medium_zoom') {
mediumZoom(ele, { background: 'var(--zoom-bg)' }) mediumZoom(ele, { background: 'var(--zoom-bg)' })
return
} }
if (service === 'fancybox') { if (service === 'fancybox') {
@@ -198,7 +199,9 @@
}) })
if (!window.fancyboxRun) { if (!window.fancyboxRun) {
Fancybox.bind('[data-fancybox]', { let options = ''
if (Fancybox.version < '6') {
options = {
Hash: false, Hash: false,
Thumbs: { Thumbs: {
showOnStart: false showOnStart: false
@@ -226,7 +229,41 @@
right: ['slideshow', 'thumbs', 'close'] right: ['slideshow', 'thumbs', 'close']
} }
} }
}) }
} else {
options = {
Hash: false,
Carousel: {
transition: 'slide',
Thumbs: {
showOnStart: false
},
Toolbar: {
display: {
left: ['counter'],
middle: [
'zoomIn',
'zoomOut',
'toggle1to1',
'rotateCCW',
'rotateCW',
'flipX',
'flipY',
"reset"
],
right: ['autoplay', 'thumbs', 'close']
}
},
Zoomable: {
Panzoom: {
maxScale: 4
}
}
}
}
}
Fancybox.bind('[data-fancybox]', options)
window.fancyboxRun = true window.fancyboxRun = true
} }
} }