Compare commits

..

7 Commits
5.5.0 ... 5.5.2

18 changed files with 155 additions and 76 deletions

View File

@@ -515,12 +515,12 @@ share:
# Share.js
# https://github.com/overtrue/share.js
sharejs:
sites: facebook,twitter,wechat,weibo,qq
sites: facebook,x,wechat,weibo,qq
# AddToAny
# https://www.addtoany.com/
addtoany:
item: facebook,twitter,wechat,sina_weibo,facebook_messenger,email,copy_link
item: facebook,x,wechat,sina_weibo,facebook_messenger,email,copy_link
# --------------------------------------
# Comments System
@@ -1067,7 +1067,7 @@ CDN:
third_party_provider: jsdelivr
# Add version number to url, true or false
version: false
version: true
# Custom format
# For example: https://cdn.staticfile.org/${cdnjs_name}/${version}/${min_cdnjs_file}

View File

@@ -49,7 +49,7 @@ search:
pagination:
prev: 上一篇
next: 下一篇
page_info: '第 ${current} / 共 ${total} '
page_info: '第 ${current} / 共 ${total} '
comment: 评论

View File

@@ -52,7 +52,7 @@ div
!= partial("includes/third-party/umami_analytics", {}, { cache: true })
if theme.busuanzi.site_uv || theme.busuanzi.site_pv || theme.busuanzi.page_pv
script(async data-pjax src= theme.asset.busuanzi || '//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js')
script(async data-pjax src=url_for(theme.asset.busuanzi) || '//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js')
!= partial('includes/third-party/search/index', {}, { cache: true })

View File

@@ -46,6 +46,10 @@
- const localDate = page.shuoshuo_url ? [] : shuoshuoFN(site.data.shuoshuo, page)
if !page.shuoshuo_url
script(type='application/json' id='shuoshuo-data')!= safeJSON(localDate)
- const { enable, native, placeholder, field } = theme.lazyload
script.
(() => {
const limitConfig = !{ JSON.stringify(page.limit || {}) }
@@ -81,6 +85,36 @@
return `${year}-${month}-${day} ${hour}:${minute}:${second}`
}
const addLazyload = str => {
const config = {
enable: !{Boolean(enable)},
native: !{Boolean(native)},
field: '!{field}',
placeholder: '!{url_for(placeholder)}',
}
if (!config.enable || config.field !== 'site') return str
const parser = new DOMParser()
const doc = parser.parseFromString(str, 'text/html')
const images = doc.querySelectorAll('img')
images.forEach(img => {
if (config.native) {
img.setAttribute('loading', 'lazy')
} else {
const src = img.getAttribute('src')
img.setAttribute('data-lazy-src', src)
if (config.placeholder) {
img.setAttribute('src', config.placeholder)
} else {
img.removeAttribute('src')
}
}
})
return doc.body.innerHTML
}
let currentPage = 1
const itemsPerPage = 8
let totalPages = 0
@@ -114,7 +148,7 @@
</time>
</div>
</div>
<div class="shuoshuo-content">${item.content}</div>
<div class="shuoshuo-content">${addLazyload(item.content)}</div>
<div class="shuoshuo-footer ${tags ? 'flex-between' : 'flex-end'}">
${tags ? `<div class="shuoshuo-tags">${tags}</div>` : ''}
${commentButton}
@@ -280,7 +314,8 @@
const response = await fetch('!{url_for(page.shuoshuo_url)}')
originData = await response.json()
} else {
originData = !{JSON.stringify(localDate)}
const dataElement = document.getElementById('shuoshuo-data')
originData = dataElement ? JSON.parse(dataElement.textContent) : []
}
data = filterDataByLimit(sortDataByDate(originData), limitConfig)

View File

@@ -51,8 +51,8 @@ script.
const loadArtalk = async (el, pageKey) => {
if (typeof Artalk === 'object') initArtalk(el, pageKey)
else {
await btf.getCSS('!{theme.asset.artalk_css}')
await btf.getScript('!{theme.asset.artalk_js}')
await btf.getCSS('!{url_for(theme.asset.artalk_css)}')
await btf.getScript('!{url_for(theme.asset.artalk_js)}')
initArtalk(el, pageKey)
}
}

View File

