Compare commits

...

62 Commits
5.2.2 ... 5.4.2

Author SHA1 Message Date
Jerry
736cd75cda Merge branch 'dev' 2025-07-09 13:25:05 +08:00
Jerry
a61e216452 fix: 修正 abcjs 報錯的 bug 2025-07-09 13:24:42 +08:00
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 16458 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] Justified Gallery
- [x] Lazyload images
- [x] Instantpage/Pangu/Snackbar notification toast/PWA......
- [x] Instantpage/Snackbar notification toast/PWA......
## ✨ Contributors

View File

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

View File

@@ -13,6 +13,7 @@ nav:
# Navigation bar logo image
logo:
display_title: true
display_post_title: true
# Whether to fix navigation bar
fixed: false
@@ -158,7 +159,7 @@ subtitle:
# Choose: false/1/2/3
# false - disable the function
# 1 - hitokoto.cn
# 2 - yijuzhan.com
# 2 - https://api.aa1.cn/doc/yiyan.html
# 3 - jinrishici.com
source: false
# If you close the typewriter effect, the subtitle will only show the first line of sub
@@ -254,12 +255,15 @@ noticeOutdate:
# Footer Settings
# --------------------------------------
footer:
nav:
owner:
enable: true
since: 2019
custom_text:
since: 2025
# Copyright of theme and framework
copyright: true
copyright:
enable: true
version: true
custom_text:
# --------------------------------------
# Aside Settings
@@ -399,6 +403,9 @@ rightside_item_order:
# Default: toc,chat,comment
show:
# Animation for the bottom right config button
rightside_config_animation: true
# --------------------------------------
# Global Settings
# --------------------------------------
@@ -696,6 +703,12 @@ umami_analytics:
# Umami Cloud (API key) / self-hosted Umami (token)
token:
# https://www.googletagmanager.com/
google_tag_manager:
tag_id:
# optional
domain:
# --------------------------------------
# Advertisement
# --------------------------------------
@@ -983,17 +996,12 @@ snackbar:
# https://instant.page/
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
# https://github.com/verlok/vanilla-lazyload
lazyload:
enable: false
# Use browser's native lazyload instead of vanilla-lazyload
native: false
# Specify the field to use lazyload (site or post)
field: site
placeholder:
@@ -1023,6 +1031,10 @@ Open_Graph_meta:
# fb_admins:
# 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
css_prefix: true
@@ -1092,7 +1104,6 @@ CDN:
# medium_zoom:
# mermaid:
# meting_js:
# pangu:
# prismjs_autoloader:
# prismjs_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.'
recommend: Related Articles
edit: Edit
back_to_home: Back to Home
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.'
recommend: Related Articles
edit: Edit
back_to_home: Back to Home
search:
title: Search

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -11,15 +11,12 @@ div
if theme.instantpage
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))
if theme.snackbar.enable
script(src=url_for(theme.asset.snackbar))
if theme.pangu.enable
!= partial("includes/third-party/pangu.pug", {}, { cache: true })
.js-pjax
if needLoadCountJs
!= partial("includes/third-party/card-post-count/index", {}, { cache: true })
@@ -36,7 +33,7 @@ div
!= partial("includes/third-party/prismjs", {}, { cache: true })
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 })
!= 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')
!= 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
if theme.footer.owner.enable
- const { nav, owner, copyright, custom_text } = theme.footer
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 sinceYear = theme.footer.owner.since
.copyright
- const sinceYear = owner.since
span.copyright
if sinceYear && sinceYear != currentYear
!= `&copy;${sinceYear} - ${currentYear} By ${config.author}`
else
!= `&copy;${currentYear} By ${config.author}`
if theme.footer.copyright
.framework-info
if copyright.enable
- const v = copyright.version ? getVersion() : false
span.framework-info
if owner.enable && nav
span.footer-separator |
span= _p('footer.framework') + ' '
a(href='https://hexo.io')= 'Hexo'
a(href='https://hexo.io')= `Hexo${ v ? ' ' + v.hexo : '' }`
span.footer-separator |
span= _p('footer.theme') + ' '
a(href='https://github.com/jerryc127/hexo-theme-butterfly')= 'Butterfly'
a(href='https://github.com/jerryc127/hexo-theme-butterfly')= `Butterfly${ v ? ' ' + v.theme : '' }`
if theme.footer.custom_text
.footer_custom_text!= theme.footer.custom_text

View File

