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:
myw
2026-08-10 18:02:27 +08:00 Unverified
parent 51d36ef83a
commit 79e16a5cdd
25 changed files with 726 additions and 624 deletions
+60 -67
View File
@@ -1,67 +1,60 @@
- const { server, site, option } = theme.artalk
- const avatarCdn = (option !== null && option.gravatar && option.gravatar.mirror) || ''
- const avatarDefault = (option !== null && option.gravatar && (option.gravatar.params || option.gravatar.default)) || ''
!= partial("includes/third-party/newest-comments/common.pug", {}, { cache: true })
script.
window.addEventListener('load', () => {
const keyName = 'artalk-newest-comments'
const { changeContent, generateHtml, run } = window.newestComments
const getAvatarValue = async () => {
const predefinedAvatarCdn = '!{avatarCdn}'
const predefinedAvatarDefault = '!{avatarDefault}'
const avatarDefaultFormat = e => e.startsWith('d=') ? e : `d=${e}`
if (predefinedAvatarCdn && predefinedAvatarDefault) {
return { avatarCdn: predefinedAvatarCdn, avatarDefault: avatarDefaultFormat(predefinedAvatarDefault) }
}
try {
const res = await fetch('!{server}/api/v2/conf')
const result = await res.json()
const { mirror, params, default: defaults } = result.frontend_conf.gravatar
const avatarCdn = predefinedAvatarCdn || mirror
let avatarDefault = avatarDefaultFormat(predefinedAvatarDefault || params || defaults)
return { avatarCdn, avatarDefault}
} catch (e) {
console.error(e)
return { avatarCdn: predefinedAvatarCdn, avatarDefault: avatarDefaultFormat(predefinedAvatarDefault) }
}
}
const searchParams = new URLSearchParams({
'site_name': '!{site}',
'limit': '!{newestCommentsLimit * 2}', // Fetch more comments to filter pending comments
})
const getComment = async (ele) => {
try {
const res = await fetch(`!{server}/api/v2/stats/latest_comments?${searchParams}`)
const result = await res.json()
const { avatarCdn, avatarDefault } = await getAvatarValue()
const artalk = result.data
.filter(e => !e.is_pending) // Filter pending comments
.slice(0, !{newestCommentsLimit}) // Limit the number of comments
.map(e => {
const avatar = avatarCdn && e.email_encrypted ? `${avatarCdn}${e.email_encrypted}?${avatarDefault}` : ''
return {
'avatar': avatar,
'content': changeContent(e.content_marked),
'nick': e.nick,
'url': e.page_url,
'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)
})
- const { server, site, option } = theme.artalk
- const avatarCdn = (option !== null && option.gravatar && option.gravatar.mirror) || ''
- const avatarDefault = (option !== null && option.gravatar && (option.gravatar.params || option.gravatar.default)) || ''
!= partial("includes/third-party/newest-comments/common.pug", {}, { cache: true })
script.
window.newestComments.ready(() => {
const keyName = 'artalk-newest-comments'
const { changeContent, run } = window.newestComments
const getAvatarValue = async () => {
const predefinedAvatarCdn = '!{avatarCdn}'
const predefinedAvatarDefault = '!{avatarDefault}'
const avatarDefaultFormat = e => e.startsWith('d=') ? e : `d=${e}`
if (predefinedAvatarCdn && predefinedAvatarDefault) {
return { avatarCdn: predefinedAvatarCdn, avatarDefault: avatarDefaultFormat(predefinedAvatarDefault) }
}
try {
const res = await fetch('!{server}/api/v2/conf')
const result = await res.json()
const { mirror, params, default: defaults } = result.frontend_conf.gravatar
const avatarCdn = predefinedAvatarCdn || mirror
const avatarDefault = avatarDefaultFormat(predefinedAvatarDefault || params || defaults)
return { avatarCdn, avatarDefault }
} catch (e) {
console.error(e)
return { avatarCdn: predefinedAvatarCdn, avatarDefault: avatarDefaultFormat(predefinedAvatarDefault) }
}
}
const searchParams = new URLSearchParams({
'site_name': '!{site}',
'limit': '!{newestCommentsLimit * 2}', // Fetch more comments to filter pending comments
})
const getComment = async () => {
const res = await fetch(`!{server}/api/v2/stats/latest_comments?${searchParams}`)
const result = await res.json()
const { avatarCdn, avatarDefault } = await getAvatarValue()
return result.data
.filter(e => !e.is_pending) // Filter pending comments
.slice(0, !{newestCommentsLimit}) // Limit the number of comments
.map(e => {
const avatar = avatarCdn && e.email_encrypted ? `${avatarCdn}${e.email_encrypted}?${avatarDefault}` : ''
return {
'avatar': avatar,
'content': changeContent(e.content_marked),
'nick': e.nick,
'url': e.page_url,
'date': e.date,
}
})
}
run(keyName, getComment)
})
+97 -61
View File
@@ -1,61 +1,97 @@
script.
window.newestComments = {
changeContent: content => {
if (content === '') return content
content = content.replace(/<img.*?src="(.*?)"?[^\>]+>/ig, '[!{_p("aside.card_newest_comments.image")}]') // replace image link
content = content.replace(/<a[^>]+?href=["']?([^"']+)["']?[^>]*>([^<]+)<\/a>/gi, '[!{_p("aside.card_newest_comments.link")}]') // replace url
content = content.replace(/<pre><code>.*?<\/pre>/gi, '[!{_p("aside.card_newest_comments.code")}]') // replace code
content = content.replace(/<code>.*?<\/code>/gi, '[!{_p("aside.card_newest_comments.code")}]') // replace code
content = content.replace(/<[^>]+>/g, "") // remove html tag
if (content.length > 150) {
content = content.substring(0, 150) + '...'
}
return content
},
generateHtml: (array, ele) => {
let result = ''
if (array.length) {
for (let i = 0; i < array.length; i++) {
result += '<div class="aside-list-item">'
if (!{theme.aside.card_newest_comments.avatar} && array[i].avatar) {
const imgAttr = '!{theme.lazyload.enable && !theme.lazyload.native ? "data-lazy-src" : "src"}'
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>`
}
result += `<div class="content">
<a class="comment" href="${array[i].url}" title="${array[i].content}">${array[i].content}</a>
<div class="name"><span>${array[i].nick} / </span><time datetime="${array[i].date}">${btf.diffDate(array[i].date, true)}</time></div>
</div></div>`
}
} else {
result += '!{_p("aside.card_newest_comments.zero")}'
}
ele.innerHTML = result
window.lazyLoadInstance && window.lazyLoadInstance.update()
window.pjax && window.pjax.refresh(ele)
},
newestCommentInit: (name, getComment) => {
const $dom = document.querySelector('#card-newest-comments .aside-list')
if ($dom) {
const data = btf.saveToLocal.get(name)
if (data) {
newestComments.generateHtml(JSON.parse(data), $dom)
} else {
getComment($dom)
}
}
},
run: (name, getComment) => {
newestComments.newestCommentInit(name, getComment)
btf.addGlobalFn('pjaxComplete', () => newestComments.newestCommentInit(name, getComment), name)
}
}
script.
window.newestComments = {
ready: fn => {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', fn, { once: true })
} else {
fn()
}
},
escapeHtml: str => {
return String(str)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
},
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) {
content = Array.from(content).slice(0, 150).join('') + '...'
}
return content
},
generateHtml: (array, ele) => {
const { escapeHtml } = window.newestComments
let result = ''
if (array.length) {
result = array.map(item => {
let html = '<div class="aside-list-item">'
if (!{theme.aside.card_newest_comments.avatar} && item.avatar) {
const imgAttr = '!{theme.lazyload.enable && !theme.lazyload.native ? "data-lazy-src" : "src"}'
const lazyloadNative = '!{theme.lazyload.enable && theme.lazyload.native ? "loading=\"lazy\"" : ""}'
html += `<a href="${escapeHtml(item.url)}" class="thumbnail"><img ${imgAttr}="${escapeHtml(item.avatar)}" alt="${escapeHtml(item.nick)}" ${lazyloadNative}></a>`
}
html += `<div class="content">
<a class="comment" href="${escapeHtml(item.url)}" title="${escapeHtml(item.content)}">${escapeHtml(item.content)}</a>
<div class="name"><span>${escapeHtml(item.nick)} / </span><time datetime="${escapeHtml(item.date)}">${btf.diffDate(item.date, true)}</time></div>
</div></div>`
return html
}).join('')
} else {
result += '!{_p("aside.card_newest_comments.zero")}'
}
ele.innerHTML = result
window.lazyLoadInstance && window.lazyLoadInstance.update()
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) => {
const $dom = document.querySelector('#card-newest-comments .aside-list')
if ($dom) {
const data = btf.saveToLocal.get(name)
if (data) {
newestComments.generateHtml(JSON.parse(data), $dom)
} else {
newestComments.fetchAndRender(name, $dom, getComment)
}
}
},
run: (name, getComment) => {
newestComments.newestCommentInit(name, getComment)
btf.addGlobalFn('pjaxComplete', () => newestComments.newestCommentInit(name, getComment), name)
}
}
@@ -1,34 +1,23 @@
!= partial("includes/third-party/newest-comments/common.pug", {}, { cache: true })
script.
window.addEventListener('load', () => {
const keyName = 'disqus-newest-comments'
const { changeContent, generateHtml, run } = window.newestComments
const getComment = ele => {
fetch('https://disqus.com/api/3.0/forums/listPosts.json?forum=!{forum}&related=thread&limit=!{newestCommentsLimit}&api_key=!{apiKey}')
.then(response => response.json())
.then(data => {
const disqusArray = data.response.map(item => {
return {
'avatar': item.author.avatar.cache,
'content': changeContent(item.message),
'nick': item.author.name,
'url': item.url,
'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)
})
!= partial("includes/third-party/newest-comments/common.pug", {}, { cache: true })
script.
window.newestComments.ready(() => {
const keyName = 'disqus-newest-comments'
const { changeContent, run } = window.newestComments
const getComment = () => {
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(data => data.response.map(item => {
return {
'avatar': item.author.avatar.cache,
'content': changeContent(item.message),
'nick': item.author.name,
'url': item.url,
'date': item.createdAt
}
}))
}
run(keyName, getComment)
})
+49 -62
View File
@@ -1,62 +1,49 @@
!= partial("includes/third-party/newest-comments/common.pug", {}, { cache: true })
script.
window.addEventListener('load', () => {
const keyName = 'github-newest-comments'
const { changeContent, generateHtml, run } = window.newestComments
const findTrueUrl = (array, ele) => {
Promise.all(array.map(item =>
fetch(item.url).then(resp => resp.json()).then(data => {
let urlArray = data.body ? data.body.match(/(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?/ig) : []
if (!Array.isArray(urlArray) || urlArray.length === 0) {
urlArray = [`${data.html_url}`]
}
if (data.user.login === 'utterances-bot') {
return urlArray.pop()
} else {
return urlArray.shift()
}
})
)).then(res => {
array = array.map((i,index)=> {
return {
...i,
url: res[index]
}
})
btf.saveToLocal.set(keyName, JSON.stringify(array), !{theme.aside.card_newest_comments.storage}/(60*24))
generateHtml(array, ele)
});
}
const getComment = ele => {
fetch('https://api.github.com/repos/!{userRepo}/issues/comments?sort=updated&direction=desc&per_page=!{newestCommentsLimit}&page=1',{
"headers": {
Accept: 'application/vnd.github.v3.html+json'
}
})
.then(response => response.json())
.then(data => {
const githubArray = data.map(item => {
return {
'avatar': item.user.avatar_url,
'content': changeContent(item.body_html || item.body),
'nick': item.user.login,
'url': item.issue_url,
'date': item.updated_at
}
})
findTrueUrl(githubArray, ele)
}).catch(e => {
console.error(e)
ele.textContent= "!{_p('aside.card_newest_comments.error')}"
})
}
run(keyName, getComment)
})
!= partial("includes/third-party/newest-comments/common.pug", {}, { cache: true })
script.
window.newestComments.ready(() => {
const keyName = 'github-newest-comments'
const { changeContent, run } = window.newestComments
const findTrueUrl = array => {
return Promise.all(array.map(item =>
fetch(item.url)
.then(resp => resp.json())
.then(data => {
let urlArray = data.body ? data.body.match(/(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?/ig) : []
if (!Array.isArray(urlArray) || urlArray.length === 0) {
urlArray = [item.html_url]
}
const isBot = data.user && data.user.login === 'utterances-bot'
return isBot ? urlArray.pop() : urlArray.shift()
})
.catch(() => item.html_url)
)).then(res => array.map((item, index) => {
return {
...item,
url: res[index]
}
}))
}
const getComment = () => {
return fetch('https://api.github.com/repos/!{userRepo}/issues/comments?sort=updated&direction=desc&per_page=!{newestCommentsLimit}&page=1', {
"headers": {
Accept: 'application/vnd.github.v3.html+json'
}
})
.then(response => response.json())
.then(data => data.map(item => {
return {
'avatar': item.user.avatar_url,
'content': changeContent(item.body_html || item.body),
'nick': item.user.login,
'url': item.html_url,
'date': item.updated_at
}
}))
.then(findTrueUrl)
}
run(keyName, getComment)
})
+24 -31
View File
@@ -1,31 +1,24 @@
- const { host, siteId } = theme.remark42
!= partial("includes/third-party/newest-comments/common.pug", {}, { cache: true })
script.
window.addEventListener('load', () => {
const keyName = 'remark42-newest-comments'
const { changeContent, generateHtml, run } = window.newestComments
const getComment = ele => {
fetch('!{host}/api/v1/last/!{newestCommentsLimit}?site=!{siteId}')
.then(response => response.json())
.then(data => {
const remark42 = data.map(e => {
return {
'avatar': e.user.picture,
'content': changeContent(e.text),
'nick': e.user.name,
'url': e.locator.url,
'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)
})
- const { host, siteId } = theme.remark42
!= partial("includes/third-party/newest-comments/common.pug", {}, { cache: true })
script.
window.newestComments.ready(() => {
const keyName = 'remark42-newest-comments'
const { changeContent, run } = window.newestComments
const getComment = () => {
return fetch('!{host}/api/v1/last/!{newestCommentsLimit}?site=!{siteId}')
.then(response => response.json())
.then(data => data.map(e => {
return {
'avatar': e.user.picture,
'content': changeContent(e.text),
'nick': e.user.name,
'url': e.locator.url,
'date': e.time,
}
}))
}
run(keyName, getComment)
})
@@ -1,45 +1,32 @@
!= partial("includes/third-party/newest-comments/common.pug", {}, { cache: true })
script.
window.addEventListener('load', () => {
const keyName = 'twikoo-newest-comments'
const { changeContent, generateHtml, run } = window.newestComments
const getComment = ele => {
const runTwikoo = () => {
twikoo.getRecentComments({
envId: '!{theme.twikoo.envId}',
region: '!{theme.twikoo.region}',
pageSize: !{newestCommentsLimit},
includeReply: true
}).then(res => {
const twikooArray = res.map(e => {
return {
'content': changeContent(e.comment),
'avatar': e.avatar,
'nick': e.nick,
'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))
generateHtml(twikooArray, ele)
}).catch(err => {
console.error(err)
ele.textContent= "!{_p('aside.card_newest_comments.error')}"
})
}
if (typeof twikoo === 'object') {
runTwikoo()
} else {
btf.getScript('!{url_for(theme.asset.twikoo)}').then(runTwikoo)
}
}
run(keyName, getComment)
})
!= partial("includes/third-party/newest-comments/common.pug", {}, { cache: true })
script.
window.newestComments.ready(() => {
const keyName = 'twikoo-newest-comments'
const { changeContent, run } = window.newestComments
const getComment = async () => {
const runTwikoo = () => twikoo.getRecentComments({
envId: '!{theme.twikoo.envId}',
region: '!{theme.twikoo.region}',
pageSize: !{newestCommentsLimit},
includeReply: true
}).then(res => res.map(e => {
return {
'content': changeContent(e.comment),
'avatar': e.avatar,
'nick': e.nick,
'url': (e.url || '') + '#' + e.id,
'date': new Date(e.created).toISOString()
}
}))
if (typeof twikoo === 'undefined') {
await btf.getScript('!{url_for(theme.asset.twikoo)}')
}
return runTwikoo()
}
run(keyName, getComment)
})
+44 -51
View File
@@ -1,51 +1,44 @@
- let default_avatar = theme.valine.avatar
script(src=url_for(theme.asset.blueimp_md5))
!= partial("includes/third-party/newest-comments/common.pug", {}, { cache: true })
script.
window.addEventListener('load', () => {
const keyName = 'valine-newest-comments'
const { changeContent, generateHtml, run } = window.newestComments
const getIcon = (icon, mail) => {
if (icon) return icon
let defaultIcon = '!{ default_avatar ? `?d=${default_avatar}` : ''}'
let iconUrl = `https://gravatar.loli.net/avatar/${md5(mail.toLowerCase()) + defaultIcon}`
return iconUrl
}
const getComment = ele => {
const serverURL = '!{theme.valine.serverURLs || `https://${theme.valine.appId.substring(0,8)}.api.lncldglobal.com` }'
var settings = {
"method": "GET",
"headers": {
"X-LC-Id": '!{theme.valine.appId}',
"X-LC-Key": '!{theme.valine.appKey}',
"Content-Type": "application/json"
},
}
fetch(`${serverURL}/1.1/classes/Comment?limit=!{newestCommentsLimit}&order=-createdAt`,settings)
.then(response => response.json())
.then(data => {
const valineArray = data.results.map(e => {
return {
'avatar': getIcon(e.QQAvatar, e.mail),
'content': changeContent(e.comment),
'nick': e.nick,
'url': e.url + '#' + e.objectId,
'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)
})
- let default_avatar = theme.valine.avatar
script(src=url_for(theme.asset.blueimp_md5))
!= partial("includes/third-party/newest-comments/common.pug", {}, { cache: true })
script.
window.newestComments.ready(() => {
const keyName = 'valine-newest-comments'
const { changeContent, run } = window.newestComments
const getIcon = (icon, mail) => {
if (icon) return icon
let defaultIcon = '!{ default_avatar ? `?d=${default_avatar}` : ''}'
let iconUrl = `https://gravatar.loli.net/avatar/${md5(mail.toLowerCase()) + defaultIcon}`
return iconUrl
}
const getComment = async () => {
const serverURL = '!{theme.valine.serverURLs || `https://${theme.valine.appId.substring(0,8)}.api.lncldglobal.com` }'
const settings = {
"method": "GET",
"headers": {
"X-LC-Id": '!{theme.valine.appId}',
"X-LC-Key": '!{theme.valine.appKey}',
"Content-Type": "application/json"
},
}
const res = await fetch(`${serverURL}/1.1/classes/Comment?limit=!{newestCommentsLimit}&order=-createdAt`, settings)
const data = await res.json()
return data.results.map(e => {
return {
'avatar': getIcon(e.QQAvatar, e.mail),
'content': changeContent(e.comment),
'nick': e.nick,
'url': (e.url || '') + '#' + e.objectId,
'date': e.updatedAt,
}
})
}
run(keyName, getComment)
})
+25 -32
View File
@@ -1,32 +1,25 @@
- const serverURL = theme.waline.serverURL.replace(/\/$/, '')
!= partial("includes/third-party/newest-comments/common.pug", {}, { cache: true })
script.
window.addEventListener('load', () => {
const keyName = 'waline-newest-comments'
const { changeContent, generateHtml, run } = window.newestComments
const getComment = async (ele) => {
try {
const res = await fetch('!{serverURL}/api/comment?type=recent&count=!{newestCommentsLimit}')
const result = await res.json()
const walineArray = result.data.map(e => {
return {
'content': changeContent(e.comment),
'avatar': e.avatar,
'nick': e.nick,
'url': e.url + '#' + e.objectId,
'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)
})
- const serverURL = theme.waline.serverURL ? theme.waline.serverURL.replace(/\/$/, '') : ''
!= partial("includes/third-party/newest-comments/common.pug", {}, { cache: true })
script.
window.newestComments.ready(() => {
const keyName = 'waline-newest-comments'
const { changeContent, run } = window.newestComments
const getComment = async () => {
const res = await fetch('!{serverURL}/api/comment?type=recent&count=!{newestCommentsLimit}')
const result = await res.json()
return result.data.map(e => {
return {
'content': changeContent(e.comment),
'avatar': e.avatar,
'nick': e.nick,
'url': (e.url || '') + '#' + e.objectId,
'date': e.time || e.insertedAt
}
})
}
run(keyName, getComment)
})