add theme

This commit is contained in:
2025-08-12 12:19:25 +08:00
parent 8c06923a46
commit ac0d1944ab
227 changed files with 18962 additions and 0 deletions

View File

@@ -0,0 +1,46 @@
script.
(() => {
const abcjsInit = () => {
const abcjsFn = () => {
setTimeout(() => {
const sheets = document.querySelectorAll(".abc-music-sheet")
for (let i = 0; i < sheets.length; i++) {
const ele = sheets[i]
if (ele.children.length > 0) continue
// Parse parameters from data-params attribute
let params = {}
const dp = ele.getAttribute("data-params")
if (dp) {
try {
params = JSON.parse(dp)
} catch (e) {
console.error("Failed to parse data-params:", e)
}
}
// Merge parsed parameters with the responsive option
// Ensures params content appears before responsive
const options = { ...params, responsive: "resize" }
// Render the music score using ABCJS.renderAbc
ABCJS.renderAbc(ele, ele.innerHTML, options)
}
}, 100)
}
if (typeof ABCJS === "object") {
abcjsFn()
} else {
btf.getScript("!{url_for(theme.asset.abcjs_basic_js)}").then(abcjsFn)
}
}
if (window.pjax) {
abcjsInit()
} else {
window.addEventListener("load", abcjsInit)
}
btf.addGlobalFn("encrypt", abcjsInit, "abcjs")
})()

View File

@@ -0,0 +1,3 @@
if theme.abcjs.enable
if theme.abcjs.per_page && (['post','page'].includes(globalPageType)) || page.abcjs
include ./abcjs.pug

View File

@@ -0,0 +1,23 @@
link(rel='stylesheet' href=url_for(theme.asset.aplayer_css) media="print" onload="this.media='all'")
script(src=url_for(theme.asset.aplayer_js))
script(src=url_for(theme.asset.meting_js))
if theme.pjax.enable
script.
(() => {
const destroyAplayer = () => {
if (window.aplayers) {
for (let i = 0; i < window.aplayers.length; i++) {
if (!window.aplayers[i].options.fixed) {
window.aplayers[i].destroy()
}
}
}
}
const runMetingJS = () => {
typeof loadMeting === 'function' && document.getElementsByClassName('aplayer').length && loadMeting()
}
btf.addGlobalFn('pjaxSend', destroyAplayer, 'destroyAplayer')
btf.addGlobalFn('pjaxComplete', loadMeting, 'runMetingJS')
})()

View File

@@ -0,0 +1,31 @@
- const { server, site } = theme.artalk
script.
(() => {
const getArtalkCount = async() => {
try {
const eleGroup = document.querySelectorAll('#recent-posts .artalk-count')
const keyArray = Array.from(eleGroup).map(i => i.getAttribute('data-page-key'))
const headerList = {
method: 'GET',
}
const searchParams = new URLSearchParams({
'site_name': '!{site}',
'page_keys': keyArray
})
const res = await fetch(`!{server}/api/v2/stats/page_comment?${searchParams}`, headerList)
const result = await res.json()
keyArray.forEach((key, index) => {
eleGroup[index].textContent = result.data[key] || 0
})
} catch (err) {
console.error(err)
}
}
window.pjax ? getArtalkCount() : window.addEventListener('load', getArtalkCount)
})()

View File

