mirror of
https://github.com/jerryc127/hexo-theme-butterfly.git
synced 2026-08-10 20:48:42 +08:00
feat: add wordcount helpers, waline lightbox, and extensive bug fixes
- wordcount/min2read/totalcount built-in helpers (replaces hexo-wordcount) - cloudTags random sort support - waline lightbox via MutationObserver - subtitle: seq guard prevents stale API response race - newest-comments: escapeHtml XSS fix, unified fetchAndRender with cache fallback - katex: try/catch asset load, encrypt re-render - chartjs: destroy charts on pjax, fix theme property check - mathjax: DOMContentLoaded instead of load - mermaid: clean stale error elements - medium-zoom: single instance, detach on pjax - getScrollPercent: WeakMap cache with resize invalidation - overflowPaddingR: avoid stale DOM refs - readmode: dedupe button, pjax cleanup - hexo-blog-decrypt: null-guard translateFn and encryptFn - tw_cn: expand skipped translate tags - localStorage: try/catch setItem - cdn.js: fix [lib|dist]* character class regex bug - findArchivesTitle: support custom archive_dir - shuoshuoFN: guard moment.tz when timezone empty - umami: no-store cache, error handling, path param - artalk: vote config option - bump version 5.7.1.260810
This commit is contained in:
+1
-1
@@ -427,7 +427,6 @@ copy:
|
|||||||
enable: false
|
enable: false
|
||||||
limit_count: 150
|
limit_count: 150
|
||||||
|
|
||||||
# Need to install the hexo-wordcount plugin
|
|
||||||
wordcount:
|
wordcount:
|
||||||
enable: false
|
enable: false
|
||||||
# Display the word count of the article in post meta
|
# Display the word count of the article in post meta
|
||||||
@@ -652,6 +651,7 @@ artalk:
|
|||||||
site:
|
site:
|
||||||
# Use Artalk visitor count as the page view count
|
# Use Artalk visitor count as the page view count
|
||||||
visitor: false
|
visitor: false
|
||||||
|
vote: false
|
||||||
option:
|
option:
|
||||||
|
|
||||||
# --------------------------------------
|
# --------------------------------------
|
||||||
|
|||||||
@@ -43,13 +43,13 @@
|
|||||||
if theme.wordcount.post_wordcount
|
if theme.wordcount.post_wordcount
|
||||||
i.far.fa-file-word.fa-fw.post-meta-icon
|
i.far.fa-file-word.fa-fw.post-meta-icon
|
||||||
span.post-meta-label= _p('post.wordcount') + ':'
|
span.post-meta-label= _p('post.wordcount') + ':'
|
||||||
span.word-count= wordcount(page.content)
|
span.word-count= wordcount(page)
|
||||||
if theme.wordcount.min2read
|
if theme.wordcount.min2read
|
||||||
span.post-meta-separator |
|
span.post-meta-separator |
|
||||||
if theme.wordcount.min2read
|
if theme.wordcount.min2read
|
||||||
i.far.fa-clock.fa-fw.post-meta-icon
|
i.far.fa-clock.fa-fw.post-meta-icon
|
||||||
span.post-meta-label= _p('post.min2read') + ':'
|
span.post-meta-label= _p('post.min2read') + ':'
|
||||||
span= min2read(page.content, {cn: 350, en: 160}) + _p('post.min2read_unit')
|
span= min2read(page) + ' ' + _p('post.min2read_unit')
|
||||||
|
|
||||||
//- for pv and count
|
//- for pv and count
|
||||||
mixin pvBlock(parent_id, parent_class, parent_title)
|
mixin pvBlock(parent_id, parent_class, parent_title)
|
||||||
|
|||||||
+21
@@ -20,8 +20,29 @@ script.
|
|||||||
path: isShuoshuo ? path : (option && option.path) || path
|
path: isShuoshuo ? path : (option && option.path) || path
|
||||||
})
|
})
|
||||||
|
|
||||||
|
let lightboxObserver = null
|
||||||
|
if (GLOBAL_CONFIG.lightbox !== 'null') {
|
||||||
|
const wrap = el.querySelector('#waline-wrap')
|
||||||
|
if (wrap) {
|
||||||
|
lightboxObserver = new MutationObserver(mutations => {
|
||||||
|
const imgs = []
|
||||||
|
mutations.forEach(mutation => {
|
||||||
|
mutation.addedNodes.forEach(node => {
|
||||||
|
if (node.nodeType !== 1) return
|
||||||
|
const nodeImgs = node.matches && node.matches('img') ? [node] : node.querySelectorAll('img')
|
||||||
|
nodeImgs.forEach(img => imgs.push(img))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
const lightboxImgs = imgs.filter(img => img.closest('.wl-content') && !img.classList.contains('wl-emoji'))
|
||||||
|
if (lightboxImgs.length) btf.loadLightbox(lightboxImgs)
|
||||||
|
})
|
||||||
|
lightboxObserver.observe(wrap, { childList: true, subtree: true })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (isShuoshuo) {
|
if (isShuoshuo) {
|
||||||
window.shuoshuoComment.destroyWaline = () => {
|
window.shuoshuoComment.destroyWaline = () => {
|
||||||
|
lightboxObserver && lightboxObserver.disconnect()
|
||||||
destroyWaline(waline)
|
destroyWaline(waline)
|
||||||
if (el.children.length) {
|
if (el.children.length) {
|
||||||
el.innerHTML = ''
|
el.innerHTML = ''
|
||||||
|
|||||||
+14
-3
@@ -22,7 +22,7 @@ script.
|
|||||||
const value = obj[key]
|
const value = obj[key]
|
||||||
// If the property is an object and has theme-specific options, apply them
|
// If the property is an object and has theme-specific options, apply them
|
||||||
if (typeof value === 'object' && value !== null) {
|
if (typeof value === 'object' && value !== null) {
|
||||||
if (value[theme]) {
|
if (theme in value) {
|
||||||
obj[key] = value[theme] // Apply the value for the current theme
|
obj[key] = value[theme] // Apply the value for the current theme
|
||||||
} else {
|
} else {
|
||||||
// Recursively process child objects
|
// Recursively process child objects
|
||||||
@@ -32,6 +32,11 @@ script.
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const destroyChart = canvas => {
|
||||||
|
const chart = canvas && window.Chart && Chart.getChart(canvas)
|
||||||
|
if (chart) chart.destroy()
|
||||||
|
}
|
||||||
|
|
||||||
const runChartJS = ele => {
|
const runChartJS = ele => {
|
||||||
window.loadChartJS = true
|
window.loadChartJS = true
|
||||||
|
|
||||||
@@ -41,9 +46,10 @@ script.
|
|||||||
const width = item.getAttribute('data-width')
|
const width = item.getAttribute('data-width')
|
||||||
const existingCanvas = document.getElementById(chartID)
|
const existingCanvas = document.getElementById(chartID)
|
||||||
|
|
||||||
// If a canvas already exists, remove it to avoid rendering duplicates
|
// If a canvas already exists, destroy its chart and remove it to avoid rendering duplicates
|
||||||
if (existingCanvas) {
|
if (existingCanvas) {
|
||||||
existingCanvas.parentNode.remove()
|
destroyChart(existingCanvas)
|
||||||
|
existingCanvas.parentNode.remove()
|
||||||
}
|
}
|
||||||
|
|
||||||
const chartDefinition = chartSrc.textContent
|
const chartDefinition = chartSrc.textContent
|
||||||
@@ -83,6 +89,11 @@ script.
|
|||||||
window.loadChartJS ? runChartJS(chartJSEle) : btf.getScript('!{url_for(theme.asset.chartjs)}').then(() => runChartJS(chartJSEle))
|
window.loadChartJS ? runChartJS(chartJSEle) : btf.getScript('!{url_for(theme.asset.chartjs)}').then(() => runChartJS(chartJSEle))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Destroy all charts before navigating away to avoid leaks
|
||||||
|
btf.addGlobalFn('pjaxSendOnce', () => {
|
||||||
|
document.querySelectorAll('.chartjs-wrap canvas').forEach(destroyChart)
|
||||||
|
}, 'chartjs')
|
||||||
|
|
||||||
// Listen for theme change events
|
// Listen for theme change events
|
||||||
btf.addGlobalFn('themeChange', loadChartJS, 'chartjs')
|
btf.addGlobalFn('themeChange', loadChartJS, 'chartjs')
|
||||||
btf.addGlobalFn('encrypt', loadChartJS, 'chartjs')
|
btf.addGlobalFn('encrypt', loadChartJS, 'chartjs')
|
||||||
|
|||||||
+8
-3
@@ -6,11 +6,16 @@ script.
|
|||||||
|
|
||||||
if (!window.katex_js_css) {
|
if (!window.katex_js_css) {
|
||||||
window.katex_js_css = true
|
window.katex_js_css = true
|
||||||
await btf.getCSS('!{url_for(theme.asset.katex)}')
|
try {
|
||||||
if (!{theme.math.katex.copy_tex}) {
|
await btf.getCSS('!{url_for(theme.asset.katex)}')
|
||||||
await btf.getScript('!{url_for(theme.asset.katex_copytex)}')
|
if (!{theme.math.katex.copy_tex}) {
|
||||||
|
await btf.getScript('!{url_for(theme.asset.katex_copytex)}')
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('[katex] failed to load assets, showing formulas anyway:', e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
showKatex()
|
showKatex()
|
||||||
|
btf.addGlobalFn('encrypt', showKatex, 'katex')
|
||||||
})()
|
})()
|
||||||
+3
-1
@@ -77,5 +77,7 @@ script.
|
|||||||
}
|
}
|
||||||
|
|
||||||
btf.addGlobalFn('encrypt', loadMathjax, 'mathjax')
|
btf.addGlobalFn('encrypt', loadMathjax, 'mathjax')
|
||||||
window.pjax ? loadMathjax() : window.addEventListener('load', loadMathjax)
|
if (window.pjax) loadMathjax()
|
||||||
|
else if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', loadMathjax)
|
||||||
|
else loadMathjax()
|
||||||
})()
|
})()
|
||||||
+3
-1
@@ -282,12 +282,14 @@ script.
|
|||||||
const theme = document.documentElement.getAttribute('data-theme') === 'dark' ? '!{theme.mermaid.theme.dark}' : '!{theme.mermaid.theme.light}'
|
const theme = document.documentElement.getAttribute('data-theme') === 'dark' ? '!{theme.mermaid.theme.dark}' : '!{theme.mermaid.theme.light}'
|
||||||
ele.forEach((item, index) => {
|
ele.forEach((item, index) => {
|
||||||
const mermaidSrc = item.firstElementChild
|
const mermaidSrc = item.firstElementChild
|
||||||
// Clean up event listeners before removing old SVG
|
// Clean up event listeners and stale content before re-rendering
|
||||||
if (item.__mermaidAbortController) {
|
if (item.__mermaidAbortController) {
|
||||||
item.__mermaidAbortController.abort()
|
item.__mermaidAbortController.abort()
|
||||||
}
|
}
|
||||||
const oldSvg = item.querySelector('svg')
|
const oldSvg = item.querySelector('svg')
|
||||||
if (oldSvg) oldSvg.remove()
|
if (oldSvg) oldSvg.remove()
|
||||||
|
const oldError = item.querySelector('.mermaid-error')
|
||||||
|
if (oldError) oldError.remove()
|
||||||
let config = {}
|
let config = {}
|
||||||
try {
|
try {
|
||||||
config = mermaidSrc.dataset.config ? JSON.parse(mermaidSrc.dataset.config) : {}
|
config = mermaidSrc.dataset.config ? JSON.parse(mermaidSrc.dataset.config) : {}
|
||||||
|
|||||||
+21
-28
@@ -5,9 +5,9 @@
|
|||||||
!= partial("includes/third-party/newest-comments/common.pug", {}, { cache: true })
|
!= partial("includes/third-party/newest-comments/common.pug", {}, { cache: true })
|
||||||
|
|
||||||
script.
|
script.
|
||||||
window.addEventListener('load', () => {
|
window.newestComments.ready(() => {
|
||||||
const keyName = 'artalk-newest-comments'
|
const keyName = 'artalk-newest-comments'
|
||||||
const { changeContent, generateHtml, run } = window.newestComments
|
const { changeContent, run } = window.newestComments
|
||||||
|
|
||||||
const getAvatarValue = async () => {
|
const getAvatarValue = async () => {
|
||||||
const predefinedAvatarCdn = '!{avatarCdn}'
|
const predefinedAvatarCdn = '!{avatarCdn}'
|
||||||
@@ -24,8 +24,8 @@ script.
|
|||||||
const result = await res.json()
|
const result = await res.json()
|
||||||
const { mirror, params, default: defaults } = result.frontend_conf.gravatar
|
const { mirror, params, default: defaults } = result.frontend_conf.gravatar
|
||||||
const avatarCdn = predefinedAvatarCdn || mirror
|
const avatarCdn = predefinedAvatarCdn || mirror
|
||||||
let avatarDefault = avatarDefaultFormat(predefinedAvatarDefault || params || defaults)
|
const avatarDefault = avatarDefaultFormat(predefinedAvatarDefault || params || defaults)
|
||||||
return { avatarCdn, avatarDefault}
|
return { avatarCdn, avatarDefault }
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
return { avatarCdn: predefinedAvatarCdn, avatarDefault: avatarDefaultFormat(predefinedAvatarDefault) }
|
return { avatarCdn: predefinedAvatarCdn, avatarDefault: avatarDefaultFormat(predefinedAvatarDefault) }
|
||||||
@@ -37,30 +37,23 @@ script.
|
|||||||
'limit': '!{newestCommentsLimit * 2}', // Fetch more comments to filter pending comments
|
'limit': '!{newestCommentsLimit * 2}', // Fetch more comments to filter pending comments
|
||||||
})
|
})
|
||||||
|
|
||||||
const getComment = async (ele) => {
|
const getComment = async () => {
|
||||||
try {
|
const res = await fetch(`!{server}/api/v2/stats/latest_comments?${searchParams}`)
|
||||||
const res = await fetch(`!{server}/api/v2/stats/latest_comments?${searchParams}`)
|
const result = await res.json()
|
||||||
const result = await res.json()
|
const { avatarCdn, avatarDefault } = await getAvatarValue()
|
||||||
const { avatarCdn, avatarDefault } = await getAvatarValue()
|
return result.data
|
||||||
const artalk = result.data
|
.filter(e => !e.is_pending) // Filter pending comments
|
||||||
.filter(e => !e.is_pending) // Filter pending comments
|
.slice(0, !{newestCommentsLimit}) // Limit the number of comments
|
||||||
.slice(0, !{newestCommentsLimit}) // Limit the number of comments
|
.map(e => {
|
||||||
.map(e => {
|
const avatar = avatarCdn && e.email_encrypted ? `${avatarCdn}${e.email_encrypted}?${avatarDefault}` : ''
|
||||||
const avatar = avatarCdn && e.email_encrypted ? `${avatarCdn}${e.email_encrypted}?${avatarDefault}` : ''
|
return {
|
||||||
return {
|
'avatar': avatar,
|
||||||
'avatar': avatar,
|
'content': changeContent(e.content_marked),
|
||||||
'content': changeContent(e.content_marked),
|
'nick': e.nick,
|
||||||
'nick': e.nick,
|
'url': e.page_url,
|
||||||
'url': e.page_url,
|
'date': e.date,
|
||||||
'date': e.date,
|
}
|
||||||
}
|
})
|
||||||
})
|
|
||||||
btf.saveToLocal.set(keyName, JSON.stringify(artalk), !{theme.aside.card_newest_comments.storage}/(60*24))
|
|
||||||
generateHtml(artalk, ele)
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e)
|
|
||||||
ele.textContent= "!{_p('aside.card_newest_comments.error')}"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
run(keyName, getComment)
|
run(keyName, getComment)
|
||||||
|
|||||||
+53
-17
@@ -1,38 +1,59 @@
|
|||||||
script.
|
script.
|
||||||
window.newestComments = {
|
window.newestComments = {
|
||||||
changeContent: content => {
|
ready: fn => {
|
||||||
if (content === '') return content
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', fn, { once: true })
|
||||||
|
} else {
|
||||||
|
fn()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
content = content.replace(/<img.*?src="(.*?)"?[^\>]+>/ig, '[!{_p("aside.card_newest_comments.image")}]') // replace image link
|
escapeHtml: str => {
|
||||||
content = content.replace(/<a[^>]+?href=["']?([^"']+)["']?[^>]*>([^<]+)<\/a>/gi, '[!{_p("aside.card_newest_comments.link")}]') // replace url
|
return String(str)
|
||||||
content = content.replace(/<pre><code>.*?<\/pre>/gi, '[!{_p("aside.card_newest_comments.code")}]') // replace code
|
.replace(/&/g, '&')
|
||||||
content = content.replace(/<code>.*?<\/code>/gi, '[!{_p("aside.card_newest_comments.code")}]') // replace code
|
.replace(/</g, '<')
|
||||||
content = content.replace(/<[^>]+>/g, "") // remove html tag
|
.replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/'/g, ''')
|
||||||
|
},
|
||||||
|
|
||||||
|
changeContent: content => {
|
||||||
|
if (!content) return ''
|
||||||
|
|
||||||
|
content = content.replace(/<img[^>]*>/gis, '[!{_p("aside.card_newest_comments.image")}]') // replace image link
|
||||||
|
content = content.replace(/<pre><code>[\s\S]*?<\/pre>/gi, '[!{_p("aside.card_newest_comments.code")}]') // replace code block
|
||||||
|
content = content.replace(/<a[^>]+?>[\s\S]*?<\/a>/gi, '[!{_p("aside.card_newest_comments.link")}]') // replace url
|
||||||
|
content = content.replace(/<code[^>]*?>[\s\S]*?<\/code>/gi, '[!{_p("aside.card_newest_comments.code")}]') // replace code
|
||||||
|
content = content.replace(/<[^>]+>/g, '') // remove html tag
|
||||||
|
|
||||||
if (content.length > 150) {
|
if (content.length > 150) {
|
||||||
content = content.substring(0, 150) + '...'
|
content = Array.from(content).slice(0, 150).join('') + '...'
|
||||||
}
|
}
|
||||||
|
|
||||||
return content
|
return content
|
||||||
},
|
},
|
||||||
|
|
||||||
generateHtml: (array, ele) => {
|
generateHtml: (array, ele) => {
|
||||||
|
const { escapeHtml } = window.newestComments
|
||||||
let result = ''
|
let result = ''
|
||||||
|
|
||||||
if (array.length) {
|
if (array.length) {
|
||||||
for (let i = 0; i < array.length; i++) {
|
result = array.map(item => {
|
||||||
result += '<div class="aside-list-item">'
|
let html = '<div class="aside-list-item">'
|
||||||
|
|
||||||
if (!{theme.aside.card_newest_comments.avatar} && array[i].avatar) {
|
if (!{theme.aside.card_newest_comments.avatar} && item.avatar) {
|
||||||
const imgAttr = '!{theme.lazyload.enable && !theme.lazyload.native ? "data-lazy-src" : "src"}'
|
const imgAttr = '!{theme.lazyload.enable && !theme.lazyload.native ? "data-lazy-src" : "src"}'
|
||||||
const lazyloadNative = '!{theme.lazyload.enable && theme.lazyload.native ? "loading=\"lazy\"" : ""}'
|
const lazyloadNative = '!{theme.lazyload.enable && theme.lazyload.native ? "loading=\"lazy\"" : ""}'
|
||||||
result += `<a href="${array[i].url}" class="thumbnail"><img ${imgAttr}="${array[i].avatar}" alt="${array[i].nick}" ${lazyloadNative}></a>`
|
html += `<a href="${escapeHtml(item.url)}" class="thumbnail"><img ${imgAttr}="${escapeHtml(item.avatar)}" alt="${escapeHtml(item.nick)}" ${lazyloadNative}></a>`
|
||||||
}
|
}
|
||||||
|
|
||||||
result += `<div class="content">
|
html += `<div class="content">
|
||||||
<a class="comment" href="${array[i].url}" title="${array[i].content}">${array[i].content}</a>
|
<a class="comment" href="${escapeHtml(item.url)}" title="${escapeHtml(item.content)}">${escapeHtml(item.content)}</a>
|
||||||
<div class="name"><span>${array[i].nick} / </span><time datetime="${array[i].date}">${btf.diffDate(array[i].date, true)}</time></div>
|
<div class="name"><span>${escapeHtml(item.nick)} / </span><time datetime="${escapeHtml(item.date)}">${btf.diffDate(item.date, true)}</time></div>
|
||||||
</div></div>`
|
</div></div>`
|
||||||
}
|
|
||||||
|
return html
|
||||||
|
}).join('')
|
||||||
} else {
|
} else {
|
||||||
result += '!{_p("aside.card_newest_comments.zero")}'
|
result += '!{_p("aside.card_newest_comments.zero")}'
|
||||||
}
|
}
|
||||||
@@ -42,6 +63,21 @@ script.
|
|||||||
window.pjax && window.pjax.refresh(ele)
|
window.pjax && window.pjax.refresh(ele)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
fetchAndRender: (keyName, ele, getComment) => {
|
||||||
|
Promise.resolve(getComment(ele)).then(array => {
|
||||||
|
btf.saveToLocal.set(keyName, JSON.stringify(array), !{theme.aside.card_newest_comments.storage}/(60*24))
|
||||||
|
newestComments.generateHtml(array, ele)
|
||||||
|
}).catch(e => {
|
||||||
|
console.error(e)
|
||||||
|
const data = btf.saveToLocal.get(keyName)
|
||||||
|
if (data) {
|
||||||
|
newestComments.generateHtml(JSON.parse(data), ele)
|
||||||
|
} else {
|
||||||
|
ele.textContent = '!{_p("aside.card_newest_comments.error")}'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
newestCommentInit: (name, getComment) => {
|
newestCommentInit: (name, getComment) => {
|
||||||
const $dom = document.querySelector('#card-newest-comments .aside-list')
|
const $dom = document.querySelector('#card-newest-comments .aside-list')
|
||||||
if ($dom) {
|
if ($dom) {
|
||||||
@@ -49,7 +85,7 @@ script.
|
|||||||
if (data) {
|
if (data) {
|
||||||
newestComments.generateHtml(JSON.parse(data), $dom)
|
newestComments.generateHtml(JSON.parse(data), $dom)
|
||||||
} else {
|
} else {
|
||||||
getComment($dom)
|
newestComments.fetchAndRender(name, $dom, getComment)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
+13
-24
@@ -1,34 +1,23 @@
|
|||||||
!= partial("includes/third-party/newest-comments/common.pug", {}, { cache: true })
|
!= partial("includes/third-party/newest-comments/common.pug", {}, { cache: true })
|
||||||
|
|
||||||
script.
|
script.
|
||||||
window.addEventListener('load', () => {
|
window.newestComments.ready(() => {
|
||||||
const keyName = 'disqus-newest-comments'
|
const keyName = 'disqus-newest-comments'
|
||||||
const { changeContent, generateHtml, run } = window.newestComments
|
const { changeContent, run } = window.newestComments
|
||||||
|
|
||||||
const getComment = ele => {
|
const getComment = () => {
|
||||||
fetch('https://disqus.com/api/3.0/forums/listPosts.json?forum=!{forum}&related=thread&limit=!{newestCommentsLimit}&api_key=!{apiKey}')
|
return fetch('https://disqus.com/api/3.0/forums/listPosts.json?forum=!{forum}&related=thread&limit=!{newestCommentsLimit}&api_key=!{apiKey}')
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => data.response.map(item => {
|
||||||
const disqusArray = data.response.map(item => {
|
return {
|
||||||
return {
|
'avatar': item.author.avatar.cache,
|
||||||
'avatar': item.author.avatar.cache,
|
'content': changeContent(item.message),
|
||||||
'content': changeContent(item.message),
|
'nick': item.author.name,
|
||||||
'nick': item.author.name,
|
'url': item.url,
|
||||||
'url': item.url,
|
'date': item.createdAt
|
||||||
'date': item.createdAt
|
}
|
||||||
}
|
}))
|
||||||
})
|
|
||||||
|
|
||||||
btf.saveToLocal.set(keyName, JSON.stringify(disqusArray), !{theme.aside.card_newest_comments.storage}/(60*24))
|
|
||||||
generateHtml(disqusArray, ele)
|
|
||||||
}).catch(e => {
|
|
||||||
console.error(e)
|
|
||||||
ele.textContent= "!{_p('aside.card_newest_comments.error')}"
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
run(keyName, getComment)
|
run(keyName, getComment)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+32
-45
@@ -1,62 +1,49 @@
|
|||||||
!= partial("includes/third-party/newest-comments/common.pug", {}, { cache: true })
|
!= partial("includes/third-party/newest-comments/common.pug", {}, { cache: true })
|
||||||
|
|
||||||
script.
|
script.
|
||||||
window.addEventListener('load', () => {
|
window.newestComments.ready(() => {
|
||||||
const keyName = 'github-newest-comments'
|
const keyName = 'github-newest-comments'
|
||||||
const { changeContent, generateHtml, run } = window.newestComments
|
const { changeContent, run } = window.newestComments
|
||||||
|
|
||||||
const findTrueUrl = (array, ele) => {
|
const findTrueUrl = array => {
|
||||||
Promise.all(array.map(item =>
|
return Promise.all(array.map(item =>
|
||||||
fetch(item.url).then(resp => resp.json()).then(data => {
|
fetch(item.url)
|
||||||
let urlArray = data.body ? data.body.match(/(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?/ig) : []
|
.then(resp => resp.json())
|
||||||
if (!Array.isArray(urlArray) || urlArray.length === 0) {
|
.then(data => {
|
||||||
urlArray = [`${data.html_url}`]
|
let urlArray = data.body ? data.body.match(/(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?/ig) : []
|
||||||
}
|
if (!Array.isArray(urlArray) || urlArray.length === 0) {
|
||||||
if (data.user.login === 'utterances-bot') {
|
urlArray = [item.html_url]
|
||||||
return urlArray.pop()
|
|
||||||
} else {
|
|
||||||
return urlArray.shift()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)).then(res => {
|
|
||||||
array = array.map((i,index)=> {
|
|
||||||
return {
|
|
||||||
...i,
|
|
||||||
url: res[index]
|
|
||||||
}
|
}
|
||||||
|
const isBot = data.user && data.user.login === 'utterances-bot'
|
||||||
|
return isBot ? urlArray.pop() : urlArray.shift()
|
||||||
})
|
})
|
||||||
|
.catch(() => item.html_url)
|
||||||
btf.saveToLocal.set(keyName, JSON.stringify(array), !{theme.aside.card_newest_comments.storage}/(60*24))
|
)).then(res => array.map((item, index) => {
|
||||||
generateHtml(array, ele)
|
return {
|
||||||
});
|
...item,
|
||||||
|
url: res[index]
|
||||||
|
}
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
const getComment = ele => {
|
const getComment = () => {
|
||||||
fetch('https://api.github.com/repos/!{userRepo}/issues/comments?sort=updated&direction=desc&per_page=!{newestCommentsLimit}&page=1',{
|
return fetch('https://api.github.com/repos/!{userRepo}/issues/comments?sort=updated&direction=desc&per_page=!{newestCommentsLimit}&page=1', {
|
||||||
"headers": {
|
"headers": {
|
||||||
Accept: 'application/vnd.github.v3.html+json'
|
Accept: 'application/vnd.github.v3.html+json'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => data.map(item => {
|
||||||
const githubArray = data.map(item => {
|
return {
|
||||||
return {
|
'avatar': item.user.avatar_url,
|
||||||
'avatar': item.user.avatar_url,
|
'content': changeContent(item.body_html || item.body),
|
||||||
'content': changeContent(item.body_html || item.body),
|
'nick': item.user.login,
|
||||||
'nick': item.user.login,
|
'url': item.html_url,
|
||||||
'url': item.issue_url,
|
'date': item.updated_at
|
||||||
'date': item.updated_at
|
}
|
||||||
}
|
}))
|
||||||
})
|
.then(findTrueUrl)
|
||||||
findTrueUrl(githubArray, ele)
|
|
||||||
}).catch(e => {
|
|
||||||
console.error(e)
|
|
||||||
ele.textContent= "!{_p('aside.card_newest_comments.error')}"
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
run(keyName, getComment)
|
run(keyName, getComment)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+13
-20
@@ -2,29 +2,22 @@
|
|||||||
!= partial("includes/third-party/newest-comments/common.pug", {}, { cache: true })
|
!= partial("includes/third-party/newest-comments/common.pug", {}, { cache: true })
|
||||||
|
|
||||||
script.
|
script.
|
||||||
window.addEventListener('load', () => {
|
window.newestComments.ready(() => {
|
||||||
const keyName = 'remark42-newest-comments'
|
const keyName = 'remark42-newest-comments'
|
||||||
const { changeContent, generateHtml, run } = window.newestComments
|
const { changeContent, run } = window.newestComments
|
||||||
|
|
||||||
const getComment = ele => {
|
const getComment = () => {
|
||||||
fetch('!{host}/api/v1/last/!{newestCommentsLimit}?site=!{siteId}')
|
return fetch('!{host}/api/v1/last/!{newestCommentsLimit}?site=!{siteId}')
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => data.map(e => {
|
||||||
const remark42 = data.map(e => {
|
return {
|
||||||
return {
|
'avatar': e.user.picture,
|
||||||
'avatar': e.user.picture,
|
'content': changeContent(e.text),
|
||||||
'content': changeContent(e.text),
|
'nick': e.user.name,
|
||||||
'nick': e.user.name,
|
'url': e.locator.url,
|
||||||
'url': e.locator.url,
|
'date': e.time,
|
||||||
'date': e.time,
|
}
|
||||||
}
|
}))
|
||||||
})
|
|
||||||
btf.saveToLocal.set(keyName, JSON.stringify(remark42), !{theme.aside.card_newest_comments.storage}/(60*24))
|
|
||||||
generateHtml(remark42, ele)
|
|
||||||
}).catch(e => {
|
|
||||||
console.error(e)
|
|
||||||
ele.textContent= "!{_p('aside.card_newest_comments.error')}"
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
run(keyName, getComment)
|
run(keyName, getComment)
|
||||||
|
|||||||
+20
-33
@@ -1,45 +1,32 @@
|
|||||||
!= partial("includes/third-party/newest-comments/common.pug", {}, { cache: true })
|
!= partial("includes/third-party/newest-comments/common.pug", {}, { cache: true })
|
||||||
|
|
||||||
script.
|
script.
|
||||||
window.addEventListener('load', () => {
|
window.newestComments.ready(() => {
|
||||||
const keyName = 'twikoo-newest-comments'
|
const keyName = 'twikoo-newest-comments'
|
||||||
const { changeContent, generateHtml, run } = window.newestComments
|
const { changeContent, run } = window.newestComments
|
||||||
|
|
||||||
const getComment = ele => {
|
const getComment = async () => {
|
||||||
const runTwikoo = () => {
|
const runTwikoo = () => twikoo.getRecentComments({
|
||||||
twikoo.getRecentComments({
|
envId: '!{theme.twikoo.envId}',
|
||||||
envId: '!{theme.twikoo.envId}',
|
region: '!{theme.twikoo.region}',
|
||||||
region: '!{theme.twikoo.region}',
|
pageSize: !{newestCommentsLimit},
|
||||||
pageSize: !{newestCommentsLimit},
|
includeReply: true
|
||||||
includeReply: true
|
}).then(res => res.map(e => {
|
||||||
}).then(res => {
|
return {
|
||||||
const twikooArray = res.map(e => {
|
'content': changeContent(e.comment),
|
||||||
return {
|
'avatar': e.avatar,
|
||||||
'content': changeContent(e.comment),
|
'nick': e.nick,
|
||||||
'avatar': e.avatar,
|
'url': (e.url || '') + '#' + e.id,
|
||||||
'nick': e.nick,
|
'date': new Date(e.created).toISOString()
|
||||||
'url': e.url + '#' + e.id,
|
}
|
||||||
'date': new Date(e.created).toISOString()
|
}))
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
btf.saveToLocal.set(keyName, JSON.stringify(twikooArray), !{theme.aside.card_newest_comments.storage}/(60*24))
|
if (typeof twikoo === 'undefined') {
|
||||||
generateHtml(twikooArray, ele)
|
await btf.getScript('!{url_for(theme.asset.twikoo)}')
|
||||||
}).catch(err => {
|
|
||||||
console.error(err)
|
|
||||||
ele.textContent= "!{_p('aside.card_newest_comments.error')}"
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof twikoo === 'object') {
|
return runTwikoo()
|
||||||
runTwikoo()
|
|
||||||
} else {
|
|
||||||
btf.getScript('!{url_for(theme.asset.twikoo)}').then(runTwikoo)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
run(keyName, getComment)
|
run(keyName, getComment)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+15
-22
@@ -4,9 +4,9 @@ script(src=url_for(theme.asset.blueimp_md5))
|
|||||||
!= partial("includes/third-party/newest-comments/common.pug", {}, { cache: true })
|
!= partial("includes/third-party/newest-comments/common.pug", {}, { cache: true })
|
||||||
|
|
||||||
script.
|
script.
|
||||||
window.addEventListener('load', () => {
|
window.newestComments.ready(() => {
|
||||||
const keyName = 'valine-newest-comments'
|
const keyName = 'valine-newest-comments'
|
||||||
const { changeContent, generateHtml, run } = window.newestComments
|
const { changeContent, run } = window.newestComments
|
||||||
|
|
||||||
const getIcon = (icon, mail) => {
|
const getIcon = (icon, mail) => {
|
||||||
if (icon) return icon
|
if (icon) return icon
|
||||||
@@ -15,10 +15,10 @@ script.
|
|||||||
return iconUrl
|
return iconUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
const getComment = ele => {
|
const getComment = async () => {
|
||||||
const serverURL = '!{theme.valine.serverURLs || `https://${theme.valine.appId.substring(0,8)}.api.lncldglobal.com` }'
|
const serverURL = '!{theme.valine.serverURLs || `https://${theme.valine.appId.substring(0,8)}.api.lncldglobal.com` }'
|
||||||
|
|
||||||
var settings = {
|
const settings = {
|
||||||
"method": "GET",
|
"method": "GET",
|
||||||
"headers": {
|
"headers": {
|
||||||
"X-LC-Id": '!{theme.valine.appId}',
|
"X-LC-Id": '!{theme.valine.appId}',
|
||||||
@@ -27,24 +27,17 @@ script.
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch(`${serverURL}/1.1/classes/Comment?limit=!{newestCommentsLimit}&order=-createdAt`,settings)
|
const res = await fetch(`${serverURL}/1.1/classes/Comment?limit=!{newestCommentsLimit}&order=-createdAt`, settings)
|
||||||
.then(response => response.json())
|
const data = await res.json()
|
||||||
.then(data => {
|
return data.results.map(e => {
|
||||||
const valineArray = data.results.map(e => {
|
return {
|
||||||
return {
|
'avatar': getIcon(e.QQAvatar, e.mail),
|
||||||
'avatar': getIcon(e.QQAvatar, e.mail),
|
'content': changeContent(e.comment),
|
||||||
'content': changeContent(e.comment),
|
'nick': e.nick,
|
||||||
'nick': e.nick,
|
'url': (e.url || '') + '#' + e.objectId,
|
||||||
'url': e.url + '#' + e.objectId,
|
'date': e.updatedAt,
|
||||||
'date': e.updatedAt,
|
}
|
||||||
}
|
})
|
||||||
})
|
|
||||||
btf.saveToLocal.set(keyName, JSON.stringify(valineArray), !{theme.aside.card_newest_comments.storage}/(60*24))
|
|
||||||
generateHtml(valineArray, ele)
|
|
||||||
}).catch(e => {
|
|
||||||
console.error(e)
|
|
||||||
ele.textContent= "!{_p('aside.card_newest_comments.error')}"
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
run(keyName, getComment)
|
run(keyName, getComment)
|
||||||
|
|||||||
+15
-22
@@ -1,31 +1,24 @@
|
|||||||
- const serverURL = theme.waline.serverURL.replace(/\/$/, '')
|
- const serverURL = theme.waline.serverURL ? theme.waline.serverURL.replace(/\/$/, '') : ''
|
||||||
|
|
||||||
!= partial("includes/third-party/newest-comments/common.pug", {}, { cache: true })
|
!= partial("includes/third-party/newest-comments/common.pug", {}, { cache: true })
|
||||||
|
|
||||||
script.
|
script.
|
||||||
window.addEventListener('load', () => {
|
window.newestComments.ready(() => {
|
||||||
const keyName = 'waline-newest-comments'
|
const keyName = 'waline-newest-comments'
|
||||||
const { changeContent, generateHtml, run } = window.newestComments
|
const { changeContent, run } = window.newestComments
|
||||||
|
|
||||||
const getComment = async (ele) => {
|
const getComment = async () => {
|
||||||
try {
|
const res = await fetch('!{serverURL}/api/comment?type=recent&count=!{newestCommentsLimit}')
|
||||||
const res = await fetch('!{serverURL}/api/comment?type=recent&count=!{newestCommentsLimit}')
|
const result = await res.json()
|
||||||
const result = await res.json()
|
return result.data.map(e => {
|
||||||
const walineArray = result.data.map(e => {
|
return {
|
||||||
return {
|
'content': changeContent(e.comment),
|
||||||
'content': changeContent(e.comment),
|
'avatar': e.avatar,
|
||||||
'avatar': e.avatar,
|
'nick': e.nick,
|
||||||
'nick': e.nick,
|
'url': (e.url || '') + '#' + e.objectId,
|
||||||
'url': e.url + '#' + e.objectId,
|
'date': e.time || e.insertedAt
|
||||||
'date': e.time || e.insertedAt
|
}
|
||||||
}
|
})
|
||||||
})
|
|
||||||
btf.saveToLocal.set(keyName, JSON.stringify(walineArray), !{theme.aside.card_newest_comments.storage}/(60*24))
|
|
||||||
generateHtml(walineArray, ele)
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err)
|
|
||||||
ele.textContent= "!{_p('aside.card_newest_comments.error')}"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
run(keyName, getComment)
|
run(keyName, getComment)
|
||||||
|
|||||||
+52
-45
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
script.
|
script.
|
||||||
window.typedJSFn = {
|
window.typedJSFn = {
|
||||||
|
_seq: 0,
|
||||||
init: str => {
|
init: str => {
|
||||||
window.typed = new Typed('#subtitle', Object.assign({
|
window.typed = new Typed('#subtitle', Object.assign({
|
||||||
strings: str,
|
strings: str,
|
||||||
@@ -13,15 +14,32 @@ script.
|
|||||||
}, !{JSON.stringify(typed_option)}))
|
}, !{JSON.stringify(typed_option)}))
|
||||||
},
|
},
|
||||||
run: subtitleType => {
|
run: subtitleType => {
|
||||||
if (!{effect}) {
|
const seq = ++window.typedJSFn._seq
|
||||||
if (typeof Typed === 'function') {
|
const invoke = () => {
|
||||||
subtitleType()
|
if (seq === window.typedJSFn._seq) subtitleType()
|
||||||
} else {
|
|
||||||
btf.getScript('!{url_for(theme.asset.typed)}').then(subtitleType)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
subtitleType()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!{effect}) {
|
||||||
|
if (typeof Typed === 'function') invoke()
|
||||||
|
else btf.getScript('!{url_for(theme.asset.typed)}').then(invoke)
|
||||||
|
} else {
|
||||||
|
invoke()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fetchSubtitle: (fetcher, parser) => {
|
||||||
|
const seq = window.typedJSFn._seq
|
||||||
|
Promise.resolve()
|
||||||
|
.then(fetcher)
|
||||||
|
.then(parser)
|
||||||
|
.then(({ content, extra = [] }) => {
|
||||||
|
if (seq !== window.typedJSFn._seq) return
|
||||||
|
typedJSFn.processSubtitle(content, extra)
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
if (seq !== window.typedJSFn._seq) return
|
||||||
|
console.error('Failed to get the subtitle API:', err)
|
||||||
|
typedJSFn.processSubtitle(!{JSON.stringify(subContent)})
|
||||||
|
})
|
||||||
},
|
},
|
||||||
processSubtitle: (content, extraContents = []) => {
|
processSubtitle: (content, extraContents = []) => {
|
||||||
if (!{effect}) {
|
if (!{effect}) {
|
||||||
@@ -44,63 +62,52 @@ script.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
btf.addGlobalFn('pjaxSendOnce', () => { typed && typed.destroy() }, 'typedDestroy')
|
btf.addGlobalFn('pjaxSendOnce', () => {
|
||||||
|
window.typedJSFn._seq++
|
||||||
|
typed && typed.destroy()
|
||||||
|
window.typed = null
|
||||||
|
}, 'typedDestroy')
|
||||||
|
|
||||||
case source
|
case source
|
||||||
when 1
|
when 1
|
||||||
script.
|
script.
|
||||||
function subtitleType () {
|
function subtitleType () {
|
||||||
fetch('https://v1.hitokoto.cn')
|
typedJSFn.fetchSubtitle(
|
||||||
.then(response => response.json())
|
() => fetch('https://v1.hitokoto.cn').then(response => response.json()),
|
||||||
.then(data => {
|
data => ({ content: data.hitokoto, extra: data.from ? ['出自 ' + data.from] : [] })
|
||||||
const from = data.from ? '出自 ' + data.from : ''
|
)
|
||||||
typedJSFn.processSubtitle(data.hitokoto, from ? [from] : [])
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
console.error('Failed to get the Hitokoto API:', err)
|
|
||||||
typedJSFn.processSubtitle(!{JSON.stringify(subContent)})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
typedJSFn.run(subtitleType)
|
typedJSFn.run(subtitleType)
|
||||||
|
|
||||||
when 2
|
when 2
|
||||||
script.
|
script.
|
||||||
function subtitleType () {
|
function subtitleType () {
|
||||||
fetch('https://v.api.aa1.cn/api/yiyan/index.php')
|
typedJSFn.fetchSubtitle(
|
||||||
.then(response => response.text())
|
() => fetch('https://v.api.aa1.cn/api/yiyan/index.php').then(response => response.text()),
|
||||||
.then(data => {
|
data => {
|
||||||
const reg = /<p>(.*?)<\/p>/g
|
const reg = /<p>(.*?)<\/p>/g
|
||||||
const result = reg.exec(data)
|
const result = reg.exec(data)
|
||||||
if (result && result[1]) {
|
if (result && result[1]) return { content: result[1] }
|
||||||
typedJSFn.processSubtitle(result[1])
|
throw new Error('Failed to parse the return value of the Yiyan API')
|
||||||
} 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)})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
typedJSFn.run(subtitleType)
|
typedJSFn.run(subtitleType)
|
||||||
|
|
||||||
when 3
|
when 3
|
||||||
script.
|
script.
|
||||||
function subtitleType () {
|
function subtitleType () {
|
||||||
btf.getScript('https://sdk.jinrishici.com/v2/browser/jinrishici.js')
|
typedJSFn.fetchSubtitle(
|
||||||
.then(() => {
|
() => btf.getScript('https://sdk.jinrishici.com/v2/browser/jinrishici.js').then(() =>
|
||||||
jinrishici.load(result => {
|
new Promise((resolve, reject) => {
|
||||||
if (result && result.data && result.data.content) {
|
jinrishici.load(result => {
|
||||||
typedJSFn.processSubtitle(result.data.content)
|
if (result && result.data && result.data.content) resolve(result.data.content)
|
||||||
} else {
|
else reject(new Error('Failed to parse the return value of Jinrishici API'))
|
||||||
throw new Error('Failed to parse the return value of Jinrishici API')
|
})
|
||||||
}
|
|
||||||
})
|
})
|
||||||
})
|
),
|
||||||
.catch(err => {
|
content => ({ content })
|
||||||
console.error('Failed to get the Jinrishici API:', err)
|
)
|
||||||
typedJSFn.processSubtitle(!{JSON.stringify(subContent)})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
typedJSFn.run(subtitleType)
|
typedJSFn.run(subtitleType)
|
||||||
|
|
||||||
|
|||||||
+32
-38
@@ -30,29 +30,25 @@ script.
|
|||||||
|
|
||||||
const extractValue = stat => (stat && typeof stat.value !== 'undefined') ? stat.value : stat
|
const extractValue = stat => (stat && typeof stat.value !== 'undefined') ? stat.value : stat
|
||||||
|
|
||||||
const getData = async (isPost) => {
|
const getData = async (isPost, path = window.location.pathname) => {
|
||||||
try {
|
const now = Date.now()
|
||||||
const now = Date.now()
|
const pathQuery = isPost ? `&url=${path}&path=${path}` : ''
|
||||||
const pathQuery = isPost ? `&url=${window.location.pathname}&path=${window.location.pathname}` : ''
|
const headers = {
|
||||||
const headers = {
|
'Accept': 'application/json',
|
||||||
'Accept': 'application/json',
|
[!{isServerURL} ? 'Authorization' : 'x-umami-api-key']: !{isServerURL} ? `Bearer ${config.token}` : config.token
|
||||||
[!{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}${pathQuery}`, {
|
|
||||||
method: 'GET',
|
|
||||||
headers
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!res.ok) {
|
|
||||||
throw new Error(`HTTP error! status: ${res.status}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
return await res.json()
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Umami Analytics: Failed to fetch data', error)
|
|
||||||
throw error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const res = await fetch(`!{apiUrl}/websites/!{website_id}/stats?startAt=0000000000&endAt=${now}${pathQuery}`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers,
|
||||||
|
cache: 'no-store'
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${res.status}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return await res.json()
|
||||||
}
|
}
|
||||||
|
|
||||||
const insertData = async () => {
|
const insertData = async () => {
|
||||||
@@ -63,7 +59,8 @@ script.
|
|||||||
tasks.push((async () => {
|
tasks.push((async () => {
|
||||||
const pagePV = document.getElementById('umamiPV')
|
const pagePV = document.getElementById('umamiPV')
|
||||||
if (pagePV) {
|
if (pagePV) {
|
||||||
const data = await getData(true)
|
const path = pagePV.dataset.path || window.location.pathname
|
||||||
|
const data = await getData(true, path)
|
||||||
if (data && data.pageviews) {
|
if (data && data.pageviews) {
|
||||||
pagePV.textContent = extractValue(data.pageviews)
|
pagePV.textContent = extractValue(data.pageviews)
|
||||||
} else {
|
} else {
|
||||||
@@ -73,26 +70,23 @@ script.
|
|||||||
})())
|
})())
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.site_uv || config.site_pv) {
|
const siteUV = config.site_uv ? document.getElementById('umami-site-uv') : null
|
||||||
|
const sitePV = config.site_pv ? document.getElementById('umami-site-pv') : null
|
||||||
|
|
||||||
|
if (siteUV || sitePV) {
|
||||||
tasks.push((async () => {
|
tasks.push((async () => {
|
||||||
const data = await getData(false)
|
const data = await getData(false)
|
||||||
|
|
||||||
if (config.site_uv) {
|
if (siteUV && data && data.visitors) {
|
||||||
const siteUV = document.getElementById('umami-site-uv')
|
siteUV.textContent = extractValue(data.visitors)
|
||||||
if (siteUV && data && data.visitors) {
|
} else if (siteUV) {
|
||||||
siteUV.textContent = extractValue(data.visitors)
|
console.warn('Umami Analytics: Invalid site UV data received')
|
||||||
} else if (siteUV) {
|
|
||||||
console.warn('Umami Analytics: Invalid site UV data received')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.site_pv) {
|
if (sitePV && data && data.pageviews) {
|
||||||
const sitePV = document.getElementById('umami-site-pv')
|
sitePV.textContent = extractValue(data.pageviews)
|
||||||
if (sitePV && data && data.pageviews) {
|
} else if (sitePV) {
|
||||||
sitePV.textContent = extractValue(data.pageviews)
|
console.warn('Umami Analytics: Invalid site PV data received')
|
||||||
} else if (sitePV) {
|
|
||||||
console.warn('Umami Analytics: Invalid site PV data received')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})())
|
})())
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "hexo-theme-butterfly",
|
"name": "hexo-theme-butterfly",
|
||||||
"version": "5.7.0",
|
"version": "5.7.1.260810",
|
||||||
"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": {
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ hexo.extend.filter.register('before_generate', () => {
|
|||||||
return Object.keys(data).reduce((result, key) => {
|
return Object.keys(data).reduce((result, key) => {
|
||||||
let { name, version, file, other_name: otherName } = data[key]
|
let { name, version, file, other_name: otherName } = data[key]
|
||||||
const cdnjsName = otherName || name
|
const cdnjsName = otherName || name
|
||||||
const cdnjsFile = file.replace(/^[lib|dist]*\/|browser\//g, '')
|
const cdnjsFile = file.replace(/^(?:lib|dist)\/|^browser\//, '')
|
||||||
const minCdnjsFile = minFile(cdnjsFile)
|
const minCdnjsFile = minFile(cdnjsFile)
|
||||||
if (cond === 'internal') file = `source/${file}`
|
if (cond === 'internal') file = `source/${file}`
|
||||||
const minFilePath = minFile(file)
|
const minFilePath = minFile(file)
|
||||||
|
|||||||
@@ -11,13 +11,17 @@ hexo.extend.helper.register('inject_head_js', function () {
|
|||||||
const createCustomJs = () => `
|
const createCustomJs = () => `
|
||||||
const saveToLocal = {
|
const saveToLocal = {
|
||||||
set: (key, value, ttl) => {
|
set: (key, value, ttl) => {
|
||||||
const data = { value }
|
try {
|
||||||
|
const data = { value }
|
||||||
|
|
||||||
if (ttl != null) {
|
if (ttl != null) {
|
||||||
data.expiry = Date.now() + ttl * 86400000
|
data.expiry = Date.now() + ttl * 86400000
|
||||||
|
}
|
||||||
|
|
||||||
|
localStorage.setItem(key, JSON.stringify(data))
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
localStorage.setItem(key, JSON.stringify(data))
|
|
||||||
},
|
},
|
||||||
get: key => {
|
get: key => {
|
||||||
const itemStr = localStorage.getItem(key)
|
const itemStr = localStorage.getItem(key)
|
||||||
|
|||||||
+66
-5
@@ -1,7 +1,7 @@
|
|||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
const { truncateContent, postDesc } = require('../common/postDesc')
|
const { truncateContent, postDesc } = require('../common/postDesc')
|
||||||
const { prettyUrls } = require('hexo-util')
|
const { prettyUrls, stripHTML } = require('hexo-util')
|
||||||
const crypto = require('crypto')
|
const crypto = require('crypto')
|
||||||
const moment = require('moment-timezone')
|
const moment = require('moment-timezone')
|
||||||
|
|
||||||
@@ -9,7 +9,7 @@ const absoluteUrlPattern = /^(?:[a-z][a-z\d+.-]*:)?\/\//i
|
|||||||
const relativeUrlPattern = /^(\.\/|\.\.\/|\/|[^/]+\/).*$/
|
const relativeUrlPattern = /^(\.\/|\.\.\/|\/|[^/]+\/).*$/
|
||||||
const colorPattern = /^(#|rgb|rgba|hsl|hsla)/i
|
const colorPattern = /^(#|rgb|rgba|hsl|hsla)/i
|
||||||
const simpleFilePattern = /\.(png|jpg|jpeg|gif|bmp|webp|svg|tiff)$/i
|
const simpleFilePattern = /\.(png|jpg|jpeg|gif|bmp|webp|svg|tiff)$/i
|
||||||
const archiveRegex = /\/archives\//
|
const escapeRegex = value => String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||||
|
|
||||||
const { version: themeVersion } = require('../../package.json')
|
const { version: themeVersion } = require('../../package.json')
|
||||||
|
|
||||||
@@ -27,6 +27,16 @@ hexo.extend.helper.register('cloudTags', function (options = {}) {
|
|||||||
source = source.limit(limit)
|
source = source.limit(limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const shuffle = list => {
|
||||||
|
for (let i = list.length - 1; i > 0; i--) {
|
||||||
|
const j = Math.floor(Math.random() * (i + 1))
|
||||||
|
const temp = list[i]
|
||||||
|
list[i] = list[j]
|
||||||
|
list[j] = temp
|
||||||
|
}
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
const sizes = [...new Set(source.map(tag => tag.length).sort((a, b) => a - b))]
|
const sizes = [...new Set(source.map(tag => tag.length).sort((a, b) => a - b))]
|
||||||
const sizeMap = new Map(sizes.map((size, index) => [size, index]))
|
const sizeMap = new Map(sizes.map((size, index) => [size, index]))
|
||||||
const length = sizes.length - 1
|
const length = sizes.length - 1
|
||||||
@@ -65,7 +75,11 @@ hexo.extend.helper.register('cloudTags', function (options = {}) {
|
|||||||
return `font-size: ${parseFloat(size.toFixed(2))}${unit}; ${colorStyle}`
|
return `font-size: ${parseFloat(size.toFixed(2))}${unit}; ${colorStyle}`
|
||||||
}
|
}
|
||||||
|
|
||||||
return source.sort(orderby, order).map((tag, idx) => {
|
const sortedSource = orderby === 'random'
|
||||||
|
? shuffle(typeof source.toArray === 'function' ? source.toArray() : Array.from(source))
|
||||||
|
: source.sort(orderby, order)
|
||||||
|
|
||||||
|
return sortedSource.map((tag, idx) => {
|
||||||
const ratio = length ? sizeMap.get(tag.length) / length : 0
|
const ratio = length ? sizeMap.get(tag.length) / length : 0
|
||||||
const size = minfontsize + ((maxfontsize - minfontsize) * ratio)
|
const size = minfontsize + ((maxfontsize - minfontsize) * ratio)
|
||||||
|
|
||||||
@@ -102,6 +116,11 @@ hexo.extend.helper.register('findArchivesTitle', function (page, menu, date) {
|
|||||||
|
|
||||||
const defaultTitle = this._p('page.archives')
|
const defaultTitle = this._p('page.archives')
|
||||||
if (!menu) return defaultTitle
|
if (!menu) return defaultTitle
|
||||||
|
const archiveDir = String(hexo.config.archive_dir || 'archives').replace(/^\/+|\/+$/g, '')
|
||||||
|
const archivePath = this.url_for(archiveDir)
|
||||||
|
const normalizedArchivePath = archivePath.endsWith('/') ? archivePath : `${archivePath}/`
|
||||||
|
const archivePathRegex = new RegExp(`${escapeRegex(normalizedArchivePath)}(?:$|[?#])`)
|
||||||
|
const archiveDirRegex = new RegExp(`/${escapeRegex(archiveDir)}/(?:$|[?#])`)
|
||||||
|
|
||||||
const loop = m => {
|
const loop = m => {
|
||||||
for (const [key, value] of Object.entries(m)) {
|
for (const [key, value] of Object.entries(m)) {
|
||||||
@@ -110,7 +129,7 @@ hexo.extend.helper.register('findArchivesTitle', function (page, menu, date) {
|
|||||||
if (result) return result
|
if (result) return result
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof value === 'string' && archiveRegex.test(value)) {
|
if (typeof value === 'string' && (archivePathRegex.test(value) || archiveDirRegex.test(value))) {
|
||||||
return key
|
return key
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -156,7 +175,9 @@ hexo.extend.helper.register('shuoshuoFN', (data, page) => {
|
|||||||
const timezone = hexo.config.timezone
|
const timezone = hexo.config.timezone
|
||||||
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 = timezone
|
||||||
|
? moment.tz(parsed.format('YYYY-MM-DD HH:mm:ss'), timezone).format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
: parsed.format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
|
||||||
// Render the content using Hexo's rendering engine to process any tags or markdown
|
// Render the content using Hexo's rendering engine to process any tags or markdown
|
||||||
const mockPost = {
|
const mockPost = {
|
||||||
@@ -222,3 +243,43 @@ hexo.extend.helper.register('safeJSON', data => {
|
|||||||
.replace(/\u2028/g, '\\u2028')
|
.replace(/\u2028/g, '\\u2028')
|
||||||
.replace(/\u2029/g, '\\u2029')
|
.replace(/\u2029/g, '\\u2029')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const charBasedRange = /[\u4e00-\u9fff\u3400-\u4dbf\uf900-\ufaff\u3040-\u309f\u30a0-\u30ff\uac00-\ud7af\u1100-\u11ff]/g
|
||||||
|
|
||||||
|
const countWords = text => {
|
||||||
|
if (!text) return 0
|
||||||
|
const charCount = (text.match(charBasedRange) || []).length
|
||||||
|
const remaining = text.replace(charBasedRange, ' ').trim()
|
||||||
|
const wordCount = remaining ? remaining.split(/\s+/).length : 0
|
||||||
|
return charCount + wordCount
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatCount = num => {
|
||||||
|
if (num >= 100000) {
|
||||||
|
return Math.round(num / 1000) + 'k'
|
||||||
|
}
|
||||||
|
return num
|
||||||
|
}
|
||||||
|
|
||||||
|
hexo.extend.helper.register('wordcount', page => {
|
||||||
|
return formatCount(countWords(stripHTML(page.encrypt ? page.origin : page.content || '')))
|
||||||
|
})
|
||||||
|
|
||||||
|
hexo.extend.helper.register('min2read', (page, options = {}) => {
|
||||||
|
const { cn = 300, en = 160 } = options
|
||||||
|
const text = stripHTML(page.encrypt ? page.origin : page.content || '')
|
||||||
|
const charCount = (text.match(charBasedRange) || []).length
|
||||||
|
const remaining = text.replace(charBasedRange, ' ').trim()
|
||||||
|
const wordCount = remaining ? remaining.split(/\s+/).length : 0
|
||||||
|
const minutes = Math.ceil(charCount / cn + wordCount / en)
|
||||||
|
return minutes < 1 ? 1 : minutes
|
||||||
|
})
|
||||||
|
|
||||||
|
hexo.extend.helper.register('totalcount', site => {
|
||||||
|
if (!site || !site.posts) return 0
|
||||||
|
let total = 0
|
||||||
|
site.posts.forEach(post => {
|
||||||
|
total += countWords(stripHTML(post.encrypt ? post.origin : post.content || ''))
|
||||||
|
})
|
||||||
|
return formatCount(total)
|
||||||
|
})
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ const chartjs = (args, content) => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const chartConfig = chartMatch && chartMatch[1] ? chartMatch[1] : ''
|
const chartConfig = chartMatch[1] || ''
|
||||||
const descContent = descMatch && descMatch[1] ? descMatch[1] : ''
|
const descContent = descMatch && descMatch[1] ? descMatch[1] : ''
|
||||||
|
|
||||||
const renderedDesc = descContent ? hexo.render.renderSync({ text: descContent, engine: 'markdown' }).trim() : ''
|
const renderedDesc = descContent ? hexo.render.renderSync({ text: descContent, engine: 'markdown' }).trim() : ''
|
||||||
|
|||||||
+9
-3
@@ -622,7 +622,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
const currentTop = window.scrollY || document.documentElement.scrollTop
|
const currentTop = window.scrollY || document.documentElement.scrollTop
|
||||||
|
|
||||||
if (isToc && GLOBAL_CONFIG.percent.toc) {
|
if (isToc && GLOBAL_CONFIG.percent.toc) {
|
||||||
$tocPercentage.textContent = btf.getScrollPercent(currentTop, $article)
|
$tocPercentage.textContent = btf.getScrollPercent(currentTop, $article, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentTop === 0) {
|
if (currentTop === 0) {
|
||||||
@@ -662,6 +662,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
*/
|
*/
|
||||||
const rightSideFn = {
|
const rightSideFn = {
|
||||||
readmode: () => { // read mode
|
readmode: () => { // read mode
|
||||||
|
if (document.querySelector('.exit-readmode')) return
|
||||||
const $body = document.body
|
const $body = document.body
|
||||||
const newEle = document.createElement('button')
|
const newEle = document.createElement('button')
|
||||||
|
|
||||||
@@ -677,6 +678,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
newEle.innerHTML = '<i class="fas fa-sign-out-alt"></i>'
|
newEle.innerHTML = '<i class="fas fa-sign-out-alt"></i>'
|
||||||
newEle.addEventListener('click', exitReadMode)
|
newEle.addEventListener('click', exitReadMode)
|
||||||
$body.appendChild(newEle)
|
$body.appendChild(newEle)
|
||||||
|
btf.addGlobalFn('pjaxSendOnce', exitReadMode, 'exitReadMode')
|
||||||
},
|
},
|
||||||
darkmode: () => { // switch between light and dark mode
|
darkmode: () => { // switch between light and dark mode
|
||||||
const willChangeMode = document.documentElement.getAttribute('data-theme') === 'dark' ? 'light' : 'dark'
|
const willChangeMode = document.documentElement.getAttribute('data-theme') === 'dark' ? 'light' : 'dark'
|
||||||
@@ -1010,8 +1012,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
// 處理 hexo-blog-encrypt 事件
|
// 處理 hexo-blog-encrypt 事件
|
||||||
window.addEventListener('hexo-blog-decrypt', e => {
|
window.addEventListener('hexo-blog-decrypt', e => {
|
||||||
forPostFn()
|
forPostFn()
|
||||||
window.translateFn.translateInitialization()
|
if (window.translateFn && typeof window.translateFn.translateInitialization === 'function') {
|
||||||
Object.values(window.globalFn.encrypt).forEach(fn => {
|
window.translateFn.translateInitialization()
|
||||||
|
}
|
||||||
|
|
||||||
|
const encryptFn = window.globalFn && window.globalFn.encrypt ? window.globalFn.encrypt : {}
|
||||||
|
Object.values(encryptFn).forEach(fn => {
|
||||||
fn()
|
fn()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
+4
-2
@@ -19,12 +19,14 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
return txt
|
return txt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const skippedTranslateTags = ['BR', 'HR', 'SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'CODE', 'PRE']
|
||||||
|
|
||||||
const translateBody = fobj => {
|
const translateBody = fobj => {
|
||||||
const nodes = typeof fobj === 'object' ? fobj.childNodes : document.body.childNodes
|
const nodes = typeof fobj === 'object' ? fobj.childNodes : document.body.childNodes
|
||||||
|
|
||||||
for (const node of nodes) {
|
for (const node of nodes) {
|
||||||
// Skip BR, HR tags, or the translate button object
|
// Skip non-translatable tags or the translate button object
|
||||||
if (['BR', 'HR'].includes(node.tagName) || node === translateButtonObject) continue
|
if (skippedTranslateTags.includes(node.tagName) || node === translateButtonObject) continue
|
||||||
|
|
||||||
if (node.nodeType === Node.ELEMENT_NODE) {
|
if (node.nodeType === Node.ELEMENT_NODE) {
|
||||||
const { tagName, title, alt, placeholder, value, type } = node
|
const { tagName, title, alt, placeholder, value, type } = node
|
||||||
|
|||||||
+57
-41
@@ -56,43 +56,30 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
overflowPaddingR: (() => {
|
overflowPaddingR: {
|
||||||
let headerElement = null
|
add: () => {
|
||||||
let menuElement = null
|
const paddingRight = window.innerWidth - document.body.clientWidth
|
||||||
|
|
||||||
const getElements = () => {
|
if (paddingRight > 0) {
|
||||||
if (!headerElement) {
|
document.body.style.paddingRight = `${paddingRight}px`
|
||||||
headerElement = document.getElementById('page-header')
|
document.body.style.overflow = 'hidden'
|
||||||
}
|
const header = document.getElementById('page-header')
|
||||||
if (!menuElement) {
|
const menu = document.getElementById('menus')
|
||||||
menuElement = document.getElementById('menus')
|
|
||||||
}
|
|
||||||
return { headerElement, menuElement }
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
add: () => {
|
|
||||||
const paddingRight = window.innerWidth - document.body.clientWidth
|
|
||||||
|
|
||||||
if (paddingRight > 0) {
|
|
||||||
document.body.style.paddingRight = `${paddingRight}px`
|
|
||||||
document.body.style.overflow = 'hidden'
|
|
||||||
const { headerElement: header, menuElement: menu } = getElements()
|
|
||||||
if (header && menu && header.classList.contains('nav-fixed')) {
|
|
||||||
menu.style.paddingRight = `${paddingRight}px`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
remove: () => {
|
|
||||||
document.body.style.paddingRight = ''
|
|
||||||
document.body.style.overflow = ''
|
|
||||||
const { headerElement: header, menuElement: menu } = getElements()
|
|
||||||
if (header && menu && header.classList.contains('nav-fixed')) {
|
if (header && menu && header.classList.contains('nav-fixed')) {
|
||||||
menu.style.paddingRight = ''
|
menu.style.paddingRight = `${paddingRight}px`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
remove: () => {
|
||||||
|
document.body.style.paddingRight = ''
|
||||||
|
document.body.style.overflow = ''
|
||||||
|
const header = document.getElementById('page-header')
|
||||||
|
const menu = document.getElementById('menus')
|
||||||
|
if (header && menu && header.classList.contains('nav-fixed')) {
|
||||||
|
menu.style.paddingRight = ''
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})(),
|
},
|
||||||
|
|
||||||
snackbarShow: (text, showAction = false, duration = 2000) => {
|
snackbarShow: (text, showAction = false, duration = 2000) => {
|
||||||
const { position, bgLight, bgDark } = GLOBAL_CONFIG.Snackbar
|
const { position, bgLight, bgDark } = GLOBAL_CONFIG.Snackbar
|
||||||
@@ -200,7 +187,12 @@
|
|||||||
const service = GLOBAL_CONFIG.lightbox
|
const service = GLOBAL_CONFIG.lightbox
|
||||||
|
|
||||||
if (service === 'medium_zoom') {
|
if (service === 'medium_zoom') {
|
||||||
mediumZoom(ele, { background: 'var(--zoom-bg)' })
|
const zoom = window.mediumZoomInstance || (window.mediumZoomInstance = mediumZoom({ background: 'var(--zoom-bg)' }))
|
||||||
|
zoom.attach(ele)
|
||||||
|
|
||||||
|
btf.addGlobalFn('pjaxSendOnce', () => {
|
||||||
|
window.mediumZoomInstance && window.mediumZoomInstance.detach()
|
||||||
|
}, 'mediumZoom')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,17 +306,41 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
getScrollPercent: (() => {
|
getScrollPercent: (() => {
|
||||||
let docHeight, winHeight, headerHeight, contentMath
|
let cache = new WeakMap()
|
||||||
|
|
||||||
return (currentTop, ele) => {
|
window.addEventListener('resize', () => {
|
||||||
if (!docHeight || ele.clientHeight !== docHeight) {
|
cache = new WeakMap()
|
||||||
docHeight = ele.clientHeight
|
})
|
||||||
winHeight = window.innerHeight
|
|
||||||
headerHeight = ele.offsetTop
|
return (currentTop, ele, useDocHeight = ele === document.body) => {
|
||||||
contentMath = Math.max(docHeight - winHeight, document.documentElement.scrollHeight - winHeight)
|
const eleHeight = ele.clientHeight
|
||||||
|
const winHeight = window.innerHeight
|
||||||
|
const cacheData = cache.get(ele)
|
||||||
|
let data = cacheData
|
||||||
|
|
||||||
|
if (
|
||||||
|
!cacheData ||
|
||||||
|
cacheData.docHeight !== eleHeight ||
|
||||||
|
cacheData.winHeight !== winHeight ||
|
||||||
|
cacheData.useDocHeight !== useDocHeight
|
||||||
|
) {
|
||||||
|
const headerHeight = ele.offsetTop
|
||||||
|
const contentMath = useDocHeight
|
||||||
|
? Math.max(eleHeight - winHeight, document.documentElement.scrollHeight - winHeight)
|
||||||
|
: Math.max(eleHeight - winHeight, 1)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
docHeight: eleHeight,
|
||||||
|
winHeight,
|
||||||
|
useDocHeight,
|
||||||
|
headerHeight,
|
||||||
|
contentMath
|
||||||
|
}
|
||||||
|
|
||||||
|
cache.set(ele, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
const scrollPercent = (currentTop - headerHeight) / contentMath
|
const scrollPercent = (currentTop - data.headerHeight) / data.contentMath
|
||||||
return Math.max(0, Math.min(100, Math.round(scrollPercent * 100)))
|
return Math.max(0, Math.min(100, Math.round(scrollPercent * 100)))
|
||||||
}
|
}
|
||||||
})(),
|
})(),
|
||||||
|
|||||||
Reference in New Issue
Block a user