@@ -1,12 +1,18 @@
- var pageTitle
- is_archive() ? page.title = findArchivesTitle(page, theme.menu, date) : ''
- if (is_tag()) pageTitle = _p('page.tag') + ': ' + page.tag
- else if (is_category()) pageTitle = _p('page.category') + ': ' + page.category
- else if (is_current('/404.html', [strict])) pageTitle = _p('error404')
- else pageTitle = page.title || config.title || ''
- globalPageType === 'archive' ? page.title = findArchivesTitle(page, theme.menu, date) : ''
case globalPageType
when 'tag'
- pageTitle = _p('page.tag') + ': ' + page.tag
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 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 pageCopyright = config.copyright || config.author
- 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
include ./head/Open_Graph.pug
//- Structured Data
include ./head/structured_data.pug
!=favicon_tag(theme.favicon || config.favicon)
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
let ogOption = Object.assign({
type: is_post() ? 'article' : 'website',
type: globalPageType === 'post' ? 'article' : 'website',
image: coverVal ? full_url_for(coverVal) : '',
fb_admins: theme.facebook_comments.user_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;
y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y);
})(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")}'
},
isPhotoFigcaption: !{theme.photofigcaption},
islazyload: !{theme.lazyload.enable},
islazyloadPlugin: !{theme.lazyload.enable && !theme.lazyload.native},
isAnchor: !{theme.anchor.auto_update || false},
percent: {
toc: !{theme.toc.scroll_percent},

View File

@@ -9,8 +9,8 @@
var showToc = false
if (theme.aside.enable && page.aside !== false) {
let tocEnable = false
if (is_post() && theme.toc.post) tocEnable = true
else if (is_page() && theme.toc.page) tocEnable = true
if (globalPageType === 'post' && theme.toc.post) tocEnable = true
else if (globalPageType === 'page' && theme.toc.page) tocEnable = true
const pageToc = typeof page.toc === 'boolean' ? page.toc : tocEnable
showToc = pageToc && (toc(page.content) !== '' || page.encrypt === true)
}
@@ -19,9 +19,7 @@
script#config-diff.
var GLOBAL_CONFIG_SITE = {
title: '!{titleVal}',
isPost: !{is_post()},
isHome: !{is_home()},
isHighlightShrink: !{isHighlightShrink},
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 = ''
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
else if is_page()
when 'page'
- top_img = page.top_img || theme.default_top_img
else if is_tag()
- top_img = theme.tag_per_img && theme.tag_per_img[page.tag]
- top_img = top_img || returnTopImg(theme.tag_img)
else if is_category()
- top_img = theme.category_per_img && theme.category_per_img[page.category]
- top_img = top_img || returnTopImg(theme.category_img)
else if is_home()
when 'tag'
- top_img = theme.tag_per_img && theme.tag_per_img[page.tag] || returnTopImg(theme.tag_img)
when 'category'
- top_img = theme.category_per_img && theme.category_per_img[page.category] || returnTopImg(theme.category_img)
when 'home'
- top_img = returnTopImg(theme.index_img)
else if is_archive()
when 'archive'
- top_img = returnTopImg(theme.archive_img)
else
default
- top_img = page.top_img || theme.default_top_img
if top_img !== false
- 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)
include ./nav.pug
if top_img !== false
if is_post()
if globalPageType === 'post'
include ./post-info.pug
else if is_home()
else if globalPageType === 'home'
#site-info
h1#site-title=config.title
if theme.subtitle.enable
@@ -48,6 +47,6 @@ header#page-header(class=`${headerClassName + isFixedClass}` style=bg_img)
#page-site-info
h1#site-title=page.title || page.tag || page.category
else
//- improvement seo
if !is_post()
//- improve seo
if globalPageType !== 'post'
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')
if theme.nav.display_title
span.site-name=config.title
if is_post()
if globalPageType === 'post' && theme.nav.display_post_title
a.nav-page-title(href=url_for('/'))
span.site-name=(page.title || config.title)
span.site-name
i.fa-solid.fa-circle-arrow-left
span= ' ' + _p('post.back_to_home')
#menus
if theme.search.use
#search-button

View File

@@ -1,7 +1,8 @@
- var globalPageType = getPageType(page, is_home)
- 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 pageType = is_post() ? 'post' : 'page'
- var pageType = globalPageType === 'post' ? 'post' : 'page'
- pageType = page.type ? pageType + ' type-' + page.type : pageType
doctype html

View File

@@ -20,7 +20,7 @@ mixin indexPostUI()
div.post-bg(style=`background: ${post_cover}`)
.recent-post-info(class=no_cover)
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
= title
.article-meta-wrap

View File

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

View File

@@ -1,9 +1,10 @@
- const { readmode, translate, darkmode, aside, chat } = theme
mixin rightsideItem(array)
each item in array
case item
when 'readmode'
if is_post() && readmode
if globalPageType === 'post' && readmode
button#readmode(type="button" title=_p('rightside.readmode_title'))
i.fas.fa-book-open
when 'translate'
@@ -23,37 +24,29 @@ mixin rightsideItem(array)
i.fas.fa-list-ul
when 'chat'
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
when 'comment'
if commentsJsLoad
a#to_comment(href="#post-comment" title=_p("rightside.scroll_to_comment"))
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
- 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
if hideArray
if hideArray.length
+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)
button#go-up(type="button" title=_p("rightside.back_to_top"))

View File

