feat(shuoshuo): render hexo tag plugins and defer post features via event

This commit is contained in:
myw
2026-07-21 21:46:58 +08:00 Unverified
parent 77ec898737
commit 85a1412b94
4 changed files with 65 additions and 41 deletions
+1 -1
View File
@@ -161,7 +161,7 @@
container.innerHTML = content
window.lazyLoadInstance && window.lazyLoadInstance.update()
btf.loadLightbox(document.querySelectorAll('#article-container img:not(.no-lightbox)'))
document.dispatchEvent(new Event('shuoshuo:rendered'))
}
const setupNavEvents = (nav) => {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "hexo-theme-butterfly",
"version": "5.6.1",
"version": "5.6.2",
"description": "A Simple and Card UI Design theme for Hexo",
"main": "package.json",
"scripts": {
+20 -1
View File
@@ -157,8 +157,27 @@ hexo.extend.helper.register('shuoshuoFN', (data, page) => {
processedData.forEach(item => {
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
// Render the content using Hexo's rendering engine to process any tags or markdown
const mockPost = { content: item.content }
hexo.execFilterSync('before_post_render', mockPost, { context: hexo })
item.content = mockPost.content
const codeBlocks = []
item.content = item.content.replace(/<hexoPostRenderCodeBlock>([\s\S]*?)<\/hexoPostRenderCodeBlock>/g, (_, c) => {
codeBlocks.push(c)
return `<!--CODEBLOCK_${codeBlocks.length - 1}-->`
})
try {
item.content = hexo.extend.tag.env.renderString(item.content, {})
} catch (e) {
// ignore tag plugin errors, fallback to markdown-only rendering
}
item.content = hexo.render.renderSync({ text: item.content, engine: 'markdown' })
item.content = item.content.replace(/<!--CODEBLOCK_(\d+)-->/g, (_, i) => codeBlocks[+i])
})
return processedData
+43 -38
View File
@@ -267,23 +267,23 @@ document.addEventListener('DOMContentLoaded', () => {
const fetchUrl = async url => {
try {
const response = await fetch(url)
if (!response.ok) throw new Error(`HTTP ${response.status}`)
if (!response.ok) throw new Error(`HTTP ${response.status}`)
return await response.json()
} catch (error) {
console.error('Failed to fetch URL:', error)
throw error
}
} catch (error) {
console.error('Failed to fetch URL:', error)
throw error
}
}
const runJustifiedGallery = (container, data, config) => {
const { isButton, tabs } = config
const limit = Math.max(1, Number(config.limit) || 20)
const firstLimit = Math.max(1, Number(config.firstLimit) || limit)
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 = dataLength
? Math.ceil(Math.max(0, dataLength - firstLimit) / limit) + 1
: 0
const maxGroupKey = dataLength
? Math.ceil(Math.max(0, dataLength - firstLimit) / limit) + 1
: 0
// Gallery configuration
const igConfig = {
@@ -300,19 +300,19 @@ document.addEventListener('DOMContentLoaded', () => {
let isLayoutHidden = false
// Utility functions
const sanitizeString = str => String(str ?? '')
.replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
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) : ''
const url = item.url ? sanitizeString(item.url) : ''
return `<div class="item">
<img src="${url}" data-grid-maintained-target="true" ${alt} ${title} />
<img src="${url}" data-grid-maintained-target="true" ${alt} ${title} />
</div>`
}
@@ -381,13 +381,13 @@ document.addEventListener('DOMContentLoaded', () => {
btf.setLoading.add(container)
ig.on('renderComplete', handleRenderComplete)
if (isButton && dataLength) {
if (isButton && dataLength) {
appendItems(1, firstLimit, true)
} else if (dataLength) {
} else if (dataLength) {
ig.on('requestAppend', handleRequestAppend)
ig.renderItems()
} else {
btf.setLoading.remove(container)
} else {
btf.setLoading.remove(container)
}
btf.addGlobalFn('pjaxSendOnce', () => ig.destroy())
@@ -412,9 +412,9 @@ document.addEventListener('DOMContentLoaded', () => {
container.textContent = ''
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')
if (!Array.isArray(data)) throw new TypeError('Gallery data must be an array')
runJustifiedGallery(container, data, config)
element.classList.add('loaded')
element.classList.add('loaded')
} catch (error) {
console.error('Gallery data parsing failed:', error)
}
@@ -456,7 +456,7 @@ document.addEventListener('DOMContentLoaded', () => {
// 檢查文檔高度是否小於視窗高度
const checkDocumentHeight = () => {
if (document.body.scrollHeight <= window.innerHeight + 56) {
if (document.body.scrollHeight <= window.innerHeight + 56) {
$rightside.classList.add('rightside-show')
return true
}
@@ -472,8 +472,8 @@ document.addEventListener('DOMContentLoaded', () => {
let flag = ''
const scrollTask = btf.rafThrottle(() => {
if (checkDocumentHeight()) return
if (checkDocumentHeight()) return
const currentTop = window.scrollY || document.documentElement.scrollTop
const isDown = scrollDirection(currentTop)
if (currentTop > 56) {
@@ -501,14 +501,14 @@ document.addEventListener('DOMContentLoaded', () => {
$header.classList.remove('nav-fixed', 'nav-visible')
}
$rightside.classList.remove('rightside-show')
}
isShowPercent && rightsideScrollPercent(currentTop)
})
checkDocumentHeight()
btf.addEventListenerPjax(window, 'scroll', scrollTask, { passive: true })
}
}
isShowPercent && rightsideScrollPercent(currentTop)
})
checkDocumentHeight()
btf.addEventListenerPjax(window, 'scroll', scrollTask, { passive: true })
}
/**
* toc, anchor
@@ -995,8 +995,11 @@ document.addEventListener('DOMContentLoaded', () => {
GLOBAL_CONFIG_SITE.pageType === 'home' && scrollDownInIndex()
scrollFn()
forPostFn()
GLOBAL_CONFIG_SITE.pageType !== 'shuoshuo' && btf.switchComments(document)
if (GLOBAL_CONFIG_SITE.pageType !== 'shuoshuo') {
forPostFn()
btf.switchComments(document)
}
openMobileMenu()
}
@@ -1012,4 +1015,6 @@ document.addEventListener('DOMContentLoaded', () => {
fn()
})
})
document.addEventListener('shuoshuo:rendered', forPostFn)
})