@@ -0,0 +1,25 @@
- const { shortname, apikey } = theme.disqus
script.
(() => {
const getCount = async () => {
try {
const eleGroup = document.querySelectorAll('#recent-posts .disqus-count')
const cleanedLinks = Array.from(eleGroup).map(i => `thread:link=${i.href.replace(/#post-comment$/, '')}`);
const res = await fetch(`https://disqus.com/api/3.0/threads/set.json?forum=!{shortname}&api_key=!{apikey}&${cleanedLinks.join('&')}`,{
method: 'GET'
})
const result = await res.json()
eleGroup.forEach(i => {
const cleanedLink = i.href.replace(/#post-comment$/, '')
const urlData = result.response.find(data => data.link === cleanedLink) || { posts: 0 }
i.textContent = urlData.posts
})
} catch (err) {
console.error(err)
}
}
window.pjax ? getCount() : window.addEventListener('load', getCount)
})()

View File

@@ -0,0 +1,18 @@
- const fbSDKVer = 'v20.0'
- const fbSDK = `https://connect.facebook.net/${theme.facebook_comments.lang}/sdk.js#xfbml=1&version=${fbSDKVer}`
script.
(()=>{
function loadFBComment () {
if (typeof FB === 'object') FB.XFBML.parse(document.getElementById('recent-posts'))
else {
let ele = document.createElement('script')
ele.setAttribute('src','!{fbSDK}')
ele.setAttribute('async', 'true')
ele.setAttribute('defer', 'true')
ele.setAttribute('crossorigin', 'anonymous')
document.body.appendChild(ele)
}
}
window.pjax ? loadFBComment() : window.addEventListener('load', loadFBComment)
})()

View File

@@ -0,0 +1,16 @@
case theme.comments.use[0]
when 'Twikoo'
include ./twikoo.pug
when 'Disqus'
when 'Disqusjs'
include ./disqus.pug
when 'Valine'
include ./valine.pug
when 'Waline'
include ./waline.pug
when 'Facebook Comments'
include ./fb.pug
when 'Remark42'
include ./remark42.pug
when 'Artalk'
include ./artalk.pug

View File

@@ -0,0 +1,18 @@
- const { host, siteId, option } = theme.remark42
script.
(()=>{
window.remark_config = Object.assign({
host: '!{host}',
site_id: '!{siteId}',
},!{JSON.stringify(option)})
function getCount () {
const s = document.createElement('script')
s.src = remark_config.host + '/web/counter.js'
s.defer = true
document.head.appendChild(s)
}
window.pjax ? getCount() : window.addEventListener('load', getCount)
})()

View File

@@ -0,0 +1,39 @@
script.
(() => {
const getCommentUrl = () => {
const eleGroup = document.querySelectorAll('#recent-posts .article-title')
let urlArray = []
eleGroup.forEach(i=>{
urlArray.push(i.getAttribute('href'))
})
return urlArray
}
const getCount = () => {
const runTwikoo = () => {
twikoo.getCommentsCount({
envId: '!{theme.twikoo.envId}',
region: '!{theme.twikoo.region}',
urls: getCommentUrl(),
includeReply: false
}).then(function (res) {
document.querySelectorAll('#recent-posts .twikoo-count').forEach((item,index) => {
if (res[index]) {
item.textContent = res[index].count
}
})
}).catch(function (err) {
console.log(err)
})
}
if (typeof twikoo === 'object') {
runTwikoo()
} else {
btf.getScript('!{url_for(theme.asset.twikoo)}').then(runTwikoo)
}
}
window.pjax ? getCount() : window.addEventListener('load', getCount)
})()

View File

@@ -0,0 +1,20 @@
script.
(() => {
function loadValine () {
function initValine () {
let initData = {
el: '#vcomment',
appId: '#{theme.valine.appId}',
appKey: '#{theme.valine.appKey}',
serverURLs: '#{theme.valine.serverURLs}'
}
const valine = new Valine(initData)
}
if (typeof Valine === 'function') initValine()
else btf.getScript('!{url_for(theme.asset.valine)}').then(initValine)
}
window.pjax ? loadValine() : window.addEventListener('load', loadValine)
})()

View File

@@ -0,0 +1,21 @@
- const serverURL = theme.waline.serverURL.replace(/\/$/, '')
script.
(() => {
async function loadWaline () {
try {
const eleGroup = document.querySelectorAll('#recent-posts .waline-comment-count')
const keyArray = Array.from(eleGroup).map(i => i.getAttribute('data-path'))
const res = await fetch(`!{serverURL}/api/comment?type=count&url=${keyArray}`, { method: 'GET' })
const result = await res.json()
result.data.forEach((count, index) => {
eleGroup[index].textContent = count
})
} catch (err) {
console.error(err)
}
}
window.pjax ? loadWaline() : window.addEventListener('load', loadWaline)
})()

View File

@@ -0,0 +1,38 @@
//- https://chatra.io/help/api/
script.
(() => {
window.ChatraID = '#{theme.chatra.id}'
window.Chatra = window.Chatra || function() {
(window.Chatra.q = window.Chatra.q || []).push(arguments)
}
btf.getScript('https://call.chatra.io/chatra.js').then(() => {
const isChatBtn = !{theme.chat.rightside_button}
const isChatHideShow = !{theme.chat.button_hide_show}
if (isChatBtn) {
const close = () => {
Chatra('minimizeWidget')
Chatra('hide')
}
const open = () => {
Chatra('openChat', true)
Chatra('show')
}
window.ChatraSetup = { startHidden: true }
window.chatBtnFn = () => document.getElementById('chatra').classList.contains('chatra--expanded') ? close() : open()
document.getElementById('chat-btn').style.display = 'block'
} else if (isChatHideShow) {
window.chatBtn = {
hide: () => Chatra('hide'),
show: () => Chatra('show')
}
}
})
})()

View File

@@ -0,0 +1,32 @@
script.
(() => {
window.$crisp = ['safe', true]
window.CRISP_WEBSITE_ID = "!{theme.crisp.website_id}"
btf.getScript('https://client.crisp.chat/l.js').then(() => {
const isChatBtn = !{theme.chat.rightside_button}
const isChatHideShow = !{theme.chat.button_hide_show}
if (isChatBtn) {
const open = () => {
$crisp.push(["do", "chat:show"])
$crisp.push(["do", "chat:open"])
}
const close = () => $crisp.push(["do", "chat:hide"])
close()
$crisp.push(["on", "chat:closed", close])
window.chatBtnFn = () => $crisp.is("chat:visible") ? close() : open()
document.getElementById('chat-btn').style.display = 'block'
} else if (isChatHideShow) {
window.chatBtn = {
hide: () => $crisp.push(["do", "chat:hide"]),
show: () => $crisp.push(["do", "chat:show"])
}
}
})
})()

View File

@@ -0,0 +1,7 @@
case theme.chat.use
when 'chatra'
include ./chatra.pug
when 'tidio'
include ./tidio.pug
when 'crisp'
include ./crisp.pug

View File

@@ -0,0 +1,45 @@
script.
(() => {
btf.getScript('//code.tidio.co/!{theme.tidio.public_key}.js').then(() => {
const isChatBtn = !{theme.chat.rightside_button}
const isChatHideShow = !{theme.chat.button_hide_show}
if (isChatBtn) {
let isShow = false
const close = () => {
window.tidioChatApi.hide()
isShow = false
}
const open = () => {
window.tidioChatApi.open()
window.tidioChatApi.show()
isShow = true
}
const onTidioChatApiReady = () => {
window.tidioChatApi.hide()
window.tidioChatApi.on("close", close)
}
if (window.tidioChatApi) {
window.tidioChatApi.on("ready", onTidioChatApiReady)
} else {
document.addEventListener("tidioChat-ready", onTidioChatApiReady)
}
window.chatBtnFn = () => {
if (!window.tidioChatApi) return
isShow ? close() : open()
}
document.getElementById('chat-btn').style.display = 'block'
} else if (isChatHideShow) {
window.chatBtn = {
hide: () => window.tidioChatApi && window.tidioChatApi.hide(),
show: () => window.tidioChatApi && window.tidioChatApi.show()
}
}
})
})()

View File

@@ -0,0 +1,73 @@
- const { server, site, option } = theme.artalk
- const { use, lazyload } = theme.comments
script.
(() => {
let artalkItem = null
const option = !{JSON.stringify(option)}
const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'
const destroyArtalk = () => {
if (artalkItem) {
artalkItem.destroy()
artalkItem = null
}
}
const artalkChangeMode = theme => artalkItem && artalkItem.setDarkMode(theme === 'dark')
const initArtalk = (el = document, pageKey = location.pathname) => {
artalkItem = Artalk.init({
el: el.querySelector('#artalk-wrap'),
server: '!{server}',
site: '!{site}',
darkMode: document.documentElement.getAttribute('data-theme') === 'dark',
...option,
pageKey: isShuoshuo ? pageKey : (option && option.pageKey) || pageKey
})
if (GLOBAL_CONFIG.lightbox === 'null') return
artalkItem.on('list-loaded', () => {
artalkItem.ctx.get('list').getCommentNodes().forEach(comment => {
const $content = comment.getRender().$content
btf.loadLightbox($content.querySelectorAll('img:not([atk-emoticon])'))
})
})
if (isShuoshuo) {
window.shuoshuoComment.destroyArtalk = () => {
destroyArtalk()
if (el.children.length) {
el.innerHTML = ''
el.classList.add('no-comment')
}
}
}
btf.addGlobalFn('pjaxSendOnce', destroyArtalk, 'destroyArtalk')
btf.addGlobalFn('themeChange', artalkChangeMode, 'artalk')
}
const loadArtalk = async (el, pageKey) => {
if (typeof Artalk === 'object') initArtalk(el, pageKey)
else {
await btf.getCSS('!{theme.asset.artalk_css}')
await btf.getScript('!{theme.asset.artalk_js}')
initArtalk(el, pageKey)
}
}
if (isShuoshuo) {
'!{use[0]}' === 'Artalk'
? window.shuoshuoComment = { loadComment: loadArtalk }
: window.loadOtherComment = loadArtalk
return
}
if ('!{use[0]}' === 'Artalk' || !!{lazyload}) {
if (!{lazyload}) btf.loadComment(document.getElementById('artalk-wrap'), loadArtalk)
else setTimeout(loadArtalk, 100)
} else {
window.loadOtherComment = loadArtalk
}
})()

View File

@@ -0,0 +1,80 @@
- const disqusPageTitle = page.title.replace(/'/ig,"\\'")
- const { shortname, apikey } = theme.disqus
- const { use, lazyload, count } = theme.comments
script.
(() => {
const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'
const disqusReset = conf => {
window.DISQUS && window.DISQUS.reset({
reload: true,
config: conf
})
}
const loadDisqus = (el, path) => {
if (isShuoshuo) {
window.shuoshuoComment.destroyDisqus = () => {
if (el.children.length) {
el.innerHTML = ''
el.classList.add('no-comment')
}
}
}
window.disqus_identifier = isShuoshuo ? path : '!{ url_for(page.path) }'
window.disqus_url = isShuoshuo ? location.origin + path : '!{ page.permalink }'
const disqus_config = function () {
this.page.url = disqus_url
this.page.identifier = disqus_identifier
this.page.title = '!{ disqusPageTitle }'
}
if (window.DISQUS) disqusReset(disqus_config)
else {
const script = document.createElement('script')
script.src = 'https://!{shortname}.disqus.com/embed.js'
script.setAttribute('data-timestamp', +new Date())
document.head.appendChild(script)
}
btf.addGlobalFn('themeChange', () => disqusReset(disqus_config), 'disqus')
}
const getCount = async() => {
try {
const eleGroup = document.querySelector('#post-meta .disqus-comment-count')
if (!eleGroup) return
const cleanedLinks = eleGroup.href.replace(/#post-comment$/, '')
const res = await fetch(`https://disqus.com/api/3.0/threads/set.json?forum=!{shortname}&api_key=!{apikey}&thread:link=${cleanedLinks}`,{
method: 'GET'
})
const result = await res.json()
const count = result.response.length ? result.response[0].posts : 0
eleGroup.textContent = count
} catch (err) {
console.error(err)
}
}
if (isShuoshuo) {
'!{use[0]}' === 'Disqus'
? window.shuoshuoComment = { loadComment: loadDisqus }
: window.loadOtherComment = loadDisqus
return
}
if ('!{use[0]}' === 'Disqus' || !!{lazyload}) {
if (!{lazyload}) btf.loadComment(document.getElementById('disqus_thread'), loadDisqus)
else {
loadDisqus()
!{ count ? `GLOBAL_CONFIG_SITE.pageType === 'post' && getCount()` : '' }
}
} else {
window.loadOtherComment = loadDisqus
}
})()

View File

@@ -0,0 +1,87 @@
- let disqusjsPageTitle = page.title && page.title.replace(/'/ig,"\\'")
- const { shortname:dqShortname, apikey:dqApikey, option:dqOption } = theme.disqusjs
script.
(() => {
const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'== 'shuoshuo'
const dqOption = !{JSON.stringify(dqOption)}
const destroyDisqusjs = () => {
disqusjs.destroy()
window.disqusjs = null
}
const themeChange = (el, path) => {
destroyDisqusjs()
initDisqusjs(el, path)
}
const initDisqusjs = (el = document, path) => {
if (isShuoshuo) {
window.shuoshuoComment.destroyDisqusjs = () => {
destroyDisqusjs()
if (el.children.length) {
el.innerHTML = ''
el.classList.add('no-comment')
}
}
}
disqusjs = new DisqusJS({
shortname: '!{dqShortname}',
title: '!{ disqusjsPageTitle }',
apikey: '!{dqApikey}',
...dqOption,
identifier: isShuoshuo ? path : (dqOption && dqOption.identifier) || '!{ url_for(page.path) }',
url: isShuoshuo ? location.origin + path : (dqOption && dqOption.url) || '!{ page.permalink }'
})
disqusjs.render(el.querySelector('#disqusjs-wrap'))
btf.addGlobalFn('themeChange', () => themeChange(el, path), 'disqusjs')
}
const loadDisqusjs = async(el, path) => {
if (window.disqusJsLoad) initDisqusjs(el, path)
else {
await btf.getCSS('!{url_for(theme.asset.disqusjs_css)}')
await btf.getScript('!{url_for(theme.asset.disqusjs)}')
initDisqusjs(el, path)
window.disqusJsLoad = true
}
}
const getCount = async() => {
try {
const eleGroup = document.querySelector('#post-meta .disqusjs-comment-count')
if (!eleGroup) return
const cleanedLinks = eleGroup.href.replace(/#post-comment$/, '')
const res = await fetch(`https://disqus.com/api/3.0/threads/set.json?forum=!{dqShortname}&api_key=!{dqApikey}&thread:link=${cleanedLinks}`,{
method: 'GET'
})
const result = await res.json()
const count = result.response.length ? result.response[0].posts : 0
eleGroup.textContent = count
} catch (err) {
console.error(err)
}
}
if (isShuoshuo) {
'!{theme.comments.use[0]}' === 'Disqusjs'
? window.shuoshuoComment = { loadComment: loadDisqusjs }
: window.loadOtherComment = loadDisqusjs
return
}
if ('!{theme.comments.use[0]}' === 'Disqusjs' || !!{theme.comments.lazyload}) {
if (!{theme.comments.lazyload}) btf.loadComment(document.getElementById('disqusjs-wrap'), loadDisqusjs)
else {
loadDisqusjs()
!{ theme.comments.count ? `GLOBAL_CONFIG_SITE.pageType === 'post' && getCount()` : '' }
}
} else {
window.loadOtherComment = loadDisqusjs
}
})()

View File

@@ -0,0 +1,64 @@
- const fbSDKVer = 'v20.0'
- const fbSDK = `https://connect.facebook.net/${theme.facebook_comments.lang}/sdk.js#xfbml=1&version=${fbSDKVer}`
script.
(()=>{
const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'== 'shuoshuo'
const loadFBComment = (el = document, path) => {
if (isShuoshuo) {
window.shuoshuoComment.destroyFB = () => {
if (el.children.length) {
el.innerHTML = ''
el.classList.add('no-comment')
}
}
}
document.getElementById('fb-root') ? '' : document.body.insertAdjacentHTML('afterend', '<div id="fb-root"></div>')
const themeNow = document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'light'
const $fbComment = el.getElementsByClassName('fb-comments')[0]
$fbComment.setAttribute('data-colorscheme',themeNow)
$fbComment.setAttribute('data-href', isShuoshuo ? '!{urlNoIndex(page.permalink)}' + '#' + path : '!{urlNoIndex(page.permalink)}')
if (typeof FB === 'object') {
FB.XFBML.parse(document.getElementsByClassName('post-meta-commentcount')[0])
FB.XFBML.parse(el.querySelector('#post-comment'))
}
else {
let ele = document.createElement('script')
ele.setAttribute('src','!{fbSDK}')
ele.setAttribute('async', 'true')
ele.setAttribute('defer', 'true')
ele.setAttribute('crossorigin', 'anonymous')
ele.setAttribute('id', 'facebook-jssdk')
document.getElementById('fb-root').insertAdjacentElement('afterbegin',ele)
}
}
const fbModeChange = theme => {
const $fbComment = document.getElementsByClassName('fb-comments')[0]
if ($fbComment && typeof FB === 'object') {
$fbComment.setAttribute('data-colorscheme',theme)
FB.XFBML.parse(document.getElementById('post-comment'))
}
}
btf.addGlobalFn('themeChange', fbModeChange, 'facebook_comments')
if (isShuoshuo) {
'!{theme.comments.use[0]}' === 'Facebook Comments'
? window.shuoshuoComment = { loadComment: loadFBComment }
: window.loadOtherComment = loadFBComment
return
}
if ('!{theme.comments.use[0]}' === 'Facebook Comments' || !!{theme.comments.lazyload}) {
if (!{theme.comments.lazyload}) btf.loadComment(document.querySelector('#post-comment .fb-comments'), loadFBComment)
else loadFBComment()
} else {
window.loadOtherComment = loadFBComment
}
})()

View File

@@ -0,0 +1,82 @@
- const { use, lazyload } = theme.comments
- const { repo, repo_id, category_id, light_theme, dark_theme, js, option } = theme.giscus
- const giscusUrl = js || 'https://giscus.app/client.js'
- const giscusOriginUrl = new URL(giscusUrl).origin
script.
(() => {
const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'
const option = !{JSON.stringify(option)}
const getGiscusTheme = theme => theme === 'dark' ? '!{dark_theme}' : '!{light_theme}'
const createScriptElement = config => {
const ele = document.createElement('script')
Object.entries(config).forEach(([key, value]) => {
ele.setAttribute(key, value)
})
return ele
}
const loadGiscus = (el = document, key) => {
const mappingConfig = isShuoshuo
? { 'data-mapping': 'specific', 'data-term': key }
: { 'data-mapping': (option && option['data-mapping']) || 'pathname' }
const giscusConfig = {
src: '!{giscusUrl}',
'data-repo': '!{repo}',
'data-repo-id': '!{repo_id}',
'data-category-id': '!{category_id}',
'data-theme': getGiscusTheme(document.documentElement.getAttribute('data-theme')),
'data-reactions-enabled': '1',
crossorigin: 'anonymous',
async: true,
...option,
...mappingConfig
}
const scriptElement = createScriptElement(giscusConfig)
el.querySelector('#giscus-wrap').appendChild(scriptElement)
if (isShuoshuo) {
window.shuoshuoComment.destroyGiscus = () => {
if (el.children.length) {
el.innerHTML = ''
el.classList.add('no-comment')
}
}
}
}
const changeGiscusTheme = theme => {
const iframe = document.querySelector('#giscus-wrap iframe')
if (iframe) {
const message = {
giscus: {
setConfig: {
theme: getGiscusTheme(theme)
}
}
}
iframe.contentWindow.postMessage(message, '!{giscusOriginUrl}')
}
}
btf.addGlobalFn('themeChange', changeGiscusTheme, 'giscus')
if (isShuoshuo) {
'!{use[0]}' === 'Giscus'
? window.shuoshuoComment = { loadComment: loadGiscus }
: window.loadOtherComment = loadGiscus
return
}
if ('!{use[0]}' === 'Giscus' || !!{lazyload}) {
if (!{lazyload}) btf.loadComment(document.getElementById('giscus-wrap'), loadGiscus)
else loadGiscus()
} else {
window.loadOtherComment = loadGiscus
}
})()

View File

@@ -0,0 +1,64 @@
- const { client_id, client_secret, repo, owner, admin, option } = theme.gitalk
script.
(() => {
const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'
const option = !{JSON.stringify(option)}
const commentCount = n => {
const isCommentCount = document.querySelector('#post-meta .gitalk-comment-count')
if (isCommentCount) {
isCommentCount.textContent= n
}
}
const initGitalk = (el, path) => {
if (isShuoshuo) {
window.shuoshuoComment.destroyGitalk = () => {
if (el.children.length) {
el.innerHTML = ''
el.classList.add('no-comment')
}
}
}
const gitalk = new Gitalk({
clientID: '!{client_id}',
clientSecret: '!{client_secret}',
repo: '!{repo}',
owner: '!{owner}',
admin: ['!{admin}'],
updateCountCallback: commentCount,
...option,
id: isShuoshuo ? path : (option && option.id) || '!{md5(page.path)}'
})
gitalk.render('gitalk-container')
}
const loadGitalk = async(el, path) => {
if (typeof Gitalk === 'function') initGitalk(el, path)
else {
await btf.getCSS('!{url_for(theme.asset.gitalk_css)}')
await btf.getScript('!{url_for(theme.asset.gitalk)}')
initGitalk(el, path)
}
}
if (isShuoshuo) {
'!{theme.comments.use[0]}' === 'Gitalk'
? window.shuoshuoComment = { loadComment: loadGitalk }
: window.loadOtherComment = loadGitalk
return
}
if ('!{theme.comments.use[0]}' === 'Gitalk' || !!{theme.comments.lazyload}) {
if (!{theme.comments.lazyload}) btf.loadComment(document.getElementById('gitalk-container'), loadGitalk)
else loadGitalk()
} else {
window.loadOtherComment = loadGitalk
}
})()

View File

@@ -0,0 +1,46 @@
- let defaultComment = theme.comments.use[0]
hr.custom-hr
#post-comment
.comment-head
.comment-headline
i.fas.fa-comments.fa-fw
span= ' ' + _p('comment')
if theme.comments.use.length > 1
.comment-switch
span.first-comment=defaultComment
span#switch-btn
span.second-comment=theme.comments.use[1]
.comment-wrap
each name in theme.comments.use
div
case name
when 'Disqus'
#disqus_thread
when 'Valine'
#vcomment.vcomment
when 'Disqusjs'
#disqusjs-wrap
when 'Livere'
#lv-container(data-id="city" data-uid=theme.livere.uid)
when 'Gitalk'
#gitalk-container
when 'Utterances'
#utterances-wrap
when 'Twikoo'
#twikoo-wrap
when 'Waline'
#waline-wrap
when 'Giscus'
#giscus-wrap
when 'Facebook Comments'
.fb-comments(data-colorscheme = theme.display_mode === 'dark' ? 'dark' : 'light'
data-numposts= theme.facebook_comments.pageSize || 10
data-order-by= theme.facebook_comments.order_by || 'social'
data-width="100%")
when 'Remark42'
#remark42
when 'Artalk'
#artalk-wrap

View File

@@ -0,0 +1,26 @@
each name in theme.comments.use
case name
when 'Valine'
!=partial('includes/third-party/comments/valine', {}, {cache: true})
when 'Disqus'
include ./disqus.pug
when 'Disqusjs'
include ./disqusjs.pug
when 'Livere'
!=partial('includes/third-party/comments/livere', {}, {cache: true})
when 'Gitalk'
include ./gitalk.pug
when 'Utterances'
!=partial('includes/third-party/comments/utterances', {}, {cache: true})
when 'Twikoo'
!=partial('includes/third-party/comments/twikoo', {}, {cache: true})
when 'Waline'
!=partial('includes/third-party/comments/waline', {}, {cache: true})
when 'Giscus'
!=partial('includes/third-party/comments/giscus', {}, {cache: true})
when 'Facebook Comments'
include ./facebook_comments.pug
when 'Remark42'
!=partial('includes/third-party/comments/remark42', {}, {cache: true})
when 'Artalk'
!=partial('includes/third-party/comments/artalk', {}, {cache: true})

View File

@@ -0,0 +1,47 @@
- const { use, lazyload } = theme.comments
script.
(() => {
const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'
const loadLivere = (el, path) => {
window.livereOptions = {
refer: path || location.pathname
}
if (isShuoshuo) {
window.shuoshuoComment.destroyLivere = () => {
if (el.children.length) {
el.innerHTML = ''
el.classList.add('no-comment')
}
}
}
if (typeof LivereTower === 'object') window.LivereTower.init()
else {
(function(d, s) {
var j, e = d.getElementsByTagName(s)[0];
if (typeof LivereTower === 'function') { return; }
j = d.createElement(s);
j.src = 'https://cdn-city.livere.com/js/embed.dist.js';
j.async = true;
e.parentNode.insertBefore(j, e);
})(document, 'script');
}
}
if (isShuoshuo) {
'!{use[0]}' === 'Livere'
? window.shuoshuoComment = { loadComment: loadLivere }
: window.loadOtherComment = loadLivere
return
}
if ('!{use[0]}' === 'Livere' || !!{lazyload}) {
if (!{lazyload}) btf.loadComment(document.getElementById('lv-container'), loadLivere)
else loadLivere()
} else {
window.loadOtherComment = loadLivere
}
})()

View File

@@ -0,0 +1,78 @@
- const { host, siteId, option } = theme.remark42
script.
(() => {
const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'
const options = !{JSON.stringify(option)}
const loadScript = src => {
const script = document.createElement('script')
script.src = src
script.defer = true
document.head.appendChild(script)
}
const addRemark42 = () => loadScript('!{host}/web/embed.js')
const getCount = () => document.querySelector('.remark42__counter') && loadScript('!{host}/web/count.js')
const destroyRemark42 = () => window.remark42Instance && window.remark42Instance.destroy()
const initRemark42 = remark_config => {
if (window.REMARK42) {
destroyRemark42()
window.remark42Instance = window.REMARK42.createInstance({
...remark_config
})
}
}
const loadRemark42 = (el, path) => {
if (isShuoshuo) {
window.shuoshuoComment.destroyRemark42 = () => {
destroyRemark42()
if (el.children.length) {
el.innerHTML = ''
el.classList.add('no-comment')
}
}
}
window.remark_config = {
host: '!{host}',
site_id: '!{siteId}',
theme: document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'light',
...options,
url: isShuoshuo ? window.location.origin + path : (options && options.url) || window.location.origin + window.location.pathname
}
if (window.REMARK42) {
initRemark42(remark_config)
getCount()
} else {
addRemark42()
window.addEventListener('REMARK42::ready', () => {
initRemark42(remark_config)
getCount()
})
}
}
const remarkChangeMode = theme => window.REMARK42 && window.REMARK42.changeTheme(theme)
btf.addGlobalFn('themeChange', remarkChangeMode, 'remark42')
if (isShuoshuo) {
'!{theme.comments.use[0]}' === 'Remark42'
? window.shuoshuoComment = { loadComment: loadRemark42 }
: window.loadOtherComment = loadRemark42
return
}
if ('!{theme.comments.use[0]}' === 'Remark42' || !!{theme.comments.lazyload}) {
if (!{theme.comments.lazyload}) btf.loadComment(document.getElementById('remark42'), loadRemark42)
else loadRemark42()
} else {
window.loadOtherComment = loadRemark42
}
})()

View File

@@ -0,0 +1,64 @@
- const { envId, region, option } = theme.twikoo
- const { use, lazyload, count } = theme.comments
script.
(() => {
const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'
const option = !{JSON.stringify(option)}
const getCount = () => {
const countELement = document.getElementById('twikoo-count')
if(!countELement) return
twikoo.getCommentsCount({
envId: '!{envId}',
region: '!{region}',
urls: [window.location.pathname],
includeReply: false
}).then(res => {
countELement.textContent = res[0].count
}).catch(err => {
console.error(err)
})
}
const init = (el = document, path = location.pathname) => {
twikoo.init({
el: el.querySelector('#twikoo-wrap'),
envId: '!{envId}',
region: '!{region}',
onCommentLoaded: () => {
btf.loadLightbox(document.querySelectorAll('#twikoo .tk-content img:not(.tk-owo-emotion)'))
},
...option,
path: isShuoshuo ? path : (option && option.path) || path
})
!{count ? `GLOBAL_CONFIG_SITE.pageType === 'post' && getCount()` : ''}
isShuoshuo && (window.shuoshuoComment.destroyTwikoo = () => {
if (el.children.length) {
el.innerHTML = ''
el.classList.add('no-comment')
}
})
}
const loadTwikoo = (el, path) => {
if (typeof twikoo === 'object') setTimeout(() => init(el, path), 0)
else btf.getScript('!{url_for(theme.asset.twikoo)}').then(() => init(el, path))
}
if (isShuoshuo) {
'!{use[0]}' === 'Twikoo'
? window.shuoshuoComment = { loadComment: loadTwikoo }
: window.loadOtherComment = loadTwikoo
return
}
if ('!{use[0]}' === 'Twikoo' || !!{lazyload}) {
if (!{lazyload}) btf.loadComment(document.getElementById('twikoo-wrap'), loadTwikoo)
else loadTwikoo()
} else {
window.loadOtherComment = loadTwikoo
}
})()

View File

@@ -0,0 +1,63 @@
- const { use, lazyload } = theme.comments
- const { repo, issue_term, light_theme, dark_theme, js, option } = theme.utterances
- const utterancesUrl = js || 'https://utteranc.es/client.js'
- const utterancesOriginUrl = new URL(utterancesUrl).origin
script.
(() => {
const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'
const option = !{JSON.stringify(option)}
const getUtterancesTheme = theme => theme === 'dark' ? '#{dark_theme}' : '#{light_theme}'
const loadUtterances = (el = document, key) => {
if (isShuoshuo) {
window.shuoshuoComment.destroyUtterances = () => {
if (el.children.length) {
el.innerHTML = ''
el.classList.add('no-comment')
}
}
}
const config = {
src: '!{utterancesUrl}',
repo: '!{repo}',
theme: getUtterancesTheme(document.documentElement.getAttribute('data-theme')),
crossorigin: 'anonymous',
async: true,
...option,
'issue-term': isShuoshuo ? key : (option && option['issue-term']) || '!{issue_term}'
}
const ele = document.createElement('script')
Object.entries(config).forEach(([key, value]) => ele.setAttribute(key, value))
el.querySelector('#utterances-wrap').appendChild(ele)
}
const changeUtterancesTheme = theme => {
const iframe = document.querySelector('#utterances-wrap iframe')
if (iframe) {
const message = {
type: 'set-theme',
theme: getUtterancesTheme(theme)
};
iframe.contentWindow.postMessage(message, '!{utterancesOriginUrl}')
}
}
btf.addGlobalFn('themeChange', changeUtterancesTheme, 'utterances')
if (isShuoshuo) {
'!{use[0]}' === 'Utterances'
? window.shuoshuoComment = { loadComment: loadUtterances }
: window.loadOtherComment = loadUtterances
return
}
if ('!{use[0]}' === 'Utterances' || !!{lazyload}) {
if (!{lazyload}) btf.loadComment(document.getElementById('utterances-wrap'), loadUtterances)
else loadUtterances()
} else {
window.loadOtherComment = loadUtterances
}
})()

View File

@@ -0,0 +1,60 @@
- const { use, lazyload } = theme.comments
- const { appId, appKey, avatar, serverURLs, visitor, option } = theme.valine
- let emojiMaps = '""'
if site.data.valine
- emojiMaps = JSON.stringify(site.data.valine)
script.
(() => {
const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'
const option = !{JSON.stringify(option)}
const initValine = (el, path) => {
if (isShuoshuo) {
window.shuoshuoComment.destroyValine = () => {
if (el.children.length) {
el.innerHTML = ''
el.classList.add('no-comment')
}
}
}
const valineConfig = {
el: '#vcomment',
appId: '#{appId}',
appKey: '#{appKey}',
avatar: '#{avatar}',
serverURLs: '#{serverURLs}',
emojiMaps: !{emojiMaps},
visitor: #{visitor},
...option,
path: isShuoshuo ? path : (option && option.path) || window.location.pathname
}
new Valine(valineConfig)
}
const loadValine = async (el, path) => {
if (typeof Valine === 'function') {
initValine(el, path)
} else {
await btf.getScript('!{url_for(theme.asset.valine)}')
initValine(el, path)
}
}
if (isShuoshuo) {
'!{use[0]}' === 'Valine'
? window.shuoshuoComment = { loadComment: loadValine }
: window.loadOtherComment = loadValine
return
}
if ('!{use[0]}' === 'Valine' || !!{lazyload}) {
if (!{lazyload}) btf.loadComment(document.getElementById('vcomment'),loadValine)
else setTimeout(loadValine, 0)
} else {
window.loadOtherComment = loadValine
}
})()

View File

@@ -0,0 +1,61 @@
- const { serverURL, option, pageview } = theme.waline
- const { lazyload, count, use } = theme.comments
script.
(() => {
let initFn = window.walineFn || null
const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'
const option = !{JSON.stringify(option)}
const destroyWaline = ele => ele.destroy()
const initWaline = (Fn, el = document, path = window.location.pathname) => {
const waline = Fn({
el: el.querySelector('#waline-wrap'),
serverURL: '!{serverURL}',
pageview: !{lazyload ? false : pageview},
dark: 'html[data-theme="dark"]',
comment: !{lazyload ? false : count},
...option,
path: isShuoshuo ? path : (option && option.path) || path
})
if (isShuoshuo) {
window.shuoshuoComment.destroyWaline = () => {
destroyWaline(waline)
if (el.children.length) {
el.innerHTML = ''
el.classList.add('no-comment')
}
}
}
}
const loadWaline = (el, path) => {
if (initFn) initWaline(initFn, el, path)
else {
btf.getCSS('!{url_for(theme.asset.waline_css)}')
.then(() => import('!{url_for(theme.asset.waline_js)}'))
.then(({ init }) => {
initFn = init || Waline.init
initWaline(initFn, el, path)
window.walineFn = initFn
})
}
}
if (isShuoshuo) {
'!{use[0]}' === 'Waline'
? window.shuoshuoComment = { loadComment: loadWaline }
: window.loadOtherComment = loadWaline
return
}
if ('!{use[0]}' === 'Waline' || !!{lazyload}) {
if (!{lazyload}) btf.loadComment(document.getElementById('waline-wrap'),loadWaline)
else setTimeout(loadWaline, 0)
} else {
window.loadOtherComment = loadWaline
}
})()

View File

@@ -0,0 +1,35 @@
if theme.fireworks && theme.fireworks.enable
canvas.fireworks(mobile=`${theme.fireworks.mobile}`)
script(src=url_for(theme.asset.fireworks))
if (theme.canvas_ribbon && theme.canvas_ribbon.enable)
script(defer id="ribbon" src=url_for(theme.asset.canvas_ribbon) size=theme.canvas_ribbon.size
alpha=theme.canvas_ribbon.alpha zIndex=theme.canvas_ribbon.zIndex mobile=`${theme.canvas_ribbon.mobile}` data-click=`${theme.canvas_ribbon.click_to_change}`)
if (theme.canvas_fluttering_ribbon && theme.canvas_fluttering_ribbon.enable)
script(defer id="fluttering_ribbon" mobile=`${theme.canvas_fluttering_ribbon.mobile}` src=url_for(theme.asset.canvas_fluttering_ribbon))
if (theme.canvas_nest && theme.canvas_nest.enable)
script#canvas_nest(defer color=theme.canvas_nest.color opacity=theme.canvas_nest.opacity zIndex=theme.canvas_nest.zIndex count=theme.canvas_nest.count mobile=`${theme.canvas_nest.mobile}` src=url_for(theme.asset.canvas_nest))
if theme.activate_power_mode.enable
script(src=url_for(theme.asset.activate_power_mode))
script.
POWERMODE.colorful = !{theme.activate_power_mode.colorful};
POWERMODE.shake = !{theme.activate_power_mode.shake};
POWERMODE.mobile = !{theme.activate_power_mode.mobile};
document.body.addEventListener('input', POWERMODE);
//- 鼠標特效
if theme.click_heart && theme.click_heart.enable
script#click-heart(src=url_for(theme.asset.click_heart) async mobile=`${theme.click_heart.mobile}`)
if theme.clickShowText && theme.clickShowText.enable
script#click-show-text(
src= url_for(theme.asset.clickShowText)
data-mobile= `${theme.clickShowText.mobile}`
data-text= theme.clickShowText.text.join(",")
data-fontsize= theme.clickShowText.fontSize
data-random= `${theme.clickShowText.random}`
async
)

View File

@@ -0,0 +1,91 @@
- const { fontColor, borderColor, scale_ticks_backdropColor } = theme.chartjs
script.
(() => {
const applyThemeDefaultsConfig = theme => {
if (theme === 'dark-mode') {
Chart.defaults.color = "!{fontColor.dark}"
Chart.defaults.borderColor = "!{borderColor.dark}"
Chart.defaults.scale.ticks.backdropColor = "!{scale_ticks_backdropColor.dark}"
} else {
Chart.defaults.color = "!{fontColor.light}"
Chart.defaults.borderColor = "!{borderColor.light}"
Chart.defaults.scale.ticks.backdropColor = "!{scale_ticks_backdropColor.light}"
}
}
// Recursively traverse the config object and automatically apply theme-specific color schemes
const applyThemeConfig = (obj, theme) => {
if (typeof obj !== 'object' || obj === null) return
Object.keys(obj).forEach(key => {
const value = obj[key]
// If the property is an object and has theme-specific options, apply them
if (typeof value === 'object' && value !== null) {
if (value[theme]) {
obj[key] = value[theme] // Apply the value for the current theme
} else {
// Recursively process child objects
applyThemeConfig(value, theme)
}
}
})
}
const runChartJS = ele => {
window.loadChartJS = true
Array.from(ele).forEach((item, index) => {
const chartSrc = item.firstElementChild
const chartID = item.getAttribute('data-chartjs-id') || ('chartjs-' + index) // Use custom ID or default ID
const width = item.getAttribute('data-width')
const existingCanvas = document.getElementById(chartID)
// If a canvas already exists, remove it to avoid rendering duplicates
if (existingCanvas) {
existingCanvas.parentNode.remove()
}
const chartDefinition = chartSrc.textContent
const canvas = document.createElement('canvas')
canvas.id = chartID
const div = document.createElement('div')
div.className = 'chartjs-wrap'
if (width) {
div.style.width = width
}
div.appendChild(canvas)
chartSrc.insertAdjacentElement('afterend', div)
const ctx = document.getElementById(chartID).getContext('2d')
const config = JSON.parse(chartDefinition)
const theme = document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark-mode' : 'light-mode'
// Set default styles (initial setup)
applyThemeDefaultsConfig(theme)
// Automatically traverse the config and apply dual-mode color schemes
applyThemeConfig(config, theme)
new Chart(ctx, config)
})
}
const loadChartJS = () => {
const chartJSEle = document.querySelectorAll('#article-container .chartjs-container')
if (chartJSEle.length === 0) return
window.loadChartJS ? runChartJS(chartJSEle) : btf.getScript('!{url_for(theme.asset.chartjs)}').then(() => runChartJS(chartJSEle))
}
// Listen for theme change events
btf.addGlobalFn('themeChange', loadChartJS, 'chartjs')
btf.addGlobalFn('encrypt', loadChartJS, 'chartjs')
window.pjax ? loadChartJS() : document.addEventListener('DOMContentLoaded', loadChartJS)
})()

View File

@@ -0,0 +1,14 @@
case theme.math.use
when 'mathjax'
if (theme.math.per_page && (['post','page'].includes(globalPageType))) || page.mathjax
include ./mathjax.pug
when 'katex'
if (theme.math.per_page && (['post','page'].includes(globalPageType))) || page.katex
include ./katex.pug
if theme.mermaid.enable
include ./mermaid.pug
if theme.chartjs.enable
include ./chartjs.pug

View File

@@ -0,0 +1,16 @@
script.
(async () => {
const showKatex = () => {
document.querySelectorAll('#article-container .katex').forEach(el => el.classList.add('katex-show'))
}
if (!window.katex_js_css) {
window.katex_js_css = true
await btf.getCSS('!{url_for(theme.asset.katex)}')
if (!{theme.math.katex.copy_tex}) {
await btf.getScript('!{url_for(theme.asset.katex_copytex)}')
}
}
showKatex()
})()

View File

@@ -0,0 +1,47 @@
//- Mathjax 3
- const { tags, enableMenu } = theme.math.mathjax
script.
(() => {
const loadMathjax = () => {
if (!window.MathJax) {
window.MathJax = {
tex: {
inlineMath: [['$', '$'], ['\\(', '\\)']],
tags: '!{tags}',
},
chtml: {
scale: 1.1
},
options: {
enableMenu: !{enableMenu},
renderActions: {
findScript: [10, doc => {
for (const node of document.querySelectorAll('script[type^="math/tex"]')) {
const display = !!node.type.match(/; *mode=display/)
const math = new doc.options.MathItem(node.textContent, doc.inputJax[0], display)
const text = document.createTextNode('')
node.parentNode.replaceChild(text, node)
math.start = {node: text, delim: '', n: 0}
math.end = {node: text, delim: '', n: 0}
doc.math.push(math)
}
}, '']
}
}
}
const script = document.createElement('script')
script.src = '!{url_for(theme.asset.mathjax)}'
script.id = 'MathJax-script'
script.async = true
document.head.appendChild(script)
} else {
MathJax.startup.document.state(0)
MathJax.texReset()
MathJax.typesetPromise()
}
}
btf.addGlobalFn('encrypt', loadMathjax, 'mathjax')
window.pjax ? loadMathjax() : window.addEventListener('load', loadMathjax)
})()

View File

@@ -0,0 +1,51 @@
script.
(() => {
const runMermaid = ele => {
window.loadMermaid = true
const theme = document.documentElement.getAttribute('data-theme') === 'dark' ? '!{theme.mermaid.theme.dark}' : '!{theme.mermaid.theme.light}'
ele.forEach((item, index) => {
const mermaidSrc = item.firstElementChild
const mermaidThemeConfig = `%%{init:{ 'theme':'${theme}'}}%%\n`
const mermaidID = `mermaid-${index}`
const mermaidDefinition = mermaidThemeConfig + mermaidSrc.textContent
const renderFn = mermaid.render(mermaidID, mermaidDefinition)
const renderMermaid = svg => {
mermaidSrc.insertAdjacentHTML('afterend', svg)
}
// mermaid v9 and v10 compatibility
typeof renderFn === 'string' ? renderMermaid(renderFn) : renderFn.then(({ svg }) => renderMermaid(svg))
})
}
const codeToMermaid = () => {
const codeMermaidEle = document.querySelectorAll('pre > code.mermaid')
if (codeMermaidEle.length === 0) return
codeMermaidEle.forEach(ele => {
const preEle = document.createElement('pre')
preEle.className = 'mermaid-src'
preEle.hidden = true
preEle.textContent = ele.textContent
const newEle = document.createElement('div')
newEle.className = 'mermaid-wrap'
newEle.appendChild(preEle)
ele.parentNode.replaceWith(newEle)
})
}
const loadMermaid = () => {
if (!{theme.mermaid.code_write}) codeToMermaid()
const $mermaid = document.querySelectorAll('#article-container .mermaid-wrap')
if ($mermaid.length === 0) return
const runMermaidFn = () => runMermaid($mermaid)
btf.addGlobalFn('themeChange', runMermaidFn, 'mermaid')
window.loadMermaid ? runMermaidFn() : btf.getScript('!{url_for(theme.asset.mermaid)}').then(runMermaidFn)
}
btf.addGlobalFn('encrypt', loadMermaid, 'mermaid')
window.pjax ? loadMermaid() : document.addEventListener('DOMContentLoaded', loadMermaid)
})()

View File

@@ -0,0 +1,67 @@
- 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)
})

View File

@@ -0,0 +1,61 @@
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)
}
}

