Compare commits

..

33 Commits
5.3.3 ... 5.4.1

35 changed files with 579 additions and 287 deletions

2
.gitignore vendored
View File

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

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
# --------------------------------------
@@ -987,6 +1000,8 @@ instantpage: false
# 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:

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

@@ -55,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,19 +1,39 @@
#footer-wrap
if theme.footer.owner.enable
- const currentYear = new Date().getFullYear()
- const sinceYear = theme.footer.owner.since
.copyright
if sinceYear && sinceYear != currentYear
!= `&copy;${sinceYear} - ${currentYear} By ${config.author}`
else
!= `&copy;${currentYear} By ${config.author}`
if theme.footer.copyright
- const v = getVersion()
.framework-info
span= _p('footer.framework') + ' '
a(href='https://hexo.io')= `Hexo ${v.hexo}`
span.footer-separator |
span= _p('footer.theme') + ' '
a(href='https://github.com/jerryc127/hexo-theme-butterfly')= `Butterfly ${v.theme}`
- 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 = owner.since
span.copyright
if sinceYear && sinceYear != currentYear
!= `&copy;${sinceYear} - ${currentYear} By ${config.author}`
else
!= `&copy;${currentYear} By ${config.author}`
if copyright.enable
- const v = copyright.version ? getVersion() : false
span.framework-info
if owner.enable && nav
span.footer-separator |
span= _p('footer.framework') + ' '
a(href='https://hexo.io')= `Hexo${ v ? ' ' + v.hexo : '' }`
span.footer-separator |
span= _p('footer.theme') + ' '
a(href='https://github.com/jerryc127/hexo-theme-butterfly')= `Butterfly${ v ? ' ' + v.theme : '' }`
if theme.footer.custom_text
.footer_custom_text!= theme.footer.custom_text

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

@@ -1,34 +1,55 @@
if theme.structured_data && page.layout === 'post'
-
// use json-ld to add structured data
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 || site.url;
const authorHref = full_url_for(authorHrefVal);
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
}]
};
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);
-
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

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

View File

@@ -1,4 +1,5 @@
- const { readmode, translate, darkmode, aside, chat } = theme
mixin rightsideItem(array)
each item in array
case item
@@ -30,30 +31,22 @@ mixin rightsideItem(array)
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 globalPageType === '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

@@ -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)
}
}
// 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)
}
}
window.pjax ? abcjsInit() : window.addEventListener('load', abcjsInit)
btf.addGlobalFn('encrypt', abcjsInit, 'abcjs')
if (window.pjax) {
abcjsInit()
} else {
window.addEventListener("load", abcjsInit)
}
btf.addGlobalFn("encrypt", abcjsInit, "abcjs")
})()

View File