@@ -3,16 +3,16 @@ if theme.menu
#menu-mask
#sidebar-menus
.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
a(href=url_for(config.archive_dir) + '/')
a(href=`${url_for(config.archive_dir)}/`)
.headline= _p('aside.articles')
.length-num= site.posts.length
a(href=url_for(config.tag_dir) + '/' )
a(href=`${url_for(config.tag_dir)}/`)
.headline= _p('aside.tags')
.length-num= site.tags.length
a(href=url_for(config.category_dir) + '/')
a(href=`${url_for(config.category_dir)}/`)
.headline= _p('aside.categories')
.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.
(() => {
const abcjsInit = () => {
const abcjsFn = () => setTimeout(() => {
document.querySelectorAll(".abc-music-sheet").forEach(ele => {
if (ele.children.length > 0) return
ABCJS.renderAbc(ele, ele.innerHTML, {responsive: 'resize'})
})
}, 100)
const abcjsFn = () => {
setTimeout(() => {
const sheets = document.querySelectorAll(".abc-music-sheet")
for (let i = 0; i < sheets.length; i++) {
const ele = sheets[i]
if (ele.children.length > 0) continue
typeof ABCJS === 'object' ? abcjsFn()
: btf.getScript('!{url_for(theme.asset.abcjs_basic_js)}').then(abcjsFn)
// Parse parameters from data-params attribute
let params = {}
const dp = ele.getAttribute("data-params")
if (dp) {
try {
params = JSON.parse(dp)
} catch (e) {
console.error("Failed to parse data-params:", e)
}
}
window.pjax ? abcjsInit() : window.addEventListener('load', abcjsInit)
btf.addGlobalFn('encrypt', abcjsInit, 'abcjs')
// Merge parsed parameters with the responsive option
// Ensures params content appears before responsive
const options = { ...params, responsive: "resize" }
// Render the music score using ABCJS.renderAbc
ABCJS.renderAbc(ele, ele.innerHTML, options)
}
}, 100)
}
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.per_page
if is_post() || is_page()
include ./abcjs.pug
else if page.abcjs
if theme.abcjs.per_page && (['post','page'].includes(globalPageType)) || page.abcjs
include ./abcjs.pug

View File

@@ -18,7 +18,9 @@ script.
includeReply: false
}).then(function (res) {
document.querySelectorAll('#recent-posts .twikoo-count').forEach((item,index) => {
if (res[index]) {
item.textContent = res[index].count
}
})
}).catch(function (err) {
console.log(err)

View File

@@ -1,6 +1,12 @@
//- https://chatra.io/help/api/
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 isChatHideShow = !{theme.chat.button_hide_show}
@@ -17,26 +23,16 @@ script.
window.ChatraSetup = { startHidden: true }
window.chatBtnFn = () => {
document.getElementById('chatra').classList.contains('chatra--expanded') ? close() : open()
}
window.chatBtnFn = () => document.getElementById('chatra').classList.contains('chatra--expanded') ? close() : open()
document.getElementById('chat-btn').style.display = 'block'
} else if (isChatHideShow) {
window.chatBtn = {
hide: () => Chatra('hide'),
show: () => Chatra('show')
}
}
(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.
(() => {
window.$crisp = [];
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])
window.$crisp = ['safe', true]
window.CRISP_WEBSITE_ID = "!{theme.crisp.website_id}"
btf.getScript('https://client.crisp.chat/l.js').then(() => {
const isChatBtn = !{theme.chat.rightside_button}
const isChatHideShow = !{theme.chat.button_hide_show}
@@ -28,10 +21,12 @@ script.
window.chatBtnFn = () => $crisp.is("chat:visible") ? close() : open()
document.getElementById('chat-btn').style.display = 'block'
} else if (isChatHideShow) {
window.chatBtn = {
hide: () => $crisp.push(["do", "chat:hide"]),
show: () => $crisp.push(["do", "chat:show"])
}
}
})
})()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,8 +2,8 @@
script.
(() => {
const isShuoshuo = GLOBAL_CONFIG_SITE.isShuoshuo
const option = !{JSON.stringify(option)}
const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'
const options = !{JSON.stringify(option)}
const loadScript = src => {
const script = document.createElement('script')
@@ -42,8 +42,8 @@ script.
host: '!{host}',
site_id: '!{siteId}',
theme: document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'light',
...option,
url: isShuoshuo ? window.location.origin + path : (option && option.url) || window.location.origin + window.location.pathname
...options,
url: isShuoshuo ? window.location.origin + path : (options && options.url) || window.location.origin + window.location.pathname
}
if (window.REMARK42) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,10 +1,10 @@
case theme.math.use
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
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
if theme.mermaid.enable

View File

@@ -34,7 +34,7 @@ script.
const searchParams = new URLSearchParams({
'site_name': '!{site}',
'limit': '!{theme.aside.card_newest_comments.limit}',
'limit': '!{newestCommentsLimit * 2}', // Fetch more comments to filter pending comments
})
const getComment = async (ele) => {
@@ -42,7 +42,10 @@ script.
const res = await fetch(`!{server}/api/v2/stats/latest_comments?${searchParams}`)
const result = await res.json()
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}` : ''
return {
'avatar': avatar,

View File

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

View File

@@ -6,7 +6,7 @@ script.
const { changeContent, generateHtml, run } = window.newestComments
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(data => {
const disqusArray = data.response.map(item => {

View File

@@ -32,7 +32,7 @@ script.
}
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": {
Accept: 'application/vnd.github.v3.html+json'
}

View File

@@ -1,7 +1,11 @@
- let { use } = theme.comments
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]
when 'Valine'
include ./valine.pug

View File

@@ -7,7 +7,7 @@ script.
const { changeContent, generateHtml, run } = window.newestComments
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(data => {
const remark42 = data.map(e => {

View File

@@ -10,7 +10,7 @@ script.
twikoo.getRecentComments({
envId: '!{theme.twikoo.envId}',
region: '!{theme.twikoo.region}',
pageSize: !{theme.aside.card_newest_comments.limit},
pageSize: !{newestCommentsLimit},
includeReply: true
}).then(res => {
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(data => {
const valineArray = data.results.map(e => {

View File

@@ -9,7 +9,7 @@ script.
const getComment = async (ele) => {
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 walineArray = result.data.map(e => {
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
if choose
if theme.Open_Graph_meta.enable && (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')
if choose.includes('Livere') || choose.includes('Utterances') || choose.includes('Giscus')
- 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.
@@ -57,7 +59,10 @@ script.
document.addEventListener('pjax:error', e => {
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
.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
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()
script.
@@ -22,6 +22,26 @@ script.
} else {
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')
@@ -33,14 +53,12 @@ case source
fetch('https://v1.hitokoto.cn')
.then(response => response.json())
.then(data => {
if (!{effect}) {
const from = '出自 ' + data.from
const sub = !{JSON.stringify(subContent)}
sub.unshift(data.hitokoto, from)
typedJSFn.init(sub)
} else {
document.getElementById('subtitle').textContent = data.hitokoto
}
typedJSFn.processSubtitle(data.hitokoto, [from])
})
.catch(err => {
console.error('Failed to get the Hitokoto API:', err)
typedJSFn.processSubtitle(!{JSON.stringify(subContent)})
})
}
typedJSFn.run(subtitleType)
@@ -48,46 +66,48 @@ case source
when 2
script.
function subtitleType () {
btf.getScript('https://yijuzhan.com/api/word.php?m=js').then(() => {
const con = str[0]
if (!{effect}) {
const from = '出自 ' + str[1]
const sub = !{JSON.stringify(subContent)}
sub.unshift(con, from)
typedJSFn.init(sub)
fetch('https://v.api.aa1.cn/api/yiyan/index.php')
.then(response => response.text())
.then(data => {
const reg = /<p>(.*?)<\/p>/g
const result = reg.exec(data)
if (result && result[1]) {
typedJSFn.processSubtitle(result[1])
} 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)
when 3
script.
function subtitleType () {
btf.getScript('https://sdk.jinrishici.com/v2/browser/jinrishici.js').then(() => {
jinrishici.load(result =>{
if (!{effect}) {
const sub = !{JSON.stringify(subContent)}
const content = result.data.content
sub.unshift(content)
typedJSFn.init(sub)
btf.getScript('https://sdk.jinrishici.com/v2/browser/jinrishici.js')
.then(() => {
jinrishici.load(result => {
if (result && result.data && result.data.content) {
typedJSFn.processSubtitle(result.data.content)
} 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)
default
- subContent = subContent.length ? subContent : new Array(config.subtitle)
if subContent.length > 0
script.
function subtitleType () {
if (!{effect}) {
typedJSFn.init(!{JSON.stringify(subContent)})
} else {
document.getElementById("subtitle").textContent = !{JSON.stringify(subContent[0])}
}
typedJSFn.processSubtitle(!{JSON.stringify(subContent)})
}
typedJSFn.run(subtitleType)

View File

@@ -35,7 +35,7 @@ script.
const insertData = async () => {
try {
if (GLOBAL_CONFIG_SITE.isPost && config.page_pv) {
if (GLOBAL_CONFIG_SITE.pageType === 'post' && config.page_pv) {
const pagePV = document.getElementById('umamiPV')
if (pagePV) {
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})
else
.toc-content(class=tocExpandClass)!=toc(page.content, {list_number: tocNumber})

View File

@@ -6,40 +6,39 @@ if theme.aside.card_webinfo.enable
.webinfo
if theme.aside.card_webinfo.post_count
.webinfo-item
.item-name= _p('aside.card_webinfo.article_name') + " :"
.item-name= `${_p('aside.card_webinfo.article_name')} :`
.item-count= site.posts.length
if theme.aside.card_webinfo.runtime_date
.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))
i.fa-solid.fa-spinner.fa-spin
if theme.wordcount.enable && theme.wordcount.total_wordcount
.webinfo-item
.item-name=_p('aside.card_webinfo.site_wordcount') + " :"
.item-count=totalcount(site)
.item-name= `${_p('aside.card_webinfo.site_wordcount')} :`
.item-count= totalcount(site)
if theme.umami_analytics.enable && theme.umami_analytics.UV_PV.site_uv
.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
i.fa-solid.fa-spinner.fa-spin
else if theme.busuanzi.site_uv
.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
i.fa-solid.fa-spinner.fa-spin
if theme.umami_analytics.enable && theme.umami_analytics.UV_PV.site_pv
.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
i.fa-solid.fa-spinner.fa-spin
else if theme.busuanzi.site_pv
.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
i.fa-solid.fa-spinner.fa-spin
if theme.aside.card_webinfo.last_push_date
.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()))
i.fa-solid.fa-spinner.fa-spin

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "hexo-theme-butterfly",
"version": "5.2.2",
"version": "5.4.2",
"description": "A Simple and Card UI Design theme for Hexo",
"main": "package.json",
"scripts": {
@@ -15,16 +15,18 @@
"hexo-theme-butterfly"
],
"repository": {
"type" : "git",
"url" : "https://github.com/jerryc127/hexo-theme-butterfly.git"
"type": "git",
"url": "https://github.com/jerryc127/hexo-theme-butterfly.git"
},
"bugs": {
"url": "https://github.com/jerryc127/hexo-theme-butterfly/issues",
"email": "my@crazywong.com"
},
"dependencies": {
"hexo-renderer-pug": "^3.0.0",
"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/",
"author": "Jerry <my@crazywong.com>",

View File

@@ -1,7 +1,7 @@
abcjs_basic_js:
name: abcjs
file: dist/abcjs-basic-min.js
version: 6.4.4
version: 6.5.1
activate_power_mode:
name: butterfly-extsrc
file: dist/activate-power-mode.min.js
@@ -9,7 +9,7 @@ activate_power_mode:
algolia_search:
name: algoliasearch
file: dist/lite/builds/browser.umd.js
version: 5.12.0
version: 5.30.0
aplayer_css:
name: aplayer
file: dist/APlayer.min.css
@@ -45,7 +45,7 @@ canvas_ribbon:
chartjs:
name: chart.js
file: dist/chart.umd.js
version: 4.4.6
version: 4.5.0
clickShowText:
name: butterfly-extsrc
file: dist/click-show-text.min.js
@@ -57,21 +57,21 @@ click_heart:
disqusjs:
name: disqusjs
file: dist/browser/disqusjs.es2015.umd.min.js
version: 3.0.2
version: 3.1.0
disqusjs_css:
name: disqusjs
file: dist/browser/styles/disqusjs.css
version: 3.0.2
version: 3.1.0
docsearch_css:
name: '@docsearch/css'
other_name: docsearch-css
file: dist/style.css
version: 3.6.3
version: 3.9.0
docsearch_js:
name: '@docsearch/js'
other_name: docsearch-js
file: dist/umd/index.js
version: 3.6.3
version: 3.9.0
egjs_infinitegrid:
name: '@egjs/infinitegrid'
other_name: egjs-infinitegrid
@@ -80,12 +80,12 @@ egjs_infinitegrid:
fancybox:
name: '@fancyapps/ui'
file: dist/fancybox/fancybox.umd.js
version: 5.0.36
version: 6.0.7
other_name: fancyapps-ui
fancybox_css:
name: '@fancyapps/ui'
file: dist/fancybox/fancybox.css
version: 5.0.36
version: 6.0.7
other_name: fancyapps-ui
fireworks:
name: butterfly-extsrc
@@ -95,7 +95,7 @@ fontawesome:
name: '@fortawesome/fontawesome-free'
file: css/all.min.css
other_name: font-awesome
version: 6.6.0
version: 6.7.2
gitalk:
name: gitalk
file: dist/gitalk.min.js
@@ -111,17 +111,17 @@ instantpage:
instantsearch:
name: instantsearch.js
file: dist/instantsearch.production.min.js
version: 4.75.3
version: 4.79.0
katex:
name: katex
file: dist/katex.min.css
other_name: KaTeX
version: 0.16.11
version: 0.16.22
katex_copytex:
name: katex
file: dist/contrib/copy-tex.min.js
other_name: KaTeX
version: 0.16.11
version: 0.16.22
lazyload:
name: vanilla-lazyload
file: dist/lazyload.iife.min.js
@@ -137,7 +137,7 @@ medium_zoom:
mermaid:
name: mermaid
file: dist/mermaid.min.js
version: 11.4.0
version: 11.8.0
meting_js:
name: butterfly-extsrc
file: metingjs/dist/Meting.min.js
@@ -152,10 +152,6 @@ pace_js:
other_name: pace
file: pace.min.js
version: 1.2.4
pangu:
name: pangu
file: dist/browser/pangu.min.js
version: 4.0.7
pjax:
name: pjax
file: pjax.min.js
@@ -164,17 +160,17 @@ prismjs_autoloader:
name: prismjs
file: plugins/autoloader/prism-autoloader.min.js
other_name: prism
version: 1.29.0
version: 1.30.0
prismjs_js:
name: prismjs
file: prism.js
other_name: prism
version: 1.29.0
version: 1.30.0
prismjs_lineNumber_js:
name: prismjs
file: plugins/line-numbers/prism-line-numbers.min.js
other_name: prism
version: 1.29.0
version: 1.30.0
sharejs:
name: butterfly-extsrc
file: sharejs/dist/js/social-share.min.js
@@ -194,7 +190,7 @@ snackbar_css:
twikoo:
name: twikoo
file: dist/twikoo.all.min.js
version: 1.6.39
version: 1.6.44
typed:
name: typed.js
file: dist/typed.umd.js
@@ -202,14 +198,14 @@ typed:
valine:
name: valine
file: dist/Valine.min.js
version: 1.5.2
version: 1.5.3
waline_css:
name: '@waline/client'
file: dist/waline.css
other_name: waline
version: 3.3.2
version: 3.5.7
waline_js:
name: '@waline/client'
file: dist/waline.js
other_name: waline
version: 3.3.2
version: 3.5.7

View File

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

View File

@@ -5,6 +5,7 @@ hexo.extend.filter.register('before_generate', () => {
nav: {
logo: null,
display_title: true,
display_post_title: true,
fixed: false
},
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.'
},
footer: {
nav: null,
owner: {
enable: true,
since: 2019
since: 2024
},
custom_text: null,
copyright: true
copyright: {
enable: true,
version: true
},
custom_text: null
},
aside: {
enable: true,
@@ -223,6 +228,7 @@ hexo.extend.filter.register('before_generate', () => {
hide: null,
show: null
},
rightside_config_animation: true,
anchor: {
auto_update: false,
click_to_scroll: false
@@ -383,6 +389,10 @@ hexo.extend.filter.register('before_generate', () => {
crisp: {
website_id: null
},
google_tag_manager: {
tag_id: null,
domain: 'https://www.googletagmanager.com'
},
baidu_analytics: null,
google_analytics: null,
cloudflare_analytics: null,
@@ -545,12 +555,9 @@ hexo.extend.filter.register('before_generate', () => {
bg_dark: '#1f1f1f'
},
instantpage: false,
pangu: {
enable: false,
field: 'site'
},
lazyload: {
enable: false,
native: false,
field: 'site',
placeholder: null,
blur: false
@@ -567,6 +574,7 @@ hexo.extend.filter.register('before_generate', () => {
enable: true,
option: null
},
structured_data: true,
css_prefix: true,
inject: {
head: null,

View File

@@ -9,6 +9,10 @@
const urlFor = require('hexo-util').url_for.bind(hexo)
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'
return htmlContent.replace(/(<img.*? src=)/ig, `$1 "${bg}" data-lazy-src=`)
}

View File

@@ -1,33 +1,57 @@
/**
* Butterfly
* ramdom cover
* Random cover for posts
*/
'use strict'
hexo.extend.filter.register('before_post_render', data => {
const imgTestReg = /\.(png|jpe?g|gif|svg|webp)(\?.*)?$/i
hexo.extend.generator.register('post', locals => {
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
// Add path to top_img and cover if post_asset_folder is enabled
if (hexo.config.post_asset_folder) {
if (topImg && topImg.indexOf('/') === -1 && imgTestReg.test(topImg)) data.top_img = `${data.path}${topImg}`
if (coverVal && coverVal.indexOf('/') === -1 && imgTestReg.test(coverVal)) data.cover = `${data.path}${coverVal}`
if (topImg && topImg.indexOf('/') === -1 && imgTestReg.test(topImg)) {
data.top_img = `${data.path}${topImg}`
}
if (coverVal && coverVal.indexOf('/') === -1 && imgTestReg.test(coverVal)) {
data.cover = `${data.path}${coverVal}`
}
const randomCoverFn = () => {
const { cover: { default_cover: defaultCover } } = hexo.theme.config
if (!defaultCover) return false
if (!Array.isArray(defaultCover)) return defaultCover
const num = Math.floor(Math.random() * defaultCover.length)
return defaultCover[num]
}
if (coverVal === false) return data
// If cover is not set, use random cover
if (!coverVal) {
const randomCover = randomCoverFn()
const { cover: { default_cover: defaultCover } } = hexo.theme.config
const randomCover = getRandomCover(defaultCover)
data.cover = randomCover
coverVal = randomCover // update coverVal
}
@@ -37,4 +61,22 @@ hexo.extend.filter.register('before_post_render', 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 = {}) {
const { config, page, site, url_for, _p } = this
const archiveDir = config.archive_dir
const { timezone } = config
const lang = toMomentLocale(page.lang || page.language || config.language)
const type = options.type || 'monthly'
const format = options.format || (type === 'monthly' ? 'MMMM YYYY' : 'YYYY')
const showCount = Object.prototype.hasOwnProperty.call(options, 'show_count') ? options.show_count : true
const order = options.order || -1
const limit = options.limit
const compareFunc = type === 'monthly'
const { archive_dir: archiveDir, timezone, language } = config
// Destructure and set default options with object destructuring
const {
type = 'monthly',
format = type === 'monthly' ? 'MMMM YYYY' : 'YYYY',
show_count: showCount = true,
order = -1,
limit,
transform
} = options
// Optimize locale handling
const lang = toMomentLocale(page.lang || page.language || language)
// Memoize comparison function to improve performance
const compareFunc =
type === 'monthly'
? (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)
if (!posts.length) return ''
// Early return if no posts
if (!site.posts.length) return ''
const data = []
posts.forEach(post => {
// Use reduce for more efficient data processing
const data = site.posts.sort('date', order).reduce((acc, post) => {
let date = post.date.clone()
if (timezone) date = date.tz(timezone)
const year = date.year()
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)
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}/`
if (type === 'monthly') {
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)
}
const len = data.length
const limitLength = limit === 0 ? len : Math.min(len, limit)
// Limit results efficiently
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">
<i class="fas fa-archive"></i>
<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>
<ul class="card-archive-list">
`
for (let i = 0; i < limitLength; i++) {
const item = data[i]
result += `
// Use map for generating list items, join for performance
const archiveList = `
<ul class="card-archive-list">
${limitedData
.map(
item => `
<li class="card-archive-list-item">
<a class="card-archive-list-link" href="${link(item)}">
<span class="card-archive-list-date">${options.transform ? options.transform(item.name) : item.name}</span>
<a class="card-archive-list-link" href="${createArchiveLink(item)}">
<span class="card-archive-list-date">
${transform ? transform(item.name) : item.name}
</span>
${showCount ? `<span class="card-archive-list-count">${item.count}</span>` : ''}
</a>
</li>
`
}
)
.join('')}
</ul>
`
result += '</ul>'
return result
return archiveHeader + archiveList
})
const toMomentLocale = function (lang) {
if (!lang || lang === 'en' || lang === 'default') {
return 'en'
}
// Improved locale conversion function
const toMomentLocale = lang => {
if (!lang || ['en', 'default'].includes(lang)) return 'en'
return lang.toLowerCase().replace('_', '-')
}

View File

@@ -81,7 +81,7 @@ hexo.extend.helper.register('findArchivesTitle', function (page, menu, date) {
return loop(menu) || defaultTitle
})
hexo.extend.helper.register('getBgPath', path => {
hexo.extend.helper.register('getBgPath', function(path) {
if (!path) return ''
const absoluteUrlPattern = /^(?:[a-z][a-z\d+.-]*:)?\/\//i
@@ -91,7 +91,7 @@ hexo.extend.helper.register('getBgPath', path => {
if (colorPattern.test(path)) {
return `background-color: ${path};`
} else if (absoluteUrlPattern.test(path) || relativeUrlPattern.test(path)) {
return `background-image: url(${path});`
return `background-image: url(${this.url_for(path)});`
} else {
return `background: ${path};`
}
@@ -131,3 +131,22 @@ hexo.extend.helper.register('shuoshuoFN', (data, page) => {
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 [name] [descr] [url] [img] %}
*
* {% gallery [button] %}
* {% gallery [button],[limit],[firstLimit] %}
* {% gallery url,[url],[button] %}
*/
@@ -11,54 +11,66 @@
const urlFor = require('hexo-util').url_for.bind(hexo)
const gallery = (args, content) => {
args = args.join(' ').split(',')
let button = false
let type = 'data'
let dataStr = ''
const DEFAULT_LIMIT = 10
const DEFAULT_FIRST_LIMIT = 10
const IMAGE_REGEX = /!\[(.*?)\]\(([^\s]*)\s*(?:["'](.*?)["']?)?\s*\)/g
if (args[0] === 'url') {
[type, dataStr, button] = args // url,[link],[lazyload]
dataStr = urlFor(dataStr)
} else {
[button] = args // [lazyload]
const regex = /!\[(.*?)\]\(([^\s]*)\s*(?:["'](.*?)["']?)?\s*\)/g
let m
const arr = []
while ((m = regex.exec(content)) !== null) {
if (m.index === regex.lastIndex) {
regex.lastIndex++
// Helper functions
const parseGalleryArgs = args => {
const [type, ...rest] = args.join(' ').split(',').map(arg => arg.trim())
return {
isUrl: type === 'url',
params: type === 'url' ? rest : [type, ...rest]
}
arr.push({
url: m[2],
alt: m[1],
title: m[3]
}
const parseImageContent = content => {
const images = []
let match
while ((match = IMAGE_REGEX.exec(content)) !== null) {
images.push({
url: match[2],
alt: match[1] || '',
title: match[3] || ''
})
}
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>`
}
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 [name, descr, url, img] = args
const imgUrl = urlFor(img)
const urlLink = urlFor(url)
const [name = '', descr = '', url = '', img = ''] = args.map(arg => arg.trim())
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>
<div class="gallery-group-name">${name}</div>
<p>${descr}</p>
<a href='${urlLink}'></a>
<a href='${urlFor(url)}'></a>
</figcaption>
</figure>
`
</figure>`
}
// Register tags
hexo.extend.tag.register('gallery', gallery, { ends: true })
hexo.extend.tag.register('galleryGroup', galleryGroup)

View File

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

View File

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

View File

@@ -6,17 +6,45 @@
'use strict'
const score = (args, content) => {
// Escape HTML tags and some special characters, including curly braces
const escapeHtmlTags = s => {
const lookup = {
'&': '&amp;',
'"': '&quot;',
'\'': '&apos;',
"'": '&apos;',
'<': '&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
* by Jerry
* Timeline tag for Hexo
* Syntax:
* {% timeline [headline],[color] %}
* <!-- timeline [title] -->
* [content]
* <!-- endtimeline -->
* <!-- timeline [title] -->
* [content]
* <!-- endtimeline -->
* {% endtimeline %}
*/
'use strict'
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 = ''
let color = ''
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>`
}
// Pre-compile markdown render function
const renderMd = text => hexo.render.renderSync({ text, engine: 'markdown' })
const matches = []
let match
// Parse arguments more efficiently
const [text, color = ''] = args.length ? args.join(' ').split(',') : []
while ((match = tlBlock.exec(content)) !== null) {
matches.push(match[1])
matches.push(match[2])
}
// Build initial headline if text exists
const headline = text
? `<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) {
const tlChildTitle = hexo.render.renderSync({ text: matches[i], engine: 'markdown' })
const tlChildContent = hexo.render.renderSync({ text: matches[i + 1], engine: 'markdown' })
// Match all timeline blocks in one pass and transform
const items = Array.from(content.matchAll(tlBlock))
.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>`
const tlContentHtml = `<div class='timeline-item-content'>${tlChildContent}</div>`
result += `<div class='timeline-item'>${tlTitleHtml + tlContentHtml}</div>`
}
return `<div class="timeline ${color || ''}">${result}</div>`
return `<div class="timeline ${color}">${headline}${items}</div>`
}
hexo.extend.tag.register('timeline', timeLineFn, { ends: true })

View File

@@ -10,8 +10,8 @@
--preloader-bg: $preloader-bg
--preloader-color: $preloader-word-color
--tab-border-color: $tab-border-color
--tab-botton-bg: $tab-botton-bg
--tab-botton-color: $tab-botton-color
--tab-button-bg: $tab-button-bg
--tab-button-color: $tab-button-color
--tab-button-hover-bg: $tab-button-hover-bg
--tab-button-active-bg: $tab-button-active-bg
--card-bg: $card-bg

View File

@@ -13,17 +13,16 @@
background-color: var(--mark-bg)
content: ''
#footer-wrap
& > *
position: relative
padding: 40px 20px
color: var(--light-grey)
text-align: center
a
color: var(--light-grey)
transition: all .3s ease-in-out
&:hover
text-decoration: underline
color: $light-blue
.footer-separator
margin: 0 4px
@@ -33,3 +32,56 @@
max-height: 1.4em
width: auto
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
&:after
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
.pagination-related
width: 50%
height: 150px
+maxWidth768()
width: 100%
+minWidth768()
flex: 1
.info-1
.info-item-2
@@ -31,6 +30,10 @@
margin-top: 40px
width: 100%
addBorderRadius()
display: flex
+maxWidth768()
flex-direction: column
.layout
.pagination

View File

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

View File

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

View File

@@ -14,14 +14,14 @@
flex-wrap: wrap
margin: 0
padding: 0
background: var(--tab-botton-bg)
background: var(--tab-button-bg)
> .tab
flex-grow: 1
padding: 8px 18px
border-top: 2px solid var(--tab-border-color)
background: var(--tab-botton-bg)
color: var(--tab-botton-color)
background: var(--tab-button-bg)
color: var(--tab-button-color)
line-height: 2
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
// font
$chinseFont = $language == 'zh-CN' ? 'Microsoft YaHei' : 'Microsoft JhengHei'
$dafault-font-family = -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue', Lato, Roboto, 'PingFang SC', $chinseFont, sans-serif
$dafault-code-font = consolas, Menlo, 'PingFang SC', $chinseFont, sans-serif
$chineseFont = $language == 'zh-CN' ? 'Microsoft YaHei' : 'Microsoft JhengHei'
$default-font-family = -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue', Lato, Roboto, 'PingFang SC', $chineseFont, 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
$code-font-family = hexo-config('font.code_font_family') ? unquote(hexo-config('font.code_font_family')) : $dafault-code-font
$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')) : $default-code-font
$site-name-font = hexo-config('blog_title_font.font_family') && unquote(hexo-config('blog_title_font.font_family'))
// hr
$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-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')
$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
@@ -175,8 +175,8 @@ $tagsP-purple-color = #6f42c1
$tagsP-green-color = #5cb85c
// Tag Plugins - Tab
$tab-border-color = #f0f0f0
$tab-botton-bg = #f0f0f0
$tab-botton-color = $font-color
$tab-button-bg = #f0f0f0
$tab-button-color = $font-color
$tab-button-hover-bg = darken($tab-border-color, 8)
$tab-active-border-color = $theme-color
$tab-button-active-bg = $card-bg

View File

@@ -224,14 +224,23 @@ document.addEventListener('DOMContentLoaded', () => {
*/
const fetchUrl = async url => {
try {
const response = await fetch(url)
return await response.json()
} catch (error) {
console.error('Failed to fetch URL:', error)
return []
}
}
const runJustifiedGallery = (item, data, isButton = false, tabs) => {
const dataLength = data.length
const runJustifiedGallery = (container, data, config) => {
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,
isConstantSize: true,
sizeRange: [150, 600],
@@ -239,132 +248,130 @@ document.addEventListener('DOMContentLoaded', () => {
// observeChildren: true,
useTransform: true
// 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 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)
const ig = new InfiniteGrid.JustifiedInfiniteGrid(container, igConfig)
let isLayoutHidden = false
const completeFn = e => {
if (tabs) {
const parentNode = item.parentNode
// Utility functions
const sanitizeString = str => (str && str.replace(/"/g, '&quot;')) || ''
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) {
parentNode.style.visibility = 'visible'
}
if (item.offsetHeight === 0) {
if (container.offsetHeight === 0) {
parentNode.style.visibility = 'hidden'
isLayoutHidden = true
}
}
const { updated, isResize, mounted } = e
if (!updated.length || !mounted.length || isResize) {
return
}
if (!updated.length || !mounted.length || isResize) return
btf.loadLightbox(item.querySelectorAll('img:not(.medium-zoom-image)'))
btf.loadLightbox(container.querySelectorAll('img:not(.medium-zoom-image)'))
if (ig.getGroups().length === maxGroupKey) {
btf.setLoading.remove(item)
!tabs && ig.off('renderComplete', completeFn)
btf.setLoading.remove(container)
!tabs && ig.off('renderComplete', handleRenderComplete)
return
}
if (isButton) {
btf.setLoading.remove(item)
addButton(item)
btf.setLoading.remove(container)
addLoadMoreButton(container)
}
}
const requestAppendFn = btf.debounce(e => {
const handleRequestAppend = btf.debounce(e => {
const nextGroupKey = (+e.groupKey || 0) + 1
appendItem(nextGroupKey, 10)
if (nextGroupKey === maxGroupKey) {
ig.off('requestAppend', requestAppendFn)
}
if (nextGroupKey === 1) appendItems(nextGroupKey, firstLimit, true)
else appendItems(nextGroupKey, limit)
if (nextGroupKey === maxGroupKey) ig.off('requestAppend', handleRequestAppend)
}, 300)
btf.setLoading.add(item)
ig.on('renderComplete', completeFn)
btf.setLoading.add(container)
ig.on('renderComplete', handleRenderComplete)
if (isButton) {
appendItem(1, 10)
appendItems(1, firstLimit, true)
} else {
ig.on('requestAppend', requestAppendFn)
ig.on('requestAppend', handleRequestAppend)
ig.renderItems()
}
btf.addGlobalFn('pjaxSendOnce', () => { ig.destroy() })
btf.addGlobalFn('pjaxSendOnce', () => ig.destroy())
}
const addJustifiedGallery = async (ele, tabs = false) => {
if (!ele.length) return
const init = async () => {
for (const item of ele) {
if (btf.isHidden(item) || item.classList.contains('loaded')) continue
const addJustifiedGallery = async (elements, tabs = false) => {
if (!elements.length) return
const initGallery = async () => {
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 {
const content = item.getAttribute('data-type') === 'url' ? await fetchUrl(text) : JSON.parse(text)
runJustifiedGallery(children, content, isButton, tabs)
} catch (e) {
console.error('Gallery data parsing failed:', e)
const data = element.getAttribute('data-type') === 'url' ? await fetchUrl(content) : JSON.parse(content)
runJustifiedGallery(container, data, config)
} catch (error) {
console.error('Gallery data parsing failed:', error)
}
}
}
if (typeof InfiniteGrid === 'function') {
init()
await initGallery()
} else {
await btf.getScript(`${GLOBAL_CONFIG.infinitegrid.js}`)
init()
await btf.getScript(GLOBAL_CONFIG.infinitegrid.js)
await initGallery()
}
}
@@ -864,7 +871,7 @@ document.addEventListener('DOMContentLoaded', () => {
menuMask && menuMask.addEventListener('click', () => { sidebarFn.close() })
clickFnOfSubMenu()
GLOBAL_CONFIG.islazyload && lazyloadImg()
GLOBAL_CONFIG.islazyloadPlugin && lazyloadImg()
GLOBAL_CONFIG.copyright !== undefined && addCopyright()
if (GLOBAL_CONFIG.autoDarkmode) {
@@ -890,7 +897,7 @@ document.addEventListener('DOMContentLoaded', () => {
initAdjust()
justifiedIndexPostUI()
if (GLOBAL_CONFIG_SITE.isPost) {
if (GLOBAL_CONFIG_SITE.pageType === 'post') {
addPostOutdateNotice()
GLOBAL_CONFIG.relativeDate.post && relativeDate(document.querySelectorAll('#post-meta time'))
} else {
@@ -900,11 +907,11 @@ document.addEventListener('DOMContentLoaded', () => {
toggleCardCategory()
}
GLOBAL_CONFIG_SITE.isHome && scrollDownInIndex()
GLOBAL_CONFIG_SITE.pageType === 'home' && scrollDownInIndex()
scrollFn()
forPostFn()
!GLOBAL_CONFIG_SITE.isShuoshuo && btf.switchComments(document)
GLOBAL_CONFIG_SITE.pageType !== 'shuoshuo' && btf.switchComments(document)
openMobileMenu()
}

View File

@@ -186,6 +186,7 @@
if (service === 'medium_zoom') {
mediumZoom(ele, { background: 'var(--zoom-bg)' })
return
}
if (service === 'fancybox') {
@@ -198,7 +199,9 @@
})
if (!window.fancyboxRun) {
Fancybox.bind('[data-fancybox]', {
let options = ''
if (Fancybox.version < '6') {
options = {
Hash: false,
Thumbs: {
showOnStart: false
@@ -226,7 +229,41 @@
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
}
}