View File

@@ -0,0 +1,34 @@
!= 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)
})

View File

@@ -0,0 +1,62 @@
!= 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)
})

View File

@@ -0,0 +1,34 @@
- let { use } = theme.comments
if use
-
let forum,apiKey,userRepo
let { limit:newestCommentsLimit } = theme.aside.card_newest_comments
if (newestCommentsLimit > 10 || newestCommentsLimit < 1) newestCommentsLimit = 6
case use[0]
when 'Valine'
include ./valine.pug
when 'Waline'
include ./waline.pug
when 'Twikoo'
include ./twikoo-comment.pug
when 'Disqus'
- forum = theme.disqus.shortname
- apiKey = theme.disqus.apikey
include ./disqus-comment.pug
when 'Disqusjs'
- forum = theme.disqusjs.shortname
- apiKey = theme.disqusjs.apikey
include ./disqus-comment.pug
when 'Gitalk'
- let { repo,owner } = theme.gitalk
- userRepo = owner + '/' + repo
include ./github-issues.pug
when 'Utterances'
- userRepo = theme.utterances.repo
include ./github-issues.pug
when 'Remark42'
include ./remark42.pug
when 'Artalk'
include ./artalk.pug

View File

@@ -0,0 +1,31 @@
- 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)
})

View File