@@ -59,7 +59,10 @@ script.
document.addEventListener('pjax:error', e => {
if (e.request.status === 404) {
window.location.href = e.request.responseURL
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,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
}
const from = '出自 ' + data.from
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)
} else {
document.getElementById('subtitle').textContent = con
}
})
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 {
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)
} else {
document.getElementById('subtitle').textContent = result.data.content
}
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 {
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)
script.
function subtitleType () {
if (!{effect}) {
typedJSFn.init(!{JSON.stringify(subContent)})
} else {
document.getElementById("subtitle").textContent = !{JSON.stringify(subContent[0])}
if subContent.length > 0
script.
function subtitleType () {
typedJSFn.processSubtitle(!{JSON.stringify(subContent)})
}
}
typedJSFn.run(subtitleType)
typedJSFn.run(subtitleType)

View File

@@ -1,6 +1,6 @@
{
"name": "hexo-theme-butterfly",
"version": "5.3.3",
"version": "5.4.1",
"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.20.2
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.7
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.8.3
version: 3.9.0
docsearch_js:
name: '@docsearch/js'
other_name: docsearch-js
file: dist/umd/index.js
version: 3.8.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
@@ -111,17 +111,17 @@ instantpage:
instantsearch:
name: instantsearch.js
file: dist/instantsearch.production.min.js
version: 4.77.3
version: 4.79.0
katex:
name: katex
file: dist/katex.min.css
other_name: KaTeX
version: 0.16.21
version: 0.16.22
katex_copytex:
name: katex
file: dist/contrib/copy-tex.min.js
other_name: KaTeX
version: 0.16.21
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.1
version: 11.8.0
meting_js:
name: butterfly-extsrc
file: metingjs/dist/Meting.min.js
@@ -160,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
@@ -190,7 +190,7 @@ snackbar_css:
twikoo:
name: twikoo
file: dist/twikoo.all.min.js
version: 1.6.41
version: 1.6.44
typed:
name: typed.js
file: dist/typed.umd.js
@@ -203,9 +203,9 @@ waline_css:
name: '@waline/client'
file: dist/waline.css
other_name: waline
version: 3.5.2
version: 3.5.7
waline_js:
name: '@waline/client'
file: dist/waline.js
other_name: waline
version: 3.5.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,

View File

@@ -2,11 +2,7 @@
hexo.extend.helper.register('aside_archives', function (options = {}) {
const { config, page, site, url_for, _p } = this
const {
archive_dir: archiveDir,
timezone,
language
} = config
const { archive_dir: archiveDir, timezone, language } = config
// Destructure and set default options with object destructuring
const {
@@ -22,33 +18,42 @@ hexo.extend.helper.register('aside_archives', function (options = {}) {
const lang = toMomentLocale(page.lang || page.language || language)
// Memoize comparison function to improve performance
const compareFunc = type === 'monthly'
? (yearA, monthA, yearB, monthB) => yearA === yearB && monthA === monthB
: (yearA, yearB) => yearA === yearB
const compareFunc =
type === 'monthly'
? (yearA, monthA, yearB, monthB) => yearA === yearB && monthA === monthB
: (yearA, yearB) => yearA === yearB
// Early return if no posts
if (!site.posts.length) return ''
// Use reduce for more efficient data processing
const data = site.posts
.sort('date', order)
.reduce((acc, post) => {
let date = post.date.clone()
if (timezone) date = date.tz(timezone)
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
const year = date.year()
const month = date.month() + 1
if (lang) date = date.locale(lang)
if (lang) date = date.locale(lang)
// Find or create archive entry
const lastEntry = acc[acc.length - 1]
if (!lastEntry || !compareFunc(
lastEntry.year,
lastEntry.month,
year,
month
)) {
// 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,
@@ -58,9 +63,10 @@ hexo.extend.helper.register('aside_archives', function (options = {}) {
} else {
lastEntry.count++
}
}
return acc
}, [])
return acc
}, [])
// Create link generator function
const createArchiveLink = item => {
@@ -72,39 +78,41 @@ hexo.extend.helper.register('aside_archives', function (options = {}) {
}
// Limit results efficiently
const limitedData = limit > 0
? data.slice(0, Math.min(data.length, limit))
: data
const limitedData = limit > 0 ? data.slice(0, Math.min(data.length, limit)) : data
// Use template literal for better readability
const archiveHeader = `
<div class="item-headline">
<i class="fas fa-archive"></i>
<span>${_p('aside.card_archives')}</span>
${data.length > limitedData.length
? `<a class="card-more-btn" href="${url_for(archiveDir)}/"
${
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>
`
// Use map for generating list items, join for performance
const archiveList = `
<ul class="card-archive-list">
${limitedData.map(item => `
${limitedData
.map(
item => `
<li class="card-archive-list-item">
<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>`
: ''}
${showCount ? `<span class="card-archive-list-count">${item.count}</span>` : ''}
</a>
</li>
`).join('')}
`
)
.join('')}
</ul>
`

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};`
}

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

@@ -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
& > *
position: relative
color: var(--light-grey)
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

@@ -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, monospace, '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

@@ -186,6 +186,7 @@
if (service === 'medium_zoom') {
mediumZoom(ele, { background: 'var(--zoom-bg)' })
return
}
if (service === 'fancybox') {
@@ -198,35 +199,71 @@
})
if (!window.fancyboxRun) {
Fancybox.bind('[data-fancybox]', {
Hash: false,
Thumbs: {
showOnStart: false
},
Images: {
Panzoom: {
maxScale: 4
}
},
Carousel: {
transition: 'slide'
},
Toolbar: {
display: {
left: ['infobar'],
middle: [
'zoomIn',
'zoomOut',
'toggle1to1',
'rotateCCW',
'rotateCW',
'flipX',
'flipY'
],
right: ['slideshow', 'thumbs', 'close']
let options = ''
if (Fancybox.version < '6') {
options = {
Hash: false,
Thumbs: {
showOnStart: false
},
Images: {
Panzoom: {
maxScale: 4
}
},
Carousel: {
transition: 'slide'
},
Toolbar: {
display: {
left: ['infobar'],
middle: [
'zoomIn',
'zoomOut',
'toggle1to1',
'rotateCCW',
'rotateCW',
'flipX',
'flipY'
],
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
}
}