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 container.innerHTML = content
window.lazyLoadInstance && window.lazyLoadInstance.update() window.lazyLoadInstance && window.lazyLoadInstance.update()
btf.loadLightbox(document.querySelectorAll('#article-container img:not(.no-lightbox)')) document.dispatchEvent(new Event('shuoshuo:rendered'))
} }
const setupNavEvents = (nav) => { const setupNavEvents = (nav) => {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "hexo-theme-butterfly", "name": "hexo-theme-butterfly",
"version": "5.6.1", "version": "5.6.2",
"description": "A Simple and Card UI Design theme for Hexo", "description": "A Simple and Card UI Design theme for Hexo",
"main": "package.json", "main": "package.json",
"scripts": { "scripts": {
+20 -1
View File
@@ -157,8 +157,27 @@ hexo.extend.helper.register('shuoshuoFN', (data, page) => {
processedData.forEach(item => { processedData.forEach(item => {
const parsed = moment.utc(item.date) 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') 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 = hexo.render.renderSync({ text: item.content, engine: 'markdown' })
item.content = item.content.replace(/<!--CODEBLOCK_(\d+)-->/g, (_, i) => codeBlocks[+i])
}) })
return processedData return processedData
+43 -38
View File
@@ -267,23 +267,23 @@ document.addEventListener('DOMContentLoaded', () => {
const fetchUrl = async url => { const fetchUrl = async url => {
try { try {
const response = await fetch(url) 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() return await response.json()
} catch (error) { } catch (error) {
console.error('Failed to fetch URL:', error) console.error('Failed to fetch URL:', error)
throw error throw error
} }
} }
const runJustifiedGallery = (container, data, config) => { const runJustifiedGallery = (container, data, config) => {
const { isButton, tabs } = config const { isButton, tabs } = config
const limit = Math.max(1, Number(config.limit) || 20) const limit = Math.max(1, Number(config.limit) || 20)
const firstLimit = Math.max(1, Number(config.firstLimit) || limit) const firstLimit = Math.max(1, Number(config.firstLimit) || limit)
const dataLength = data.length const dataLength = data.length
const maxGroupKey = dataLength const maxGroupKey = dataLength
? Math.ceil(Math.max(0, dataLength - firstLimit) / limit) + 1 ? Math.ceil(Math.max(0, dataLength - firstLimit) / limit) + 1
: 0 : 0
// Gallery configuration // Gallery configuration
const igConfig = { const igConfig = {
@@ -300,19 +300,19 @@ document.addEventListener('DOMContentLoaded', () => {
let isLayoutHidden = false let isLayoutHidden = false
// Utility functions // Utility functions
const sanitizeString = str => String(str ?? '') const sanitizeString = str => String(str ?? '')
.replace(/&/g, '&amp;') .replace(/&/g, '&amp;')
.replace(/"/g, '&quot;') .replace(/"/g, '&quot;')
.replace(/'/g, '&#39;') .replace(/'/g, '&#39;')
.replace(/</g, '&lt;') .replace(/</g, '&lt;')
.replace(/>/g, '&gt;') .replace(/>/g, '&gt;')
const createImageItem = item => { const createImageItem = item => {
const alt = item.alt ? `alt="${sanitizeString(item.alt)}"` : '' const alt = item.alt ? `alt="${sanitizeString(item.alt)}"` : ''
const title = item.title ? `title="${sanitizeString(item.title)}"` : '' 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"> 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>` </div>`
} }
@@ -381,13 +381,13 @@ document.addEventListener('DOMContentLoaded', () => {
btf.setLoading.add(container) btf.setLoading.add(container)
ig.on('renderComplete', handleRenderComplete) ig.on('renderComplete', handleRenderComplete)
if (isButton && dataLength) { if (isButton && dataLength) {
appendItems(1, firstLimit, true) appendItems(1, firstLimit, true)
} else if (dataLength) { } else if (dataLength) {
ig.on('requestAppend', handleRequestAppend) ig.on('requestAppend', handleRequestAppend)
ig.renderItems() ig.renderItems()
} else { } else {
btf.setLoading.remove(container) btf.setLoading.remove(container)
} }
btf.addGlobalFn('pjaxSendOnce', () => ig.destroy()) btf.addGlobalFn('pjaxSendOnce', () => ig.destroy())
@@ -412,9 +412,9 @@ document.addEventListener('DOMContentLoaded', () => {
container.textContent = '' container.textContent = ''
try { try {
const data = element.getAttribute('data-type') === 'url' ? await fetchUrl(content) : JSON.parse(content) 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) runJustifiedGallery(container, data, config)
element.classList.add('loaded') element.classList.add('loaded')
} catch (error) { } catch (error) {
console.error('Gallery data parsing failed:', error) console.error('Gallery data parsing failed:', error)
} }
@@ -456,7 +456,7 @@ document.addEventListener('DOMContentLoaded', () => {
// 檢查文檔高度是否小於視窗高度 // 檢查文檔高度是否小於視窗高度
const checkDocumentHeight = () => { const checkDocumentHeight = () => {
if (document.body.scrollHeight <= window.innerHeight + 56) { if (document.body.scrollHeight <= window.innerHeight + 56) {
$rightside.classList.add('rightside-show') $rightside.classList.add('rightside-show')
return true return true
} }
@@ -472,8 +472,8 @@ document.addEventListener('DOMContentLoaded', () => {
let flag = '' let flag = ''
const scrollTask = btf.rafThrottle(() => { const scrollTask = btf.rafThrottle(() => {
if (checkDocumentHeight()) return if (checkDocumentHeight()) return
const currentTop = window.scrollY || document.documentElement.scrollTop const currentTop = window.scrollY || document.documentElement.scrollTop
const isDown = scrollDirection(currentTop) const isDown = scrollDirection(currentTop)
if (currentTop > 56) { if (currentTop > 56) {
@@ -501,14 +501,14 @@ document.addEventListener('DOMContentLoaded', () => {
$header.classList.remove('nav-fixed', 'nav-visible') $header.classList.remove('nav-fixed', 'nav-visible')
} }
$rightside.classList.remove('rightside-show') $rightside.classList.remove('rightside-show')
} }
isShowPercent && rightsideScrollPercent(currentTop) isShowPercent && rightsideScrollPercent(currentTop)
}) })
checkDocumentHeight() checkDocumentHeight()
btf.addEventListenerPjax(window, 'scroll', scrollTask, { passive: true }) btf.addEventListenerPjax(window, 'scroll', scrollTask, { passive: true })
} }
/** /**
* toc, anchor * toc, anchor
@@ -995,8 +995,11 @@ document.addEventListener('DOMContentLoaded', () => {
GLOBAL_CONFIG_SITE.pageType === 'home' && scrollDownInIndex() GLOBAL_CONFIG_SITE.pageType === 'home' && scrollDownInIndex()
scrollFn() scrollFn()
forPostFn() if (GLOBAL_CONFIG_SITE.pageType !== 'shuoshuo') {
GLOBAL_CONFIG_SITE.pageType !== 'shuoshuo' && btf.switchComments(document) forPostFn()
btf.switchComments(document)
}
openMobileMenu() openMobileMenu()
} }
@@ -1012,4 +1015,6 @@ document.addEventListener('DOMContentLoaded', () => {
fn() fn()
}) })
}) })
document.addEventListener('shuoshuo:rendered', forPostFn)
}) })