@@ -0,0 +1,45 @@
!= 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)
})

View File

@@ -0,0 +1,51 @@
- 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)
})

View File

@@ -0,0 +1,32 @@
- 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)
})

View File

@@ -0,0 +1,68 @@
- var pjaxExclude = 'a:not([target="_blank"])'
if theme.pjax.exclude
each val in theme.pjax.exclude
- pjaxExclude += `:not([href="${val}"])`
- let pjaxSelectors = ['head > title', '#config-diff', '#body-wrap', '#rightside-config-hide', '#rightside-config-show', '.js-pjax']
- let choose = theme.comments.use
if choose
if choose.includes('Livere') || choose.includes('Utterances') || choose.includes('Giscus')
- pjaxSelectors.unshift('link[rel="canonical"]')
if theme.Open_Graph_meta.enable
- pjaxSelectors.unshift('meta[property="og:image"]', 'meta[property="og:title"]', 'meta[property="og:url"]', 'meta[property="og:description"]')
else
- pjaxSelectors.unshift('meta[name="description"]')
script(src=url_for(theme.asset.pjax))
script.
(() => {
const pjaxSelectors = !{JSON.stringify(pjaxSelectors)}
window.pjax = new Pjax({
elements: '!{pjaxExclude}',
selectors: pjaxSelectors,
cacheBust: false,
analytics: !{theme.google_analytics ? true : false},
scrollRestoration: false
})
const triggerPjaxFn = (val) => {
if (!val) return
Object.values(val).forEach(fn => fn())
}
document.addEventListener('pjax:send', () => {
// removeEventListener
btf.removeGlobalFnEvent('pjaxSendOnce')
btf.removeGlobalFnEvent('themeChange')
// reset readmode
const $bodyClassList = document.body.classList
if ($bodyClassList.contains('read-mode')) $bodyClassList.remove('read-mode')
triggerPjaxFn(window.globalFn.pjaxSend)
})
document.addEventListener('pjax:complete', () => {
btf.removeGlobalFnEvent('pjaxCompleteOnce')
document.querySelectorAll('script[data-pjax]').forEach(item => {
const newScript = document.createElement('script')
const content = item.text || item.textContent || item.innerHTML || ""
Array.from(item.attributes).forEach(attr => newScript.setAttribute(attr.name, attr.value))
newScript.appendChild(document.createTextNode(content))
item.parentNode.replaceChild(newScript, item)
})
triggerPjaxFn(window.globalFn.pjaxComplete)
})
document.addEventListener('pjax:error', e => {
if (e.request.status === 404) {
const usePjax = !{theme.pjax && theme.pjax.enable}
!{theme.error_404 && theme.error_404.enable}
? (usePjax ? pjax.loadUrl('!{url_for("/404.html")}') : window.location.href = '!{url_for("/404.html")}')
: window.location.href = e.request.responseURL
}
})
})()

