This commit is contained in:
myw
2026-07-16 16:53:38 +08:00 Unverified
parent 6b44647d4e
commit d8942c236a
22 changed files with 263 additions and 240 deletions
+5 -2
View File
@@ -2,8 +2,11 @@ script.
(() => {
const abcjsInit = () => {
const abcjsFn = () => {
const $article = document.getElementById("article-container")
if (!$article || $article.querySelector(".hbe-container")) return
setTimeout(() => {
const sheets = document.querySelectorAll(".abc-music-sheet")
const sheets = $article.querySelectorAll(".abc-music-sheet")
for (let i = 0; i < sheets.length; i++) {
const ele = sheets[i]
if (ele.children.length > 0) continue
@@ -29,7 +32,7 @@ script.
}, 100)
}
if (typeof ABCJS === "object") {
if (typeof ABCJS === "object" && ABCJS !== null) {
abcjsFn()
} else {
btf.getScript("!{url_for(theme.asset.abcjs_basic_js)}").then(abcjsFn)
+10 -12
View File
@@ -1,14 +1,15 @@
//- Knocket live chat — https://trtc.io/solutions/knocket
script.
(() => {
const id = '#{theme.knocket.identifier}'
if (!id) return
const identifier = !{JSON.stringify(theme.knocket.identifier || '').replace(/</g, '\\u003c')}
if (!identifier) return
btf.getScript(`https://trtc.io/knocket-sdk/sdk.js?identifier=${id}`).then(() => {
btf.getScript(`https://trtc.io/knocket-sdk/sdk.js?identifier=${encodeURIComponent(identifier)}`).then(() => {
const isChatBtn = !{theme.chat.rightside_button}
const isChatHideShow = !{theme.chat.button_hide_show}
const getWidget = () => document.getElementById('contact-widget-auto')
let widget = null
const getWidget = () => widget || (widget = document.getElementById('contact-widget-auto'))
if (isChatBtn) {
const hide = () => { const w = getWidget(); if (w) w.style.display = 'none' }
@@ -24,21 +25,18 @@ script.
window.chatBtnFn = () => {
const w = getWidget()
if (!w) return
if (w.style.display === 'none') show()
else hide()
w.style.display = w.style.display === 'none' ? '' : 'none'
}
document.getElementById('chat-btn').style.display = 'block'
const chatBtn = document.getElementById('chat-btn')
if (chatBtn) chatBtn.style.display = 'block'
} else if (isChatHideShow) {
const observer = new MutationObserver(() => {
if (getWidget()) observer.disconnect()
})
observer.observe(document.body, { childList: true, subtree: true })
window.chatBtn = {
hide: () => { const w = getWidget(); if (w) w.style.display = 'none' },
show: () => { const w = getWidget(); if (w) w.style.display = '' }
}
}
}).catch(error => {
console.warn('[Knocket] Failed to load the chat SDK.', error)
})
})()
+1 -1
View File
@@ -15,7 +15,7 @@ script.
const loadMathjax = () => {
const article = document.getElementById('article-container')
if (!article) return
if (!article || article.querySelector('.hbe-container')) return
changeScriptToMath(article)
if (!window.MathJax) {
+7 -4
View File
@@ -330,8 +330,8 @@ script.
})
}
const codeToMermaid = () => {
const codeMermaidEle = document.querySelectorAll('pre > code.mermaid')
const codeToMermaid = $article => {
const codeMermaidEle = $article.querySelectorAll('pre > code.mermaid')
if (codeMermaidEle.length === 0) return
codeMermaidEle.forEach(ele => {
@@ -347,8 +347,11 @@ script.
}
const loadMermaid = () => {
if (!{theme.mermaid.code_write}) codeToMermaid()
const $mermaid = document.querySelectorAll('#article-container .mermaid-wrap')
const $article = document.getElementById('article-container')
if (!$article) return
if (!{theme.mermaid.code_write}) codeToMermaid($article)
const $mermaid = $article.querySelectorAll('.mermaid-wrap')
if ($mermaid.length === 0) return
const runMermaidFn = () => runMermaid($mermaid)
+5 -4
View File
@@ -65,12 +65,13 @@ script.
})
document.addEventListener('pjax:error', e => {
if (e.request.status === 404) {
const responseURL = e.request && e.request.responseURL
if (e.request && e.request.status === 404) {
!{theme.error_404 && theme.error_404.enable}
? pjax.loadUrl('!{url_for("/404.html")}')
: window.location.href = e.request.responseURL
} else {
window.location.href = e.request.responseURL
: responseURL && (window.location.href = responseURL)
} else if (responseURL) {
window.location.href = responseURL
}
})
})
+5 -1
View File
@@ -5,11 +5,15 @@
if (syntax_highlighter === 'prismjs' || enable) && !preprocess
script.
(() => {
const $article = document.getElementById('article-container')
if (!$article || $article.querySelector('.hbe-container')) return
window.Prism = window.Prism || {}
window.Prism.manual = true
const highlightAll = () => {
window.Prism.highlightAll()
// window.Prism.highlightAll()
window.Prism.highlightAllUnder($article, true)
}
window.addEventListener('load', highlightAll)
+5 -5
View File
@@ -44,7 +44,7 @@ script.
}
}
}
btf.addGlobalFn('pjaxSendOnce', () => { typed.destroy() }, 'typedDestroy')
btf.addGlobalFn('pjaxSendOnce', () => { typed && typed.destroy() }, 'typedDestroy')
case source
when 1
@@ -53,8 +53,8 @@ case source
fetch('https://v1.hitokoto.cn')
.then(response => response.json())
.then(data => {
const from = '出自 ' + data.from
typedJSFn.processSubtitle(data.hitokoto, [from])
const from = data.from ? '出自 ' + data.from : ''
typedJSFn.processSubtitle(data.hitokoto, from ? [from] : [])
})
.catch(err => {
console.error('Failed to get the Hitokoto API:', err)
@@ -79,7 +79,7 @@ case source
})
.catch(err => {
console.error('Failed to get the Yiyan API:', err)
typedJSFn.processSubtitle(!{JSON.stringify(subContent.length)})
typedJSFn.processSubtitle(!{JSON.stringify(subContent)})
})
}
typedJSFn.run(subtitleType)
@@ -99,7 +99,7 @@ case source
})
.catch(err => {
console.error('Failed to get the Jinrishici API:', err)
typedJSFn.processSubtitle(!{JSON.stringify(subContent.length)})
typedJSFn.processSubtitle(!{JSON.stringify(subContent)})
})
}
typedJSFn.run(subtitleType)
+40 -34
View File
@@ -28,21 +28,20 @@ script.
})
}
const extractValue = stat => (stat && typeof stat.value !== 'undefined') ? stat.value : stat
const getData = async (isPost) => {
try {
const now = Date.now()
const keyUrl = isPost ? `&url=${window.location.pathname}&path=${window.location.pathname}` : ''
const headerList = { 'Accept': 'application/json' }
if (!{isServerURL}) {
headerList['Authorization'] = `Bearer ${config.token}`
} else {
headerList['x-umami-api-key'] = config.token
const pathQuery = isPost ? `&url=${window.location.pathname}&path=${window.location.pathname}` : ''
const headers = {
'Accept': 'application/json',
[!{isServerURL} ? 'Authorization' : 'x-umami-api-key']: !{isServerURL} ? `Bearer ${config.token}` : config.token
}
const res = await fetch(`!{apiUrl}/websites/!{website_id}/stats?startAt=0000000000&endAt=${now}${keyUrl}`, {
method: "GET",
headers: headerList
const res = await fetch(`!{apiUrl}/websites/!{website_id}/stats?startAt=0000000000&endAt=${now}${pathQuery}`, {
method: 'GET',
headers
})
if (!res.ok) {
@@ -58,39 +57,47 @@ script.
const insertData = async () => {
try {
const tasks = []
if (GLOBAL_CONFIG_SITE.pageType === 'post' && config.page_pv) {
const pagePV = document.getElementById('umamiPV')
if (pagePV) {
const data = await getData(true)
if (data && data.pageviews) {
pagePV.textContent = typeof data.pageviews.value !== 'undefined' ? data.pageviews.value : data.pageviews
} else {
console.warn('Umami Analytics: Invalid page view data received')
tasks.push((async () => {
const pagePV = document.getElementById('umamiPV')
if (pagePV) {
const data = await getData(true)
if (data && data.pageviews) {
pagePV.textContent = extractValue(data.pageviews)
} else {
console.warn('Umami Analytics: Invalid page view data received')
}
}
}
})())
}
if (config.site_uv || config.site_pv) {
const data = await getData(false)
tasks.push((async () => {
const data = await getData(false)
if (config.site_uv) {
const siteUV = document.getElementById('umami-site-uv')
if (siteUV && data && data.visitors) {
siteUV.textContent = typeof data.visitors.value !== 'undefined' ? data.visitors.value : data.visitors
} else if (siteUV) {
console.warn('Umami Analytics: Invalid site UV data received')
if (config.site_uv) {
const siteUV = document.getElementById('umami-site-uv')
if (siteUV && data && data.visitors) {
siteUV.textContent = extractValue(data.visitors)
} else if (siteUV) {
console.warn('Umami Analytics: Invalid site UV data received')
}
}
}
if (config.site_pv) {
const sitePV = document.getElementById('umami-site-pv')
if (sitePV && data && data.pageviews) {
sitePV.textContent = typeof data.pageviews.value !== 'undefined' ? data.pageviews.value : data.pageviews
} else if (sitePV) {
console.warn('Umami Analytics: Invalid site PV data received')
if (config.site_pv) {
const sitePV = document.getElementById('umami-site-pv')
if (sitePV && data && data.pageviews) {
sitePV.textContent = extractValue(data.pageviews)
} else if (sitePV) {
console.warn('Umami Analytics: Invalid site PV data received')
}
}
}
})())
}
await Promise.all(tasks)
} catch (error) {
console.error('Umami Analytics: Failed to insert data', error)
}
@@ -99,7 +106,6 @@ script.
btf.addGlobalFn('pjaxComplete', runTrack, 'umami_analytics_run_track')
btf.addGlobalFn('pjaxComplete', insertData, 'umami_analytics_insert')
loadUmamiJS()
if (document.readyState === 'loading') {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "hexo-theme-butterfly",
"version": "5.5.5",
"version": "5.6.0",
"description": "A Simple and Card UI Design theme for Hexo",
"main": "package.json",
"scripts": {
+6 -6
View File
@@ -1,7 +1,7 @@
abcjs_basic_js:
name: abcjs
file: dist/abcjs-basic-min.js
version: 6.6.3
version: 6.6.4
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.53.0
version: 5.56.0
aplayer_css:
name: aplayer
file: dist/APlayer.min.css
@@ -95,7 +95,7 @@ fontawesome:
name: '@fortawesome/fontawesome-free'
file: css/all.min.css
other_name: font-awesome
version: 7.2.0
version: 7.3.1
gitalk:
name: gitalk
file: dist/gitalk.min.js
@@ -125,7 +125,7 @@ lazyload:
mathjax:
name: mathjax
file: tex-mml-chtml.js
version: 4.1.2
version: 4.1.3
medium_zoom:
name: medium-zoom
file: dist/medium-zoom.min.js
@@ -133,7 +133,7 @@ medium_zoom:
mermaid:
name: mermaid
file: dist/mermaid.min.js
version: 11.15.0
version: 11.16.0
meting_js:
name: butterfly-extsrc
file: metingjs/dist/Meting.min.js
@@ -186,7 +186,7 @@ snackbar_css:
twikoo:
name: twikoo
file: dist/twikoo.all.min.js
version: 1.7.11
version: 1.7.14
typed:
name: typed.js
file: dist/typed.umd.js
+6 -7
View File
@@ -17,12 +17,11 @@ hexo.extend.helper.register('getArchiveLength', function () {
const m = date.month() + 1
const d = date.date()
if (yearly) {
const keyYear = `${y}`
map.set(keyYear, (map.get(keyYear) || 0) + 1)
}
// Always track year so year archive pages can be counted
const keyYear = `${y}`
map.set(keyYear, (map.get(keyYear) || 0) + 1)
if (monthly) {
if (monthly || daily) {
const keyMonth = `${y}-${m}`
map.set(keyMonth, (map.get(keyMonth) || 0) + 1)
}
@@ -37,8 +36,8 @@ hexo.extend.helper.register('getArchiveLength', function () {
// Determine the appropriate key to fetch based on current page context
let key
if (yearly && year) key = `${year}`
if (monthly && month) key = `${year}-${month}`
if (yearly || monthly || daily) key = `${year}`
if ((monthly || daily) && month) key = `${year}-${month}`
if (daily && day) key = `${year}-${month}-${day}`
// Return the count for the current period or default to the total posts
+11 -12
View File
@@ -11,6 +11,8 @@ const colorPattern = /^(#|rgb|rgba|hsl|hsla)/i
const simpleFilePattern = /\.(png|jpg|jpeg|gif|bmp|webp|svg|tiff)$/i
const archiveRegex = /\/archives\//
const { version: themeVersion } = require('../../package.json')
hexo.extend.helper.register('truncate', truncateContent)
hexo.extend.helper.register('postDesc', data => {
@@ -58,8 +60,6 @@ hexo.extend.helper.register('cloudTags', function (options = {}) {
const userColors = normalizeColors(custom_colors)
const resolveColorClass = (idx) => `tag-color-${idx % userColors.length}`
const generateStyle = (size, unit, page, color) => {
const colorStyle = page === 'tags' ? `background-color: ${color};` : `color: ${color};`
return `font-size: ${parseFloat(size.toFixed(2))}${unit}; ${colorStyle}`
@@ -70,10 +70,9 @@ hexo.extend.helper.register('cloudTags', function (options = {}) {
const size = minfontsize + ((maxfontsize - minfontsize) * ratio)
if (userColors && userColors.length) {
const colorClass = resolveColorClass(idx)
const color = userColors[idx % userColors.length]
const style = generateStyle(size, unit, page, color)
return `<a href="${env.url_for(tag.path)}" class="tag-cloud-item ${colorClass}" style="${style}">${tag.name}</a>`
return `<a href="${env.url_for(tag.path)}" class="tag-cloud-item" style="${style}">${tag.name}</a>`
}
const color = getRandomColor()
@@ -105,13 +104,13 @@ hexo.extend.helper.register('findArchivesTitle', function (page, menu, date) {
if (!menu) return defaultTitle
const loop = m => {
for (const key in m) {
if (typeof m[key] === 'object') {
const result = loop(m[key])
for (const [key, value] of Object.entries(m)) {
if (value && typeof value === 'object') {
const result = loop(value)
if (result) return result
}
if (archiveRegex.test(m[key])) {
if (typeof value === 'string' && archiveRegex.test(value)) {
return key
}
}
@@ -154,9 +153,10 @@ hexo.extend.helper.register('shuoshuoFN', (data, page) => {
// This is a hack method, because hexo treats time as UTC time
// so you need to manually convert the time zone
const timezone = hexo.config.timezone
processedData.forEach(item => {
const utcDate = moment.utc(item.date).format('YYYY-MM-DD HH:mm:ss')
item.date = moment.tz(utcDate, hexo.config.timezone).format('YYYY-MM-DD HH:mm:ss')
const parsed = moment.utc(item.date)
item.date = moment.tz(parsed.format('YYYY-MM-DD HH:mm:ss'), timezone).format('YYYY-MM-DD HH:mm:ss')
// markdown
item.content = hexo.render.renderSync({ text: item.content, engine: 'markdown' })
})
@@ -179,8 +179,7 @@ hexo.extend.helper.register('getPageType', (page, isHome) => {
})
hexo.extend.helper.register('getVersion', () => {
const { version } = require('../../package.json')
return { hexo: hexo.version, theme: version }
return { hexo: hexo.version, theme: themeVersion }
})
hexo.extend.helper.register('safeJSON', data => {
+9 -7
View File
@@ -23,7 +23,6 @@ hexo.extend.helper.register('related_posts', function (currentPost) {
if (relatedPosts.has(post.path)) {
relatedPosts.get(post.path).weight += 1
} else {
const getPostDesc = post.postDesc || postDesc(post, hexo)
relatedPosts.set(post.path, {
title: post.title,
path: post.path,
@@ -32,7 +31,7 @@ hexo.extend.helper.register('related_posts', function (currentPost) {
weight: 1,
updated: post.updated,
created: post.date,
postDesc: getPostDesc,
post,
random: Math.random()
})
}
@@ -61,12 +60,15 @@ hexo.extend.helper.register('related_posts', function (currentPost) {
result += `<div class="headline"><i class="fas fa-thumbs-up fa-fw"></i><span>${headlineLang}</span></div>`
result += '<div class="relatedPosts-list">'
for (let i = 0; i < Math.min(relatedPostsList.length, limitNum); i++) {
let { cover, title, path, cover_type, created, updated, postDesc } = relatedPostsList[i]
const max = Math.min(relatedPostsList.length, limitNum)
for (let i = 0; i < max; i++) {
const item = relatedPostsList[i]
let { cover, title, path, cover_type, created, updated, post } = item
const { escape_html, url_for, date } = this
cover = cover || 'var(--default-bg-color)'
title = escape_html(title)
const className = postDesc ? 'pagination-related' : 'pagination-related no-desc'
const desc = post.postDesc || postDesc(post, hexo)
const className = desc ? 'pagination-related' : 'pagination-related no-desc'
result += `<a class="${className}" href="${url_for(path)}" title="${title}">`
if (cover_type === 'img') {
result += `<img class="cover" src="${url_for(cover)}" alt="cover">`
@@ -80,8 +82,8 @@ hexo.extend.helper.register('related_posts', function (currentPost) {
}
result += `<div class="info-item-2">${title}</div></div>`
if (postDesc) {
result += `<div class="info-2"><div class="info-item-1">${postDesc}</div></div>`
if (desc) {
result += `<div class="info-2"><div class="info-item-1">${desc}</div></div>`
}
result += '</div></a>'
}
+5 -4
View File
@@ -7,15 +7,16 @@
'use strict'
const urlFor = require('hexo-util').url_for.bind(hexo)
const { url_for, escapeHTML } = require('hexo-util')
const urlFor = url_for.bind(hexo)
const btn = args => {
const [url = '', text = '', icon = '', option = ''] = args.join(' ').split(',').map(arg => arg.trim())
const iconHTML = icon ? `<i class="${icon}"></i>` : ''
const textHTML = text ? `<span>${text}</span>` : ''
const iconHTML = icon ? `<i class="${escapeHTML(icon)}"></i>` : ''
const textHTML = text ? `<span>${escapeHTML(text)}</span>` : ''
return `<a class="btn-beautify ${option}" href="${urlFor(url)}" title="${text}">${iconHTML}${textHTML}</a>`
return `<a class="btn-beautify ${escapeHTML(option)}" href="${urlFor(url)}" title="${escapeHTML(text)}">${iconHTML}${textHTML}</a>`
}
hexo.extend.tag.register('btn', btn, { ends: false })
+8 -7
View File
@@ -4,24 +4,25 @@
'use strict'
const urlFor = require('hexo-util').url_for.bind(hexo)
const { url_for, escapeHTML } = require('hexo-util')
const urlFor = url_for.bind(hexo)
const flinkFn = (args, content) => {
const data = hexo.render.renderSync({ text: content, engine: 'yaml' })
let result = ''
data.forEach(item => {
const className = item.class_name ? `<div class="flink-name">${item.class_name}</div>` : ''
const classDesc = item.class_desc ? `<div class="flink-desc">${item.class_desc}</div>` : ''
const className = item.class_name ? `<div class="flink-name">${escapeHTML(item.class_name)}</div>` : ''
const classDesc = item.class_desc ? `<div class="flink-desc">${escapeHTML(item.class_desc)}</div>` : ''
const listResult = item.link_list.map(link => `
<div class="flink-list-item">
<a href="${link.link}" title="${link.name}" target="_blank">
<a href="${escapeHTML(link.link)}" title="${escapeHTML(link.name)}" target="_blank">
<div class="flink-item-icon">
<img class="no-lightbox" src="${link.avatar}" onerror='this.onerror=null;this.src="${urlFor(hexo.theme.config.error_img.flink)}"' alt="${link.name}" />
<img class="no-lightbox" src="${escapeHTML(link.avatar)}" onerror='this.onerror=null;this.src="${urlFor(hexo.theme.config.error_img.flink)}"' alt="${escapeHTML(link.name)}" />
</div>
<div class="flink-item-name">${link.name}</div>
<div class="flink-item-desc" title="${link.descr}">${link.descr}</div>
<div class="flink-item-name">${escapeHTML(link.name)}</div>
<div class="flink-item-desc" title="${escapeHTML(link.descr)}">${escapeHTML(link.descr)}</div>
</a>
</div>`).join('')
+1
View File
@@ -27,6 +27,7 @@ const parseGalleryArgs = args => {
const parseImageContent = content => {
const images = []
let match
IMAGE_REGEX.lastIndex = 0
while ((match = IMAGE_REGEX.exec(content)) !== null) {
images.push({
+1 -1
View File
@@ -16,7 +16,7 @@
'use strict'
const parseArgs = args => args.join(' ').split(',')
const parseArgs = args => args.join(' ').split(',').map(s => s.trim())
const generateStyle = (bg, color) => {
let style = 'style="'
+3 -13
View File
@@ -5,20 +5,10 @@
'use strict'
const { escapeHTML } = require('hexo-util')
const score = (args, content) => {
// Escape HTML tags and some special characters, including curly braces
const escapeHtmlTags = s => {
const lookup = {
'&': '&amp;',
'"': '&quot;',
"'": '&apos;',
'<': '&lt;',
'>': '&gt;',
'{': '&#123;',
'}': '&#125;'
}
return s.replace(/[&"'<>{}]/g, c => lookup[c])
}
const escapeHtmlTags = s => escapeHTML(s).replace(/[{}]/g, c => c === '{' ? '&#123;' : '&#125;')
const trimmed = content.trim()
// Split content using six dashes as a delimiter
+4
View File
@@ -12,6 +12,10 @@
const urlFor = require('hexo-util').url_for.bind(hexo)
const groups = {}
hexo.extend.filter.register('before_generate', () => {
Object.keys(groups).forEach(k => delete groups[k])
})
hexo.extend.filter.register('before_post_render', data => {
if (!hexo.theme.config.series.enable) return data
+35 -24
View File
@@ -267,18 +267,23 @@ document.addEventListener('DOMContentLoaded', () => {
const fetchUrl = async url => {
try {
const response = await fetch(url)
if (!response.ok) throw new Error(`HTTP ${response.status}`)
return await response.json()
} catch (error) {
console.error('Failed to fetch URL:', error)
return []
throw error
}
}
const runJustifiedGallery = (container, data, config) => {
const { isButton, limit, firstLimit, tabs } = config
const { isButton, tabs } = config
const limit = Math.max(1, Number(config.limit) || 20)
const firstLimit = Math.max(1, Number(config.firstLimit) || limit)
const dataLength = data.length
const maxGroupKey = Math.ceil((dataLength - firstLimit) / limit + 1)
const maxGroupKey = dataLength
? Math.ceil(Math.max(0, dataLength - firstLimit) / limit) + 1
: 0
// Gallery configuration
const igConfig = {
@@ -295,13 +300,19 @@ document.addEventListener('DOMContentLoaded', () => {
let isLayoutHidden = false
// Utility functions
const sanitizeString = str => (str && str.replace(/"/g, '&quot;')) || ''
const sanitizeString = str => String(str ?? '')
.replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
const createImageItem = item => {
const alt = item.alt ? `alt="${sanitizeString(item.alt)}"` : ''
const title = item.title ? `title="${sanitizeString(item.title)}"` : ''
const url = item.url ? sanitizeString(item.url) : ''
return `<div class="item">
<img src="${item.url}" data-grid-maintained-target="true" ${alt} ${title} />
<img src="${url}" data-grid-maintained-target="true" ${alt} ${title} />
</div>`
}
@@ -370,11 +381,13 @@ document.addEventListener('DOMContentLoaded', () => {
btf.setLoading.add(container)
ig.on('renderComplete', handleRenderComplete)
if (isButton) {
if (isButton && dataLength) {
appendItems(1, firstLimit, true)
} else {
} else if (dataLength) {
ig.on('requestAppend', handleRequestAppend)
ig.renderItems()
} else {
btf.setLoading.remove(container)
}
btf.addGlobalFn('pjaxSendOnce', () => ig.destroy())
@@ -397,11 +410,11 @@ document.addEventListener('DOMContentLoaded', () => {
const container = element.firstElementChild
const content = container.textContent
container.textContent = ''
element.classList.add('loaded')
try {
const data = element.getAttribute('data-type') === 'url' ? await fetchUrl(content) : JSON.parse(content)
if (!Array.isArray(data)) throw new TypeError('Gallery data must be an array')
runJustifiedGallery(container, data, config)
element.classList.add('loaded')
} catch (error) {
console.error('Gallery data parsing failed:', error)
}
@@ -436,24 +449,20 @@ document.addEventListener('DOMContentLoaded', () => {
*/
const scrollFn = () => {
const $rightside = document.getElementById('rightside')
const innerHeight = window.innerHeight + 56
let initTop = 0
const $header = document.getElementById('page-header')
const isChatBtn = typeof chatBtn !== 'undefined'
const isChatBtn = typeof window.chatBtn !== 'undefined'
const isShowPercent = GLOBAL_CONFIG.percent.rightside
// 檢查文檔高度是否小於視窗高度
const checkDocumentHeight = () => {
if (document.body.scrollHeight <= innerHeight) {
if (document.body.scrollHeight <= window.innerHeight + 56) {
$rightside.classList.add('rightside-show')
return true
}
return false
}
// 如果文檔高度小於視窗高度,直接返回
if (checkDocumentHeight()) return
// find the scroll direction
const scrollDirection = currentTop => {
const result = currentTop > initTop // true is down & false is up
@@ -463,6 +472,8 @@ document.addEventListener('DOMContentLoaded', () => {
let flag = ''
const scrollTask = btf.rafThrottle(() => {
if (checkDocumentHeight()) return
const currentTop = window.scrollY || document.documentElement.scrollTop
const isDown = scrollDirection(currentTop)
if (currentTop > 56) {
@@ -493,9 +504,9 @@ document.addEventListener('DOMContentLoaded', () => {
}
isShowPercent && rightsideScrollPercent(currentTop)
checkDocumentHeight()
})
checkDocumentHeight()
btf.addEventListenerPjax(window, 'scroll', scrollTask, { passive: true })
}
@@ -631,17 +642,17 @@ document.addEventListener('DOMContentLoaded', () => {
const handleThemeChange = mode => {
const globalFn = window.globalFn || {}
const themeChange = globalFn.themeChange || {}
if (!themeChange) {
return
}
const themeChange = globalFn.themeChange
if (!themeChange) return
Object.keys(themeChange).forEach(key => {
const themeChangeFn = themeChange[key]
const fn = themeChange[key]
if (typeof fn !== 'function') return
if (['disqus', 'disqusjs'].includes(key)) {
setTimeout(() => themeChangeFn(mode), 300)
setTimeout(() => fn(mode), 300)
} else {
themeChangeFn(mode)
fn(mode)
}
})
}
@@ -952,7 +963,7 @@ document.addEventListener('DOMContentLoaded', () => {
const forPostFn = () => {
const $article = document.getElementById('article-container')
if (!$article) return
if (!$article || $article.querySelector('.hbe-container')) return
addHighlightTool($article)
addPhotoFigcaption($article)