@@ -6,7 +6,11 @@ script.
ele.forEach((item, index) => {
const mermaidSrc = item.firstElementChild
const mermaidThemeConfig = `%%{init:{ 'theme':'${theme}'}}%%\n`
const config = mermaidSrc.dataset.config ? JSON.parse(mermaidSrc.dataset.config) : {}
if (!config.theme) {
config.theme = theme
}
const mermaidThemeConfig = `%%{init: ${JSON.stringify(config)}}%%\n`
const mermaidID = `mermaid-${index}`
const mermaidDefinition = mermaidThemeConfig + mermaidSrc.textContent

View File

@@ -1,6 +1,6 @@
{
"name": "hexo-theme-butterfly",
"version": "5.5.0",
"version": "5.5.2",
"description": "A Simple and Card UI Design theme for Hexo",
"main": "package.json",
"scripts": {
@@ -25,7 +25,7 @@
"dependencies": {
"hexo-renderer-pug": "^3.0.0",
"hexo-renderer-stylus": "^3.0.1",
"hexo-util": "^3.3.0",
"hexo-util": "^4.0.0",
"moment-timezone": "^0.6.0"
},
"homepage": "https://butterfly.js.org/",

View File

@@ -9,7 +9,7 @@ activate_power_mode:
algolia_search:
name: algoliasearch
file: dist/lite/builds/browser.umd.js
version: 5.37.0
version: 5.43.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.5.0
version: 4.5.1
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.1.0
version: 3.2.1
disqusjs_css:
name: disqusjs
file: dist/browser/styles/disqusjs.css
version: 3.1.0
version: 3.2.1
docsearch_css:
name: '@docsearch/css'
other_name: docsearch-css
file: dist/style.css
version: 3.9.0
version: 4.3.1
docsearch_js:
name: '@docsearch/js'
other_name: docsearch-js
file: dist/umd/index.js
version: 3.9.0
version: 4.3.1
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: 6.0.29
version: 6.1.4
other_name: fancyapps-ui
fancybox_css:
name: '@fancyapps/ui'
file: dist/fancybox/fancybox.css
version: 6.0.29
version: 6.1.4
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: 7.0.1
version: 7.1.0
gitalk:
name: gitalk
file: dist/gitalk.min.js
@@ -112,12 +112,12 @@ katex:
name: katex
file: dist/katex.min.css
other_name: KaTeX
version: 0.16.22
version: 0.16.25
katex_copytex:
name: katex
file: dist/contrib/copy-tex.min.js
other_name: KaTeX
version: 0.16.22
version: 0.16.25
lazyload:
name: vanilla-lazyload
file: dist/lazyload.iife.min.js
@@ -133,7 +133,7 @@ medium_zoom:
mermaid:
name: mermaid
file: dist/mermaid.min.js
version: 11.11.0
version: 11.12.1
meting_js:
name: butterfly-extsrc
file: metingjs/dist/Meting.min.js
@@ -199,9 +199,9 @@ waline_css:
name: '@waline/client'
file: dist/waline.css
other_name: waline
version: 3.6.0
version: 3.7.1
waline_js:
name: '@waline/client'
file: dist/waline.js
other_name: waline
version: 3.6.0
version: 3.7.1

View File

@@ -290,10 +290,10 @@ module.exports = {
share: {
use: 'sharejs',
sharejs: {
sites: 'facebook,twitter,wechat,weibo,qq'
sites: 'facebook,x,wechat,weibo,qq'
},
addtoany: {
item: 'facebook,twitter,wechat,sina_weibo,facebook_messenger,email,copy_link'
item: 'facebook,x,wechat,sina_weibo,facebook_messenger,email,copy_link'
}
},
comments: {
@@ -591,7 +591,7 @@ module.exports = {
CDN: {
internal_provider: 'local',
third_party_provider: 'jsdelivr',
version: false,
version: true,
custom_format: null,
option: null
}

View File

@@ -18,20 +18,12 @@ const lazyload = htmlContent => {
const bg = hexo.theme.config.lazyload.placeholder ? urlFor(hexo.theme.config.lazyload.placeholder) : 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'
// Use more precise replacement: handle src attributes with double and single quotes, but avoid replacing content inside script tags
let result = htmlContent
// Handle src attributes with double quotes
result = result.replace(/(<img(?![^>]*?\bdata-lazy-src=)(?:\s[^>]*?)?\ssrc="([^"]+)")(?![^<]*<\/script>)/gi, (match, tag, src) => {
return tag.replace(`src="${src}"`, `src="${bg}" data-lazy-src="${src}"`)
// Handle src attributes with double quotes, single quotes, or no quotes (unified approach)
// Matches: src="..." or src='...' or src=... (e.g., after minification by hexo-minify)
return htmlContent.replace(/(<img(?![^>]*?\bdata-lazy-src=)(?:\s[^>]*?)?\ssrc=)(?:"([^"]*)"|'([^']*)'|([^\s>]+))(?![^<]*<\/script>)/gi, (match, prefix, srcDoubleQuote, srcSingleQuote, srcNoQuote) => {
const src = srcDoubleQuote || srcSingleQuote || srcNoQuote
return `${prefix}"${bg}" data-lazy-src="${src}"`
})
// Handle src attributes with single quotes
result = result.replace(/(<img(?![^>]*?\bdata-lazy-src=)(?:\s[^>]*?)?\ssrc='([^']+)')(?![^<]*<\/script>)/gi, (match, tag, src) => {
return tag.replace(`src='${src}'`, `src='${bg}' data-lazy-src='${src}'`)
})
return result
}
hexo.extend.filter.register('after_render:html', data => {

View File

@@ -1,7 +1,7 @@
'use strict'
hexo.extend.helper.register('aside_archives', function (options = {}) {
const { config, page, site, url_for, _p } = this
const { config, page, site, url_for: urlFor, _p } = this
const { archive_dir: archiveDir, timezone, language } = config
// Destructure and set default options with object destructuring
@@ -74,7 +74,7 @@ hexo.extend.helper.register('aside_archives', function (options = {}) {
if (type === 'monthly') {
url += item.month < 10 ? `0${item.month}/` : `${item.month}/`
}
return url_for(url)
return urlFor(url)
}
// Limit results efficiently
@@ -87,7 +87,7 @@ hexo.extend.helper.register('aside_archives', function (options = {}) {
<span>${_p('aside.card_archives')}</span>
${
data.length > limitedData.length
? `<a class="card-more-btn" href="${url_for(archiveDir)}/"
? `<a class="card-more-btn" href="${urlFor(archiveDir)}/"
title="${_p('aside.more_button')}">
<i class="fas fa-angle-right"></i>
</a>`

View File

@@ -4,9 +4,9 @@ hexo.extend.helper.register('inject_head_js', function () {
const { darkmode, aside, pjax } = this.theme
const start = darkmode.start || 6
const end = darkmode.end || 18
const { theme_color } = hexo.theme.config
const themeColorLight = theme_color && theme_color.enable ? theme_color.meta_theme_color_light : '#ffffff'
const themeColorDark = theme_color && theme_color.enable ? theme_color.meta_theme_color_dark : '#0d0d0d'
const { theme_color: themeColor } = hexo.theme.config
const themeColorLight = themeColor && themeColor.enable ? themeColor.meta_theme_color_light : '#ffffff'
const themeColorDark = themeColor && themeColor.enable ? themeColor.meta_theme_color_dark : '#0d0d0d'
const createCustomJs = () => `
const saveToLocal = {

View File

@@ -157,3 +157,12 @@ hexo.extend.helper.register('getVersion', () => {
const { version } = require('../../package.json')
return { hexo: hexo.version, theme: version }
})
hexo.extend.helper.register('safeJSON', data => {
// Safely serialize JSON for embedding in <script> tags
return JSON.stringify(data)
.replace(/</g, '\\u003c')
.replace(/>/g, '\\u003e')
.replace(/\u2028/g, '\\u2028')
.replace(/\u2029/g, '\\u2029')
})

View File

@@ -9,7 +9,8 @@
const { escapeHTML } = require('hexo-util')
const mermaid = (args, content) => {
return `<div class="mermaid-wrap"><pre class="mermaid-src" hidden>
const config = args[0] || '{}'
return `<div class="mermaid-wrap"><pre class="mermaid-src" data-config="${escapeHTML(config)}" hidden>
${escapeHTML(content)}
</pre></div>`
}

View File

@@ -100,16 +100,17 @@ $code-block
.highlight-tools
display: flex
align-items: center
overflow: hidden
padding: 0 8px
min-height: 24px
height: 2.15em
background: var(--hltools-bg)
color: var(--hltools-color)
font-size: $code-font-size
overflow: hidden
& > *
padding: 5px
flex: 0 0 auto
margin: 2px
i
cursor: pointer
@@ -130,28 +131,25 @@ $code-block
padding: 0
.code-lang
flex: 1
flex: 1 1 auto
overflow: hidden
padding-right: 10px
text-transform: uppercase
text-overflow: ellipsis
white-space: nowrap
font-weight: bold
font-size: 1.15em
user-select: none
-webkit-user-select: none
padding 2px
.copy-notice
padding-right: 2px
opacity: 0
transition: opacity .4s
if hexo-config('code_blocks.language')
.code-lang
flex: 1
else if (!$highlight_macstyle && hexo-config('code_blocks.shrink') != 'none')
& > div:nth-child(2)
flex: 1
margin-right: auto
else if !$highlight_macstyle && hexo-config('code_blocks.shrink') != 'none'
& > :nth-child(2)
margin-right: auto
else
.macStyle
flex: 1
& > :nth-child(1)
margin-right: auto
.gutter
user-select: none
@@ -163,11 +161,21 @@ $code-block
td
border: none
.copy-notice
position: absolute
z-index: 99999
padding: 2px 6px
border-radius: 3px
background: var(--hltools-bg)
white-space: nowrap
font-size: 12px
pointer-events: none
if $highlight_macstyle
.container
figure.highlight
margin: 0 0 24px
border-radius: 7px
border-radius: 8px
box-shadow: 0 5px 10px 0 $highlight-mac-border
-webkit-transform: translateZ(0)
@@ -231,7 +239,7 @@ if hexo-config('code_blocks.height_limit')
& ~ pre
overflow: hidden
height: unit(hexo-config('code_blocks.height_limit'), px)
@keyframes code-expand-key
0%
opacity: .6
@@ -264,8 +272,8 @@ if hexo-config('code_blocks.fullpage')
& ~ table
display: block
overflow: auto
height: calc(100vh - 2.15em)
margin-bottom: 0
height: calc(100vh - 2.15em)
@keyframes code-fullpage
0%,

View File

@@ -91,6 +91,7 @@
justify-content: center
align-items: center
margin-right: 6px
margin-top: 3px
min-width: 24px
color: $search-color
content: attr(value) '.'

View File

@@ -68,7 +68,7 @@ document.addEventListener('DOMContentLoaded', () => {
const isPrismjs = plugin === 'prismjs'
const highlightShrinkClass = isHighlightShrink === true ? 'closed' : ''
const highlightShrinkEle = isHighlightShrink !== undefined ? '<i class="fas fa-angle-down expand"></i>' : ''
const highlightCopyEle = highlightCopy ? '<div class="copy-notice"></div><i class="fas fa-paste copy-button"></i>' : ''
const highlightCopyEle = highlightCopy ? '<i class="fas fa-paste copy-button"></i>' : ''
const highlightMacStyleEle = '<div class="macStyle"><div class="mac-close"></div><div class="mac-minimize"></div><div class="mac-maximize"></div></div>'
const highlightFullpageEle = highlightFullpage ? '<i class="fa-solid fa-up-right-and-down-left-from-center fullpage-button"></i>' : ''
@@ -76,12 +76,41 @@ document.addEventListener('DOMContentLoaded', () => {
if (GLOBAL_CONFIG.Snackbar !== undefined) {
btf.snackbarShow(text)
} else {
ele.textContent = text
ele.style.opacity = 1
setTimeout(() => { ele.style.opacity = 0 }, 800)
const newEle = document.createElement('div')
newEle.className = 'copy-notice'
newEle.textContent = text
document.body.appendChild(newEle)
const buttonRect = ele.getBoundingClientRect()
const scrollTop = window.pageYOffset || document.documentElement.scrollTop
const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft
const finalTop = buttonRect.top + scrollTop - 40
const finalLeft = buttonRect.left + scrollLeft + buttonRect.width / 2
const topValue = ele.closest('figure.highlight').classList.contains('code-fullpage') ? finalTop + 60 : finalTop
newEle.style.cssText = `
top: ${topValue + 10}px;
left: ${finalLeft}px;
transform: translateX(-50%);
opacity: 0;
transition: opacity 0.3s ease, top 0.3s ease;
`
requestAnimationFrame(() => {
newEle.style.opacity = '1'
newEle.style.top = `${topValue}px`
})
setTimeout(() => {
newEle.style.opacity = '0'
newEle.style.top = `${topValue + 10}px`
setTimeout(() => {
newEle?.remove()
}, 300)
}, 800)
}
}
const copy = async (text, ctx) => {
try {
await navigator.clipboard.writeText(text)
@@ -99,7 +128,7 @@ document.addEventListener('DOMContentLoaded', () => {
const preCodeSelector = isPrismjs ? 'pre code' : 'table .code pre'
const codeElement = $buttonParent.querySelector(preCodeSelector)
if (!codeElement) return
copy(codeElement.innerText, clickEle.previousElementSibling)
copy(codeElement.innerText, clickEle)
$buttonParent.classList.remove('copy-true')
}

View File

@@ -156,10 +156,10 @@ class LocalSearch {
}
slicesOfContent.forEach(slice => {
resultItem += `<p class="search-result">${this.highlightKeyword(content, slice)}...</p></a>`
resultItem += `<p class="search-result">${this.highlightKeyword(content, slice)}...</p>`
})
resultItem += '</li>'
resultItem += '</a></li>'
resultItems.push({
item: resultItem,
id: resultItems.length,