View File

@@ -0,0 +1,23 @@
- const { prismjs_js, prismjs_autoloader, prismjs_lineNumber_js } = theme.asset
- const { prismjs, syntax_highlighter } = config
- const { enable, preprocess, line_number } = prismjs
if (syntax_highlighter === 'prismjs' || enable) && !preprocess
script.
(() => {
window.Prism = window.Prism || {}
window.Prism.manual = true
const highlightAll = () => {
window.Prism.highlightAll()
}
window.addEventListener('load', highlightAll)
btf.addGlobalFn('pjaxComplete', highlightAll, 'prismjs')
btf.addGlobalFn('encrypt', highlightAll, 'prismjs')
})()
script(src=url_for(prismjs_js))
script(src=url_for(prismjs_autoloader))
if (line_number)
script(src=url_for(prismjs_lineNumber_js))

View File

@@ -0,0 +1,22 @@
#algolia-search
.search-dialog
nav.search-nav
span.search-dialog-title= _p('search.title')
button.search-close-button
i.fas.fa-times
.search-wrap
#algolia-search-input
hr
#algolia-search-results
#algolia-hits
#algolia-pagination
#algolia-info
.algolia-stats
.algolia-poweredBy
#search-mask
script(src=url_for(theme.asset.algolia_search))
script(src=url_for(theme.asset.instantsearch))
script(src=url_for(theme.asset.algolia_js))

View File

@@ -0,0 +1,29 @@
- const { placeholder, docsearch: { appId, apiKey, indexName, option } } = theme.search
.docsearch-wrap
#docsearch(style="display:none")
link(rel="stylesheet" href=url_for(theme.asset.docsearch_css))
script(src=url_for(theme.asset.docsearch_js))
script.
(() => {
docsearch(Object.assign({
appId: '!{appId}',
apiKey: '!{apiKey}',
indexName: '!{indexName}',
container: '#docsearch',
placeholder: '!{ placeholder || _p("search.input_placeholder")}',
}, !{JSON.stringify(option)}))
const handleClick = () => {
document.querySelector('.DocSearch-Button').click()
}
const searchClickFn = () => {
btf.addEventListenerPjax(document.querySelector('#search-button > .search'), 'click', handleClick)
}
searchClickFn()
window.addEventListener('pjax:complete', searchClickFn)
})()

View File

@@ -0,0 +1,7 @@
case theme.search.use
when 'algolia_search'
include ./algolia.pug
when 'local_search'
include ./local-search.pug
when 'docsearch'
include ./docsearch.pug

View File

@@ -0,0 +1,22 @@
#local-search
.search-dialog
nav.search-nav
span.search-dialog-title= _p('search.title')
span#loading-status
button.search-close-button
i.fas.fa-times
#loading-database.text-center
i.fas.fa-spinner.fa-pulse
span= ' ' + _p("search.load_data")
.search-wrap
#local-search-input
.local-search-box
input(placeholder=theme.search.placeholder || _p("search.input_placeholder") type="text").local-search-box--input
hr
#local-search-results
#local-search-stats-wrap
#search-mask
script(src=url_for(theme.asset.local_search))

View File

@@ -0,0 +1,10 @@
.addtoany
.a2a_kit.a2a_kit_size_32.a2a_default_style
- let addtoanyItem = theme.share.addtoany.item.split(',')
each name in addtoanyItem
a(class="a2a_button_" + name)
a.a2a_dd(href="https://www.addtoany.com/share")
script(async src='https://static.addtoany.com/menu/page.js')

View File

@@ -0,0 +1,9 @@
- const { use } = theme.share
if use
.post-share
case use
when 'addtoany'
!=partial('includes/third-party/share/addtoany', {}, {cache: true})
when 'sharejs'
include ./share-js.pug

View File

@@ -0,0 +1,4 @@
- const coverVal = page.cover_type === 'img' ? page.cover : theme.avatar.img
.social-share(data-image=url_for(coverVal) data-sites= theme.share.sharejs.sites)
link(rel='stylesheet' href=url_for(theme.asset.sharejs_css) media="print" onload="this.media='all'")
script(src=url_for(theme.asset.sharejs) defer)

View File

@@ -0,0 +1,113 @@
- const { effect, source, sub, typed_option } = theme.subtitle
- let subContent = sub || new Array()
script.
window.typedJSFn = {
init: str => {
window.typed = new Typed('#subtitle', Object.assign({
strings: str,
startDelay: 300,
typeSpeed: 150,
loop: true,
backSpeed: 50,
}, !{JSON.stringify(typed_option)}))
},
run: subtitleType => {
if (!{effect}) {
if (typeof Typed === 'function') {
subtitleType()
} else {
btf.getScript('!{url_for(theme.asset.typed)}').then(subtitleType)
}
} else {
subtitleType()
}
},
processSubtitle: (content, extraContents = []) => {
if (!{effect}) {
const sub = !{JSON.stringify(subContent)}.slice()
if (extraContents.length > 0) {
sub.unshift(...extraContents)
}
if (typeof content === 'string') {
sub.unshift(content)
} else if (Array.isArray(content)) {
sub.unshift(...content)
}
sub.length > 0 && typedJSFn.init(sub)
} else {
document.getElementById('subtitle').textContent = typeof content === 'string' ? content :
(Array.isArray(content) && content.length > 0 ? content[0] : '')
}
}
}
btf.addGlobalFn('pjaxSendOnce', () => { typed.destroy() }, 'typedDestroy')
case source
when 1
script.
function subtitleType () {
fetch('https://v1.hitokoto.cn')
.then(response => response.json())
.then(data => {
const from = '出自 ' + data.from
typedJSFn.processSubtitle(data.hitokoto, [from])
})
.catch(err => {
console.error('Failed to get the Hitokoto API:', err)
typedJSFn.processSubtitle(!{JSON.stringify(subContent)})
})
}
typedJSFn.run(subtitleType)
when 2
script.
function subtitleType () {
fetch('https://v.api.aa1.cn/api/yiyan/index.php')
.then(response => response.text())
.then(data => {
const reg = /<p>(.*?)<\/p>/g
const result = reg.exec(data)
if (result && result[1]) {
typedJSFn.processSubtitle(result[1])
} 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.length)})
})
}
typedJSFn.run(subtitleType)
when 3
script.
function subtitleType () {
btf.getScript('https://sdk.jinrishici.com/v2/browser/jinrishici.js')
.then(() => {
jinrishici.load(result => {
if (result && result.data && result.data.content) {
typedJSFn.processSubtitle(result.data.content)
} else {
throw new Error('Failed to parse the return value of Jinrishici API')
}
})
})
.catch(err => {
console.error('Failed to get the Jinrishici API:', err)
typedJSFn.processSubtitle(!{JSON.stringify(subContent.length)})
})
}
typedJSFn.run(subtitleType)
default
if subContent.length > 0
script.
function subtitleType () {
typedJSFn.processSubtitle(!{JSON.stringify(subContent)})
}
typedJSFn.run(subtitleType)

View File

@@ -0,0 +1,65 @@
- let { serverURL, website_id, option, UV_PV } = theme.umami_analytics
- const isServerURL = !!serverURL
- const baseURL = serverURL ? serverURL.replace(/\/$/, '') : 'https://cloud.umami.is'
- const apiUrl = serverURL ? serverURL.replace(/\/$/, '') + '/api' : 'https://api.umami.is/v1'
script.
(() => {
const option = !{JSON.stringify(option)}
const config = !{JSON.stringify(UV_PV)}
const runTrack = () => {
umami.track(props => ({ ...props, url: window.location.pathname, title: GLOBAL_CONFIG_SITE.title }))
}
const loadUmamiJS = () => {
btf.getScript('!{baseURL}/script.js', {
'data-website-id': '!{website_id}',
'data-auto-track': 'false',
...option
}).then(runTrack)
}
const getData = async (isPost) => {
const now = Date.now()
const keyUrl = isPost ? `&url=${window.location.pathname}` : ''
const headerList = { 'Accept': 'application/json' }
if (!{isServerURL}) headerList['Authorization'] = `Bearer ${config.token}`
else headerList['x-umami-api-key'] = config.token
const res = await fetch(`!{apiUrl}/websites/!{website_id}/stats?startAt=0000000000&endAt=${now}${keyUrl}`, {
method: "GET",
headers: headerList
})
return await res.json()
}
const insertData = async () => {
try {
if (GLOBAL_CONFIG_SITE.pageType === 'post' && config.page_pv) {
const pagePV = document.getElementById('umamiPV')
if (pagePV) {
const data = await getData(true)
pagePV.textContent = data.pageviews.value
}
} else {
const data = (config.site_uv || config.site_pv) && await getData()
if (config.site_uv) {
const siteUV = document.getElementById('umami-site-uv')
if (siteUV) siteUV.textContent = data.visitors.value
}
if (config.site_pv) {
const sitePV = document.getElementById('umami-site-pv')
if (sitePV) sitePV.textContent = data.pageviews.value
}
}
} catch (e) {
console.error('Failed to load Umami Analytics:', e)
}
}
btf.addGlobalFn('pjaxComplete', runTrack, 'umami_analytics_run_track')
btf.addGlobalFn('pjaxComplete', insertData, 'umami_analytics_insert')
loadUmamiJS()
insertData()
})()