breaking change: 重構 gallery 標籤外掛

improvement: 首頁社交圖標左右邊距調整
feat: 文章版權增加圖標
improvement: 重構 main.js 代碼
improvement: 優化 pjax 下的性能
fix: 修復子目錄下,pjax 跳轉 404 錯誤
feat: getScript 增加 attribute 配置
improvement: 優化手機端 toc 打開和關閉特效
improvement: 文章進入特效改為 transform, 優化 stylus
improvement: 目錄側邊欄出現滾動條時,元素不會被擠壓
feat: 文章左右對齊
improvement: 處理 waline 的 url 後面多 / 導致跨域的問題
fix: 修復夜間模式下,小屏幕的toc 滾動條顏色不明顯的 bug
fix: 修復設置字體超過17px時,toc 裏面的邊框異常的 bug
improvement: 優化語言文件部分用詞
improvement: disqus 和 disqusjs 的評論數獲取不到時,顯示為 0
improvement: disqusjs 的評論數改為 api 獲取
improvement: 代碼優化
improvement: 更新 plugins.yml
This commit is contained in:
Jerry
2023-08-06 18:43:39 +08:00
parent 347f605def
commit 775942ae3e
63 changed files with 1267 additions and 1113 deletions

View File

@@ -977,8 +977,7 @@ CDN:
# click_heart:
# ClickShowText:
# fontawesome:
# flickr_justified_gallery_js:
# flickr_justified_gallery_css:
# egjs_infinitegrid:
# aplayer_css:
# aplayer_js:
# meting_js:

View File

@@ -3,9 +3,9 @@ footer:
theme: Theme
copy:
success: Copy successfully
error: Copy error
noSupport: The browser does not support
success: Copy Successful
error: Copy Error
noSupport: Browser Not Supported
page:
articles: Articles
@@ -15,15 +15,15 @@ page:
card_post_count: comments
no_title: No title
no_title: Untitled
post:
created: Created
updated: Updated
wordcount: Word count
min2read: Reading time
min2read_unit: min
page_pv: Post View
wordcount: Word Count
min2read: Reading Time
min2read_unit: mins
page_pv: Post Views
comments: Comments
copyright:
author: Author
@@ -47,8 +47,8 @@ search:
hits_stats: '${hits} results found'
pagination:
prev: Previous Post
next: Next Post
prev: Previous
next: Next
comment: Comment
@@ -65,33 +65,33 @@ aside:
headline: Info
article_name: Article
runtime:
name: Run time
name: Runtime
unit: days
last_push_date:
name: Last Push
name: Last Update
site_wordcount: Total Count
site_uv_name: UV
site_pv_name: PV
more_button: More
more_button: View More
card_newest_comments:
headline: Newest Comments
headline: Latest Comments
loading_text: loading...
error: Unable to get the data, please make sure the settings are correct.
zero: No Comment
error: Unable to retrieve comments, please check the configuration
zero: No comments
image: image
link: link
code: code
card_toc: Catalog
card_toc: Contents
card_post_series: Series
date_suffix:
just: Just
just: Just now
min: minutes ago
hour: hours ago
day: days ago
month: months ago
donate: Donate
donate: Sponsor
share: Share
rightside:
@@ -102,7 +102,7 @@ rightside:
toc: Table Of Contents
scroll_to_comment: Scroll To Comments
setting: Setting
aside: Toggle between single-column and double-column
aside: Toggle between Single-column and Double-column
chat: Chat
copy_copyright:
@@ -112,12 +112,12 @@ copy_copyright:
info: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.
Snackbar:
chs_to_cht: Traditional Chinese Activated Manually
cht_to_chs: Simplified Chinese Activated Manually
day_to_night: Dark Mode Activated Manually
night_to_day: Light Mode Activated Manually
chs_to_cht: You have switched to Traditional Chinese
cht_to_chs: You have switched to Simplified Chinese
day_to_night: You have switched to Dark Mode
night_to_day: You have switched to Light Mode
loading: Loading...
load_more: Load More
error404: Page not found
error404: Page Not Found

View File

@@ -3,9 +3,9 @@ footer:
theme: Theme
copy:
success: Copy successfully
error: Copy error
noSupport: The browser does not support
success: Copy Successful
error: Copy Error
noSupport: Browser Not Supported
page:
articles: Articles
@@ -15,15 +15,15 @@ page:
card_post_count: comments
no_title: No title
no_title: Untitled
post:
created: Created
updated: Updated
wordcount: Word count
min2read: Reading time
min2read_unit: min
page_pv: Post View
wordcount: Word Count
min2read: Reading Time
min2read_unit: mins
page_pv: Post Views
comments: Comments
copyright:
author: Author
@@ -47,8 +47,8 @@ search:
hits_stats: '${hits} results found'
pagination:
prev: Previous Post
next: Next Post
prev: Previous
next: Next
comment: Comment
@@ -65,44 +65,44 @@ aside:
headline: Info
article_name: Article
runtime:
name: Run time
name: Runtime
unit: days
last_push_date:
name: Last Push
name: Last Update
site_wordcount: Total Count
site_uv_name: UV
site_pv_name: PV
more_button: More
more_button: View More
card_newest_comments:
headline: Newest Comments
headline: Latest Comments
loading_text: loading...
error: Unable to get the data, please make sure the settings are correct.
zero: No Comment
error: Unable to retrieve comments, please check the configuration
zero: No comments
image: image
link: link
code: code
card_toc: Catalog
card_toc: Contents
card_post_series: Series
date_suffix:
just: Just
just: Just now
min: minutes ago
hour: hours ago
day: days ago
month: months ago
donate: Donate
donate: Sponsor
share: Share
rightside:
readmode_title: Read Mode
translate_title: Switch Between Traditional Chinese And Simplified Chinese
night_mode_title: Switch Between Light And Dark Mode
translate_title: Toggle Between Traditional Chinese And Simplified Chinese
night_mode_title: Toggle Between Light And Dark Mode
back_to_top: Back To Top
toc: Table Of Contents
scroll_to_comment: Scroll To Comments
setting: Setting
aside: Toggle between single-column and double-column
aside: Toggle between Single-column and Double-column
chat: Chat
copy_copyright:
@@ -112,12 +112,12 @@ copy_copyright:
info: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.
Snackbar:
chs_to_cht: Traditional Chinese Activated Manually
cht_to_chs: Simplified Chinese Activated Manually
day_to_night: Dark Mode Activated Manually
night_to_day: Light Mode Activated Manually
chs_to_cht: You have switched to Traditional Chinese
cht_to_chs: You have switched to Simplified Chinese
day_to_night: You have switched to Dark Mode
night_to_day: You have switched to Light Mode
loading: Loading...
load_more: Load More
error404: Page not found
error404: Page Not Found

View File

@@ -92,7 +92,7 @@ date_suffix:
day: 天前
month: 个月前
donate: 打赏
donate: 赞助
share: 分享
rightside:
@@ -113,8 +113,8 @@ copy_copyright:
info: 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
Snackbar:
chs_to_cht: 你已切换为繁体
cht_to_chs: 你已切换为简体
chs_to_cht: 你已切换为繁体中文
cht_to_chs: 你已切换为简体中文
day_to_night: 你已切换为深色模式
night_to_day: 你已切换为浅色模式

View File

@@ -15,7 +15,7 @@ page:
card_post_count: 條評論
no_title: 無題
no_title:
post:
created: 發表於
@@ -83,7 +83,7 @@ aside:
link: 連結
code: 程式碼
card_toc: 目錄
card_post_series: 系列文章
card_post_series: 文章系列
date_suffix:
just: 剛剛
@@ -92,14 +92,14 @@ date_suffix:
day: 天前
month: 個月前
donate: 打賞
donate: 贊助
share: 分享
rightside:
readmode_title: 閱讀模式
translate_title: 簡繁轉換
night_mode_title: 淺色和深色模式轉換
back_to_top: 頂部
back_to_top: 回頂部
toc: 目錄
scroll_to_comment: 直達評論
setting: 設定
@@ -110,11 +110,11 @@ copy_copyright:
author: 作者
link: 連結
source: 來源
info: 著作權歸作者所有。商業轉載請聯作者獲得授權,非商業轉載請註明出處。
info: 著作權歸作者所有。商業轉載請聯作者獲得授權,非商業轉載請註明出處。
Snackbar:
chs_to_cht: 你已切換為繁體
cht_to_chs: 你已切換為簡體
chs_to_cht: 你已切換為繁體中文
cht_to_chs: 你已切換為簡體中文
day_to_night: 你已切換為深色模式
night_to_day: 你已切換為淺色模式

View File

@@ -3,7 +3,6 @@ extends includes/layout.pug
block content
include ./includes/mixins/article-sort.pug
#archive
- const archiveLength = findArchiveLength(fragment_cache)
.article-sort-title= _p('page.articles') + ' - ' + archiveLength
.article-sort-title= `${_p('page.articles')} - ${getArchiveLength()}`
+articleSort(page.posts)
include includes/pagination.pug

View File

@@ -4,9 +4,9 @@
- var nowYear = now.getFullYear()
if theme.footer.owner.since != false
if theme.footer.owner.since != nowYear
.copyright!= `©${theme.footer.owner.since} - ${nowYear} By ${config.author}`
.copyright!= `©${theme.footer.owner.since} - ${nowYear} By ${config.author}`
else
.copyright!= `©${nowYear} By ${config.author}`
.copyright!= `©${nowYear} By ${config.author}`
if theme.footer.copyright
.framework-info
span= _p('footer.framework') + ' '

View File

@@ -55,7 +55,7 @@ if theme.fancybox
!=partial('includes/head/analytics', {}, {cache: true})
//- font
if theme.blog_title_font && theme.blog_title_font.font_link
if theme.blog_title_font && theme.blog_title_font.font_link
link(rel='stylesheet' href=url_for(theme.blog_title_font.font_link) media="print" onload="this.media='all'")
//- global config

View File

@@ -115,11 +115,9 @@ script.
copyright: !{copyright},
lightbox: '!{ theme.medium_zoom ? "mediumZoom" : (theme.fancybox ? "fancybox" : "null" )}',
Snackbar: !{Snackbar},
source: {
justifiedGallery: {
js: '!{url_for(theme.asset.flickr_justified_gallery_js)}',
css: '!{url_for(theme.asset.flickr_justified_gallery_css)}'
}
infinitegrid: {
js: '!{url_for(theme.asset.egjs_infinitegrid)}',
buttonText: '!{_p("load_more")}'
},
isPhotoFigcaption: !{theme.photofigcaption},
islazyload: !{theme.lazyload.enable},

View File

@@ -3,10 +3,6 @@ noscript.
#nav {
opacity: 1
}
.justified-gallery img {
opacity: 1
}
#recent-posts time,
#post-meta time {
display: inline !important

View File

@@ -102,14 +102,12 @@
case whichCount
when 'Disqus'
+countBlock
span.disqus-comment-count
a(href=full_url_for(page.path) + '#disqus_thread')
i.fa-solid.fa-spinner.fa-spin
a.disqus-comment-count(href=full_url_for(page.path) + '#post-comment')
i.fa-solid.fa-spinner.fa-spin
when 'Disqusjs'
+countBlock
a(href=full_url_for(page.path) + '#disqusjs')
span.disqus-comment-count(data-disqus-url=full_url_for(page.path))
i.fa-solid.fa-spinner.fa-spin
a.disqusjs-comment-count(href=full_url_for(page.path) + '#post-comment')
i.fa-solid.fa-spinner.fa-spin
when 'Valine'
+countBlock
a(href=url_for(page.path) + '#post-comment' itemprop="discussionUrl")

View File

@@ -71,14 +71,10 @@ mixin postUI(posts)
if theme.comments.card_post_count && theme.comments.use
case theme.comments.use[0]
when 'Disqus'
+countBlockInIndex
a(href=full_url_for(link) + '#disqus_thread')
i.fa-solid.fa-spinner.fa-spin
when 'Disqusjs'
+countBlockInIndex
a(href=full_url_for(link) + '#disqusjs')
span.disqus-comment-count(data-disqus-url=full_url_for(link))
i.fa-solid.fa-spinner.fa-spin
a.disqus-count(href=full_url_for(link) + '#post-comment')
i.fa-solid.fa-spinner.fa-spin
when 'Valine'
+countBlockInIndex
a(href=url_for(link) + '#post-comment')

View File

@@ -5,13 +5,19 @@ if theme.post_copyright.enable && page.copyright !== false
- let info = page.copyright_info || _p('post.copyright.copyright_content', theme.post_copyright.license_url, theme.post_copyright.license, config.url, config.title)
.post-copyright
.post-copyright__author
span.post-copyright-meta= _p('post.copyright.author') + ": "
span.post-copyright-meta
i.fas.fa-circle-user.fa-fw
= _p('post.copyright.author') + ": "
span.post-copyright-info
a(href=authorHref)=author
.post-copyright__type
span.post-copyright-meta= _p('post.copyright.link') + ": "
span.post-copyright-meta
i.fas.fa-square-arrow-up-right.fa-fw
= _p('post.copyright.link') + ": "
span.post-copyright-info
a(href=url_for(url))= theme.post_copyright.decode ? decodeURI(url) : url
.post-copyright__notice
span.post-copyright-meta= _p('post.copyright.copyright_notice') + ": "
span.post-copyright-meta
i.fas.fa-circle-exclamation.fa-fw
= _p('post.copyright.copyright_notice') + ": "
span.post-copyright-info!= info

View File

@@ -42,15 +42,15 @@ mixin rightsideItem(array)
#rightside-config-show
if enable
if hide
button#rightside_config(type="button" title=_p("rightside.setting"))
button#rightside-config(type="button" title=_p("rightside.setting"))
i.fas.fa-cog.fa-spin
else
if is_post()
if (readmode || translate.enable || (darkmode.enable && darkmode.button))
button#rightside_config(type="button" title=_p("rightside.setting"))
button#rightside-config(type="button" title=_p("rightside.setting"))
i.fas.fa-cog.fa-spin
else if translate.enable || (darkmode.enable && darkmode.button)
button#rightside_config(type="button" title=_p("rightside.setting"))
button#rightside-config(type="button" title=_p("rightside.setting"))
i.fas.fa-cog.fa-spin
if showArray

View File

@@ -1,15 +1,15 @@
script.
(() => {
function abcjsInit() {
function abcjsFn() {
for (let abcContainer of document.getElementsByClassName("abc-music-sheet")) {
ABCJS.renderAbc(abcContainer, abcContainer.innerHTML, {responsive: 'resize'})
}
const abcjsInit = () => {
const abcjsFn = () => {
document.querySelectorAll(".abc-music-sheet").forEach(ele => {
ABCJS.renderAbc(ele, ele.innerHTML, {responsive: 'resize'})
})
}
typeof ABCJS === 'object' ? abcjsFn()
: getScript('!{url_for(theme.asset.abcjs_basic_js)}').then(abcjsFn)
}
window.pjax ? abcjsInit() : document.addEventListener('DOMContentLoaded', abcjsInit)
window.pjax ? abcjsInit() : window.addEventListener('load', abcjsInit)
})()

View File

@@ -1,16 +1,25 @@
- const { shortname, apikey } = theme.disqus
script.
(() => {
const getCount = () => {
if (window.DISQUSWIDGETS === undefined) {
var d = document, s = d.createElement('script');
s.src = 'https://!{theme.disqus.shortname}.disqus.com/count.js';
s.id = 'dsq-count-scr';
(d.head || d.body).appendChild(s);
} else {
DISQUSWIDGETS.getCount({reset: true});
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

@@ -1,3 +1,4 @@
- const serverURL = theme.waline.serverURL.replace(/\/$/, '')
script.
(() => {
async function loadWaline () {
@@ -5,7 +6,7 @@ script.
const eleGroup = document.querySelectorAll('#recent-posts .waline-comment-count')
const keyArray = Array.from(eleGroup).map(i => i.getAttribute('data-path'))
const res = await fetch(`!{theme.waline.serverURL}/api/comment?type=count&url=${keyArray}`, { method: 'GET' })
const res = await fetch(`!{serverURL}/api/comment?type=count&url=${keyArray}`, { method: 'GET' })
const result = await res.json()
result.data.forEach((count, index) => {

View File

@@ -1,8 +1,9 @@
- const { server, site, option } = theme.artalk
- const { use, lazyload } = theme.comments
script.
function loadArtalk () {
function initArtalk () {
(() => {
const initArtalk = () => {
window.artalkItem = new Artalk(Object.assign({
el: '#artalk-wrap',
server: '!{server}',
@@ -23,28 +24,29 @@ script.
})
}
if (typeof window.artalkItem === 'object') initArtalk()
else {
getCSS('!{theme.asset.artalk_css}').then(()=>{
getScript('!{theme.asset.artalk_js}').then(initArtalk)
})
const loadArtalk = async () => {
if (typeof window.artalkItem === 'object') initArtalk()
else {
await getCSS('!{theme.asset.artalk_css}')
await getScript('!{theme.asset.artalk_js}')
initArtalk()
}
}
}
function artalkChangeMode (theme) {
const artalkWrap = document.getElementById('artalk-wrap')
if (!(artalkWrap && artalkWrap.children.length)) return
const isDark = theme === 'dark'
window.artalkItem.setDarkMode(isDark)
}
btf.addModeChange('artalk', artalkChangeMode)
const artalkChangeMode = theme => {
const artalkWrap = document.getElementById('artalk-wrap')
if (!(artalkWrap && artalkWrap.children.length)) return
const isDark = theme === 'dark'
window.artalkItem.setDarkMode(isDark)
}
if ('!{theme.comments.use[0]}' === 'Artalk' || !!{theme.comments.lazyload}) {
if (!{theme.comments.lazyload}) btf.loadComment(document.getElementById('artalk-wrap'), loadArtalk)
else loadArtalk()
} else {
function loadOtherComment () {
loadArtalk()
btf.addGlobalFn('themeChange', artalkChangeMode, 'artalk')
if ('!{use[0]}' === 'Artalk' || !!{lazyload}) {
if (!{lazyload}) btf.loadComment(document.getElementById('artalk-wrap'), loadArtalk)
else loadArtalk()
} else {
window.loadOtherComment = loadArtalk
}
}
})()

View File

@@ -1,7 +1,9 @@
- let disqusPageTitle = page.title.replace(/'/ig,"\\'")
- const disqusPageTitle = page.title.replace(/'/ig,"\\'")
- const { shortname, apikey } = theme.disqus
- const { use, lazyload, count } = theme.comments
script.
function loadDisqus () {
(() => {
const disqus_config = function () {
this.page.url = '!{ page.permalink }'
this.page.identifier = '!{ url_for(page.path) }'
@@ -9,41 +11,49 @@ script.
}
const disqusReset = () => {
DISQUS.reset({
window.DISQUS && window.DISQUS.reset({
reload: true,
config: disqus_config
})
}
btf.addModeChange('disqus', disqusReset)
btf.addGlobalFn('themeChange', disqusReset, 'disqus')
if (window.DISQUS) disqusReset()
else {
(function() {
var d = document, s = d.createElement('script');
s.src = 'https://!{theme.disqus.shortname}.disqus.com/embed.js';
s.setAttribute('data-timestamp', +new Date());
(d.head || d.body).appendChild(s);
})();
const loadDisqus = () =>{
if (window.DISQUS) disqusReset()
else {
const script = document.createElement('script')
script.src = 'https://!{shortname}.disqus.com/embed.js'
script.setAttribute('data-timestamp', +new Date())
document.head.appendChild(script)
}
}
}
if ('!{theme.comments.use[0]}' === 'Disqus' || !!{theme.comments.lazyload}) {
if (!{theme.comments.lazyload}) btf.loadComment(document.getElementById('disqus_thread'), loadDisqus)
else loadDisqus()
} else {
function loadOtherComment () {
loadDisqus()
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 is_post() && !theme.comments.lazyload && theme.comments.count && theme.comments.use[0] === 'Disqus'
script.
if (window.DISQUSWIDGETS === undefined) {
var d = document, s = d.createElement('script');
s.src = 'https://!{theme.disqus.shortname}.disqus.com/count.js';
s.id = 'dsq-count-scr';
(d.head || d.body).appendChild(s);
if ('!{use[0]}' === 'Disqus' || !!{lazyload}) {
if (!{lazyload}) btf.loadComment(document.getElementById('disqus_thread'), loadDisqus)
else {
loadDisqus()
!{ count ? 'GLOBAL_CONFIG_SITE.isPost && getCount()' : '' }
}
} else {
DISQUSWIDGETS.getCount({reset: true});
window.loadOtherComment = loadDisqus
}
})()

View File

@@ -1,18 +1,19 @@
- let disqusjsPageTitle = page.title.replace(/'/ig,"\\'")
- const { shortname:dqShortname, apikey:dqApikey, option:dqOption } = theme.disqusjs
script.
function loadDisqusjs () {
function initDisqusjs () {
(() => {
const initDisqusjs = () => {
window.disqusjs = null
disqusjs = new DisqusJS(Object.assign({
shortname: '!{theme.disqusjs.shortname}',
shortname: '!{dqShortname}',
identifier: '!{ url_for(page.path) }',
url: '!{ page.permalink }',
title: '!{ disqusjsPageTitle }',
apikey: '!{theme.disqusjs.apikey}',
},!{JSON.stringify(theme.disqusjs.option)}))
apikey: '!{dqApikey}',
},!{JSON.stringify(dqOption)}))
disqusjs.render(document.getElementById('disqusjs'))
disqusjs.render(document.getElementById('disqusjs-wrap'))
}
const themeChange = () => {
@@ -22,36 +23,42 @@ script.
initDisqusjs()
}
btf.addGlobalFn('themeChange', themeChange, 'disqusjs')
btf.addModeChange('disqusjs', themeChange)
if (window.disqusJsLoad) initDisqusjs()
else {
getCSS('!{url_for(theme.asset.disqusjs_css)}')
getScript('!{url_for(theme.asset.disqusjs)}').then(initDisqusjs)
window.disqusJsLoad = true
const loadDisqusjs = async() => {
if (window.disqusJsLoad) initDisqusjs()
else {
await getCSS('!{url_for(theme.asset.disqusjs_css)}')
await getScript('!{url_for(theme.asset.disqusjs)}')
initDisqusjs()
window.disqusJsLoad = true
}
}
}
if ('!{theme.comments.use[0]}' === 'Disqusjs' || !!{theme.comments.lazyload}) {
if (!{theme.comments.lazyload}) btf.loadComment(document.getElementById('disqusjs'), loadDisqusjs)
else loadDisqusjs()
}
else {
function loadOtherComment () {
loadDisqusjs()
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 is_post() && !theme.comments.lazyload && theme.comments.count && theme.comments.use[0] === 'Disqusjs'
script.
if (window.DISQUSWIDGETS === undefined) {
var d = document, s = d.createElement('script');
s.src = 'https://!{theme.disqus.shortname}.disqus.com/count.js';
s.id = 'dsq-count-scr';
(d.head || d.body).appendChild(s);
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.isPost && getCount()' : '' }
}
} else {
DISQUSWIDGETS.getCount({reset: true});
window.loadOtherComment = loadDisqusjs
}
})()

View File

@@ -2,45 +2,45 @@
- const fbSDK = theme.messenger.enable ? `https://connect.facebook.net/${theme.facebook_comments.lang}/sdk/xfbml.customerchat.js#xfbml=1&version=${fbSDKVer}` : `https://connect.facebook.net/${theme.facebook_comments.lang}/sdk.js#xfbml=1&version=${fbSDKVer}`
script.
function loadFBComment () {
document.getElementById('fb-root') ? '' : document.body.insertAdjacentHTML('afterend', '<div id="fb-root"></div>')
(()=>{
const loadFBComment = () => {
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 = document.getElementsByClassName('fb-comments')[0]
$fbComment.setAttribute('data-colorscheme',themeNow)
$fbComment.setAttribute('data-href', '!{urlNoIndex(page.permalink)}')
const themeNow = document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'light'
const $fbComment = document.getElementsByClassName('fb-comments')[0]
$fbComment.setAttribute('data-colorscheme',themeNow)
$fbComment.setAttribute('data-href', '!{urlNoIndex(page.permalink)}')
if (typeof FB === 'object') {
FB.XFBML.parse(document.getElementsByClassName('post-meta-commentcount')[0])
FB.XFBML.parse(document.getElementById('post-comment'))
if (typeof FB === 'object') {
FB.XFBML.parse(document.getElementsByClassName('post-meta-commentcount')[0])
FB.XFBML.parse(document.getElementById('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)
}
}
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'))
}
}
}
function 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 ('!{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
}
}
btf.addModeChange('facebook_comments', fbModeChange)
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 {
function loadOtherComment () {
loadFBComment()
}
}
})()

View File

@@ -1,53 +1,54 @@
- const { repo, repo_id, category_id, theme:themes, option } = theme.giscus
- const giscusUrl = theme.asset.giscus || 'https://giscus.app/client.js'
- const giscusOriginUrl = new URL(giscusUrl).origin
- const { use, lazyload } = theme.comments
script.
function getGiscusTheme (theme) {
return theme === 'dark' ? '!{themes.dark}' : '!{themes.light}'
}
function loadGiscus () {
const config = Object.assign({
src: '!{giscusUrl}',
'data-repo': '!{repo}',
'data-repo-id': '!{repo_id}',
'data-category-id': '!{category_id}',
'data-mapping': 'pathname',
'data-theme': getGiscusTheme(document.documentElement.getAttribute('data-theme')),
'data-reactions-enabled': '1',
crossorigin: 'anonymous',
async: true
},!{JSON.stringify(option)})
let ele = document.createElement('script')
for (let key in config) {
ele.setAttribute(key, config[key])
}
document.getElementById('giscus-wrap').insertAdjacentElement('afterbegin',ele)
}
function changeGiscusTheme (theme) {
function sendMessage(message) {
const iframe = document.querySelector('iframe.giscus-frame')
if (!iframe) return
iframe.contentWindow.postMessage({ giscus: message }, '!{giscusOriginUrl}')
(()=>{
const getGiscusTheme = theme => {
return theme === 'dark' ? '!{themes.dark}' : '!{themes.light}'
}
sendMessage({
setConfig: {
theme: getGiscusTheme(theme)
const loadGiscus = () => {
const config = Object.assign({
src: '!{giscusUrl}',
'data-repo': '!{repo}',
'data-repo-id': '!{repo_id}',
'data-category-id': '!{category_id}',
'data-mapping': 'pathname',
'data-theme': getGiscusTheme(document.documentElement.getAttribute('data-theme')),
'data-reactions-enabled': '1',
crossorigin: 'anonymous',
async: true
},!{JSON.stringify(option)})
const ele = document.createElement('script')
for (let key in config) {
ele.setAttribute(key, config[key])
}
});
}
btf.addModeChange('giscus', changeGiscusTheme)
if ('!{theme.comments.use[0]}' === 'Giscus' || !!{theme.comments.lazyload}) {
if (!{theme.comments.lazyload}) btf.loadComment(document.getElementById('giscus-wrap'), loadGiscus)
else loadGiscus()
} else {
function loadOtherComment () {
loadGiscus()
document.getElementById('giscus-wrap').appendChild(ele)
}
}
const changeGiscusTheme = theme => {
const sendMessage = message => {
const iframe = document.querySelector('iframe.giscus-frame')
if (!iframe) return
iframe.contentWindow.postMessage({ giscus: message }, '!{giscusOriginUrl}')
}
sendMessage({
setConfig: {
theme: getGiscusTheme(theme)
}
});
}
btf.addGlobalFn('themeChange', changeGiscusTheme, 'giscus')
if ('!{use[0]}' === 'Giscus' || !!{lazyload}) {
if (!{lazyload}) btf.loadComment(document.getElementById('giscus-wrap'), loadGiscus)
else loadGiscus()
} else {
window.loadOtherComment= loadGiscus
}
})()

View File

@@ -1,41 +1,44 @@
- const { client_id, client_secret, repo, owner, admin, option } = theme.gitalk
script.
function loadGitalk () {
function initGitalk () {
var gitalk = new Gitalk(Object.assign({
clientID: '!{theme.gitalk.client_id}',
clientSecret: '!{theme.gitalk.client_secret}',
repo: '!{theme.gitalk.repo}',
owner: '!{theme.gitalk.owner}',
admin: ['!{theme.gitalk.admin}'],
(() => {
const initGitalk = () => {
const gitalk = new Gitalk(Object.assign({
clientID: '!{client_id}',
clientSecret: '!{client_secret}',
repo: '!{repo}',
owner: '!{owner}',
admin: ['!{admin}'],
id: '!{md5(page.path)}',
updateCountCallback: commentCount
},!{JSON.stringify(theme.gitalk.option)}))
},!{JSON.stringify(option)}))
gitalk.render('gitalk-container')
}
if (typeof Gitalk === 'function') initGitalk()
else {
getCSS('!{url_for(theme.asset.gitalk_css)}')
getScript('!{url_for(theme.asset.gitalk)}').then(initGitalk)
const loadGitalk = async() => {
if (typeof Gitalk === 'function') initGitalk()
else {
await getCSS('!{url_for(theme.asset.gitalk_css)}')
await getScript('!{url_for(theme.asset.gitalk)}')
initGitalk()
}
}
}
function commentCount(n){
let isCommentCount = document.querySelector('#post-meta .gitalk-comment-count')
if (isCommentCount) {
isCommentCount.textContent= n
const commentCount = n => {
const isCommentCount = document.querySelector('#post-meta .gitalk-comment-count')
if (isCommentCount) {
isCommentCount.textContent= n
}
}
}
if ('!{theme.comments.use[0]}' === 'Gitalk' || !!{theme.comments.lazyload}) {
if (!{theme.comments.lazyload}) btf.loadComment(document.getElementById('gitalk-container'), loadGitalk)
else loadGitalk()
} else {
function loadOtherComment () {
loadGitalk()
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

@@ -7,9 +7,9 @@ hr.custom-hr
span= ' ' + _p('comment')
if theme.comments.use.length > 1
#comment-switch
.comment-switch
span.first-comment=defaultComment
span.switch-btn
span#switch-btn
span.second-comment=theme.comments.use[1]
@@ -22,7 +22,7 @@ hr.custom-hr
when 'Valine'
#vcomment.vcomment
when 'Disqusjs'
#disqusjs
#disqusjs-wrap
when 'Livere'
#lv-container(data-id="city" data-uid=theme.livere.uid)
when 'Gitalk'

View File

@@ -1,26 +1,25 @@
script.
function loadLivere () {
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');
}
}
- const { use, lazyload } = theme.comments
if ('!{theme.comments.use[0]}' === 'Livere' || !!{theme.comments.lazyload}) {
if (!{theme.comments.lazyload}) btf.loadComment(document.getElementById('lv-container'), loadLivere)
else loadLivere()
}
else {
function loadOtherComment () {
loadLivere()
script.
(()=>{
const loadLivere = () => {
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 ('!{use[0]}' === 'Livere' || !!{lazyload}) {
if (!{lazyload}) btf.loadComment(document.getElementById('lv-container'), loadLivere)
else loadLivere()
} else {
window.loadOtherComment = loadLivere
}
})()

View File

@@ -56,7 +56,7 @@ script.
window.REMARK42.changeTheme(theme)
}
btf.addModeChange('remark42', remarkChangeMode)
btf.addGlobalFn('themeChange', remarkChangeMode, 'remark42')
if ('!{theme.comments.use[0]}' === 'Remark42' || !!{theme.comments.lazyload}) {
if (!{theme.comments.lazyload}) btf.loadComment(document.getElementById('remark42'), loadRemark42)

View File

@@ -2,18 +2,23 @@
- const { use, lazyload, count } = theme.comments
script.
(()=>{
(() => {
const init = () => {
twikoo.init(Object.assign({
el: '#twikoo-wrap',
envId: '!{envId}',
region: '!{region}',
onCommentLoaded: function () {
onCommentLoaded: () => {
btf.loadLightbox(document.querySelectorAll('#twikoo .tk-content img:not(.tk-owo-emotion)'))
}
}, !{JSON.stringify(option)}))
}
const loadTwikoo = () => {
if (typeof twikoo === 'object') setTimeout(init,0)
else getScript('!{url_for(theme.asset.twikoo)}').then(init)
}
const getCount = () => {
const countELement = document.getElementById('twikoo-count')
if(!countELement) return
@@ -22,32 +27,20 @@ script.
region: '!{region}',
urls: [window.location.pathname],
includeReply: false
}).then(function (res) {
}).then(res => {
countELement.textContent = res[0].count
}).catch(function (err) {
console.error(err);
});
}
const runFn = () => {
init()
!{count ? 'GLOBAL_CONFIG_SITE.isPost && getCount()' : ''}
}
const loadTwikoo = () => {
if (typeof twikoo === 'object') {
setTimeout(runFn,0)
return
}
getScript('!{url_for(theme.asset.twikoo)}').then(runFn)
}).catch(err => {
console.error(err)
})
}
if ('!{use[0]}' === 'Twikoo' || !!{lazyload}) {
if (!{lazyload}) btf.loadComment(document.getElementById('twikoo-wrap'), loadTwikoo)
else loadTwikoo()
} else {
window.loadOtherComment = () => {
else {
loadTwikoo()
!{count ? 'GLOBAL_CONFIG_SITE.isPost && getCount()' : ''}
}
} else {
window.loadOtherComment = loadTwikoo
}
})()

View File

@@ -1,36 +1,39 @@
- const { use, lazyload } = theme.comments
- const { repo, issue_term, light_theme, dark_theme } = theme.utterances
script.
function loadUtterances () {
let ele = document.createElement('script')
ele.setAttribute('id', 'utterances_comment')
ele.setAttribute('src', 'https://utteranc.es/client.js')
ele.setAttribute('repo', '!{theme.utterances.repo}')
ele.setAttribute('issue-term', '!{theme.utterances.issue_term}')
let nowTheme = document.documentElement.getAttribute('data-theme') === 'dark' ? '#{theme.utterances.dark_theme}' : '#{theme.utterances.light_theme}'
ele.setAttribute('theme', nowTheme)
ele.setAttribute('crossorigin', 'anonymous')
ele.setAttribute('async', 'true')
document.getElementById('utterances-wrap').insertAdjacentElement('afterbegin',ele)
}
function utterancesTheme (theme) {
const iframe = document.querySelector('.utterances-frame')
if (iframe) {
const theme = theme === 'dark' ? '#{theme.utterances.dark_theme}' : '#{theme.utterances.light_theme}'
const message = {
type: 'set-theme',
theme: theme
};
iframe.contentWindow.postMessage(message, 'https://utteranc.es');
(() => {
const loadUtterances = () => {
let ele = document.createElement('script')
ele.id = 'utterances_comment'
ele.src = 'https://utteranc.es/client.js'
ele.setAttribute('repo', '!{repo}')
ele.setAttribute('issue-term', '!{issue_term}')
const nowTheme = document.documentElement.getAttribute('data-theme') === 'dark' ? '#{dark_theme}' : '#{light_theme}'
ele.setAttribute('theme', nowTheme)
ele.crossOrigin = 'anonymous'
ele.async = true
document.getElementById('utterances-wrap').appendChild(ele)
}
}
btf.addModeChange('utterances', utterancesTheme)
if ('!{theme.comments.use[0]}' === 'Utterances' || !!{theme.comments.lazyload}) {
if (!{theme.comments.lazyload}) btf.loadComment(document.getElementById('utterances-wrap'), loadUtterances)
else loadUtterances()
} else {
function loadOtherComment () {
loadUtterances()
const utterancesTheme = theme => {
const iframe = document.querySelector('.utterances-frame')
if (iframe) {
const theme = theme === 'dark' ? '#{dark_theme}' : '#{light_theme}'
const message = {
type: 'set-theme',
theme: theme
};
iframe.contentWindow.postMessage(message, 'https://utteranc.es');
}
}
}
btf.addGlobalFn('themeChange', utterancesTheme, 'utterances')
if ('!{use[0]}' === 'Utterances' || !!{lazyload}) {
if (!{lazyload}) btf.loadComment(document.getElementById('utterances-wrap'), loadUtterances)
else loadUtterances()
} else {
window.loadOtherComment = loadUtterances
}
})()

View File

@@ -1,33 +1,38 @@
- 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.
function loadValine () {
function initValine () {
(() => {
const initValine = () => {
const valine = new Valine(Object.assign({
el: '#vcomment',
appId: '#{theme.valine.appId}',
appKey: '#{theme.valine.appKey}',
avatar: '#{theme.valine.avatar}',
serverURLs: '#{theme.valine.serverURLs}',
appId: '#{appId}',
appKey: '#{appKey}',
avatar: '#{avatar}',
serverURLs: '#{serverURLs}',
emojiMaps: !{emojiMaps},
path: window.location.pathname,
visitor: #{theme.valine.visitor}
}, !{JSON.stringify(theme.valine.option)}))
visitor: #{visitor}
}, !{JSON.stringify(option)}))
}
if (typeof Valine === 'function') initValine()
else getScript('!{url_for(theme.asset.valine)}').then(initValine)
}
if ('!{theme.comments.use[0]}' === 'Valine' || !!{theme.comments.lazyload}) {
if (!{theme.comments.lazyload}) btf.loadComment(document.getElementById('vcomment'),loadValine)
else setTimeout(loadValine, 0)
} else {
function loadOtherComment () {
loadValine()
const loadValine = async () => {
if (typeof Valine === 'function') initValine()
else {
await getScript('!{url_for(theme.asset.valine)}')
initValine()
}
}
}
if ('!{use[0]}' === 'Valine' || !!{lazyload}) {
if (!{lazyload}) btf.loadComment(document.getElementById('vcomment'),loadValine)
else setTimeout(loadValine, 0)
} else {
window.loadOtherComment = loadValine
}
})()

View File

@@ -2,8 +2,8 @@
- const { lazyload, count, use } = theme.comments
script.
function loadWaline () {
function initWaline () {
(() => {
const initWaline = () => {
const waline = Waline.init(Object.assign({
el: '#waline-wrap',
serverURL: '!{serverURL}',
@@ -14,21 +14,20 @@ script.
}, !{JSON.stringify(option)}))
}
if (typeof Waline === 'object') initWaline()
else {
getCSS('!{url_for(theme.asset.waline_css)}').then(() => {
getScript('!{url_for(theme.asset.waline_js)}').then(initWaline)
})
const loadWaline = async () => {
if (typeof Waline === 'object') initWaline()
else {
await getCSS('!{url_for(theme.asset.waline_css)}')
await getScript('!{url_for(theme.asset.waline_js)}')
initWaline()
}
}
}
if ('!{use[0]}' === 'Waline' || !!{lazyload}) {
if (!{lazyload}) btf.loadComment(document.getElementById('waline-wrap'),loadWaline)
else setTimeout(loadWaline, 0)
} else {
function loadOtherComment () {
loadWaline()
if ('!{use[0]}' === 'Waline' || !!{lazyload}) {
if (!{lazyload}) btf.loadComment(document.getElementById('waline-wrap'),loadWaline)
else setTimeout(loadWaline, 0)
} else {
window.loadOtherComment = loadWaline
}
}
})()

View File

@@ -32,7 +32,7 @@ script.
window.loadMermaid ? runMermaid() : getScript('!{url_for(theme.asset.mermaid)}').then(runMermaid)
}
btf.addModeChange('mermaid', runMermaid)
btf.addGlobalFn('themeChange', runMermaid, 'mermaid')
window.pjax ? loadMermaid() : document.addEventListener('DOMContentLoaded', loadMermaid)
})()

View File

@@ -1,3 +1,5 @@
- const serverURL = theme.waline.serverURL.replace(/\/$/, '')
script.
window.addEventListener('load', () => {
const changeContent = content => {
@@ -43,7 +45,7 @@ script.
const getComment = async () => {
try {
const res = await fetch('!{theme.waline.serverURL}/api/comment?type=recent&count=!{theme.newest_comments.limit}', { method: 'GET' })
const res = await fetch('!{serverURL}/api/comment?type=recent&count=!{theme.newest_comments.limit}', { method: 'GET' })
const result = await res.json()
const walineArray = result.data.map(e => {
return {

View File

@@ -26,11 +26,11 @@ script.
document.addEventListener('pjax:send', function () {
// removeEventListener scroll
window.tocScrollFn && window.removeEventListener('scroll', window.tocScrollFn)
window.scrollCollect && window.removeEventListener('scroll', scrollCollect)
// removeEventListener
btf.removeGlobalFnEvent('pjax')
btf.removeGlobalFnEvent('themeChange')
document.getElementById('rightside').style.cssText = "opacity: ''; transform: ''"
document.getElementById('rightside').classList.remove('rightside-show')
if (window.aplayers) {
for (let i = 0; i < window.aplayers.length; i++) {
@@ -76,8 +76,8 @@ script.
typeof Prism === 'object' && Prism.highlightAll()
})
document.addEventListener('pjax:error', (e) => {
document.addEventListener('pjax:error', e => {
if (e.request.status === 404) {
pjax.loadUrl('/404.html')
pjax.loadUrl('!{url_for("/404.html")}')
}
})

View File

@@ -13,11 +13,12 @@
container: '#docsearch',
}, !{JSON.stringify(option)}))
const handleClick = () => {
document.querySelector('.DocSearch-Button').click()
}
const searchClickFn = () => {
document.querySelector('#search-button > .search').addEventListener('click', () => {
document.querySelector('.DocSearch-Button').click()
})
btf.addEventListenerPjax(document.querySelector('#search-button > .search'), 'click', handleClick)
}
searchClickFn()

View File

@@ -1,6 +1,6 @@
{
"name": "hexo-theme-butterfly",
"version": "4.9.0",
"version": "4.10.0-b1",
"description": "A Simple and Card UI Design theme for Hexo",
"main": "package.json",
"scripts": {

View File

@@ -5,7 +5,7 @@ algolia_search:
instantsearch:
name: instantsearch.js
file: dist/instantsearch.production.min.js
version: 4.56.3
version: 4.56.5
pjax:
name: pjax
file: pjax.min.js
@@ -73,7 +73,7 @@ katex_copytex:
mermaid:
name: mermaid
file: dist/mermaid.min.js
version: 10.2.3
version: 10.2.4
canvas_ribbon:
name: butterfly-extsrc
file: dist/canvas-ribbon.min.js
@@ -105,7 +105,7 @@ ClickShowText:
lazyload:
name: vanilla-lazyload
file: dist/lazyload.iife.min.js
version: 17.8.3
version: 17.8.4
instantpage:
name: instant.page
file: instantpage.js
@@ -121,12 +121,12 @@ pangu:
fancybox_css:
name: '@fancyapps/ui'
file: dist/fancybox/fancybox.css
version: 5.0.19
version: 5.0.20
other_name: fancyapps-ui
fancybox:
name: '@fancyapps/ui'
file: dist/fancybox/fancybox.umd.js
version: 5.0.19
version: 5.0.20
other_name: fancyapps-ui
medium_zoom:
name: medium-zoom
@@ -145,14 +145,11 @@ fontawesome:
file: css/all.min.css
other_name: font-awesome
version: 6.4.0
flickr_justified_gallery_js:
name: flickr-justified-gallery
file: dist/fjGallery.min.js
version: 2.1.2
flickr_justified_gallery_css:
name: flickr-justified-gallery
file: dist/fjGallery.css
version: 2.1.2
egjs_infinitegrid:
name: '@egjs/infinitegrid'
other_name: egjs-infinitegrid
file: dist/infinitegrid.min.js
version: 4.10.0
aplayer_css:
name: aplayer
file: dist/APlayer.min.css

View File

@@ -1,15 +1,16 @@
/**
* stylus
* Stylus renderer
*/
'use strict'
hexo.extend.filter.register('stylus:renderer', function (style) {
const { highlight, prismjs } = hexo.config
style
.define('$highlight_enable', highlight && highlight.enable)
.define('$highlight_line_number', highlight && highlight.line_number)
.define('$prismjs_enable', prismjs && prismjs.enable)
.define('$prismjs_line_number', prismjs && prismjs.line_number)
// .import(this.source_dir.replace(/\\/g, '/') + '_data/css/*')
hexo.extend.filter.register('stylus:renderer', style => {
const { enable: highlightEnable, line_number: highlightLineNumber } = hexo.config.highlight
const { enable: prismjsEnable, line_number: prismjsLineNumber } = hexo.config.prismjs
style.define('$highlight_enable', highlightEnable)
.define('$highlight_line_number', highlightLineNumber)
.define('$prismjs_enable', prismjsEnable)
.define('$prismjs_line_number', prismjsLineNumber)
// .import(`${this.source_dir.replace(/\\/g, '/')}_data/css/*`)
})

View File

@@ -8,22 +8,20 @@
const urlFor = require('hexo-util').url_for.bind(hexo)
function lazyload (htmlContent) {
const lazyload = htmlContent => {
const bg = hexo.theme.config.lazyload.placeholder ? urlFor(hexo.theme.config.lazyload.placeholder) : 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'
return htmlContent.replace(/(<img.*? src=)/ig, `$1 "${bg}" data-lazy-src=`)
}
hexo.extend.filter.register('after_render:html', function (data) {
const config = hexo.theme.config.lazyload
if (!config.enable) return
if (config.field !== 'site') return
return lazyload.call(this, data)
hexo.extend.filter.register('after_render:html', data => {
const { enable, field } = hexo.theme.config.lazyload
if (!enable || field !== 'site') return
return lazyload(data)
})
hexo.extend.filter.register('after_post_render', data => {
const config = hexo.theme.config.lazyload
if (!config.enable) return
if (config.field !== 'post') return
data.content = lazyload.call(this, data.content)
const { enable, field } = hexo.theme.config.lazyload
if (!enable || field !== 'post') return
data.content = lazyload(data.content)
return data
})

View File

@@ -5,40 +5,35 @@
'use strict'
hexo.extend.filter.register('before_post_render', function (data) {
hexo.extend.filter.register('before_post_render', data => {
const imgTestReg = /\.(png|jpe?g|gif|svg|webp)(\?.*)?$/i
let randomCover
let coverVal = data.cover
let { cover: coverVal, top_img: topImg } = data
// Add path to top_img and cover if post_asset_folder is enabled
if (hexo.config.post_asset_folder) {
const topImg = data.top_img
if (topImg && topImg.indexOf('/') === -1 && imgTestReg.test(topImg)) data.top_img = data.path + topImg
if (coverVal && coverVal.indexOf('/') === -1 && imgTestReg.test(coverVal)) data.cover = data.path + coverVal
if (topImg && topImg.indexOf('/') === -1 && imgTestReg.test(topImg)) data.top_img = `${data.path}${topImg}`
if (coverVal && coverVal.indexOf('/') === -1 && imgTestReg.test(coverVal)) data.cover = `${data.path}${coverVal}`
}
const randomCoverFn = () => {
const theme = hexo.theme.config
if (!(theme.cover && theme.cover.default_cover)) return false
if (!Array.isArray(theme.cover.default_cover)) return theme.cover.default_cover
const num = Math.floor(Math.random() * theme.cover.default_cover.length)
return theme.cover.default_cover[num]
const { cover: { default_cover: defaultCover } } = hexo.theme.config
if (!defaultCover) return false
if (!Array.isArray(defaultCover)) return defaultCover
const num = Math.floor(Math.random() * defaultCover.length)
return defaultCover[num]
}
if (coverVal === false) return data
// If cover is not set, use random cover
if (!coverVal) {
randomCover = randomCoverFn()
const randomCover = randomCoverFn()
data.cover = randomCover
coverVal = randomCover // update coverVal
}
if (coverVal) {
if (coverVal.indexOf('//') !== -1 || imgTestReg.test(coverVal)) {
data.cover_type = 'img'
return data
}
if (coverVal && (coverVal.indexOf('//') !== -1 || imgTestReg.test(coverVal))) {
data.cover_type = 'img'
}
return data

View File

@@ -31,6 +31,7 @@ hexo.extend.helper.register('aside_categories', function (categories, options) {
if (parent) { query.parent = parent } else { query.parent = { $exists: false } }
return categories.find(query).sort(orderby, order).filter((cat) => cat.length)
}
let expandBtn = ''
const hierarchicalList = (t, level, parent, topparent = true) => {
let result = ''
@@ -59,6 +60,7 @@ hexo.extend.helper.register('aside_categories', function (categories, options) {
}
if (isExpand && isTopParent && child) {
expandBtn = ' expandBtn'
result += `<i class="fas fa-caret-left ${expandClass}"></i>`
}
@@ -91,7 +93,7 @@ hexo.extend.helper.register('aside_categories', function (categories, options) {
<span>${this._p('aside.card_categories')}</span>
${moreButton()}
</div>
<ul class="card-category-list" id="aside-cat-list">
<ul class="card-category-list${expandBtn}" id="aside-cat-list">
${list[0]}
</ul>`
})

View File

@@ -1,36 +1,36 @@
hexo.extend.helper.register('findArchiveLength', function (func) {
const allPostsLength = this.site.posts.length
if (hexo.config.archive_generator && hexo.config.archive_generator.enable === false) return allPostsLength
const { yearly, monthly, daily } = hexo.config.archive_generator
hexo.extend.helper.register('getArchiveLength', function () {
const { archive_generator: archiveGenerator } = hexo.config
if (archiveGenerator && archiveGenerator.enable === false) return this.site.posts.length
const { yearly, monthly, daily } = archiveGenerator
const { year, month, day } = this.page
if (yearly === false || !year) return allPostsLength
if (yearly === false || !year) return this.site.posts.length
const posts = this.site.posts.sort('date')
const compareFunc = (type, y1, m1, d1, y2, m2, d2) => {
if (type === 'year') {
return y1 === y2
} else if (type === 'month') {
return y1 === y2 && m1 === m2
} else if (type === 'day') {
return y1 === y2 && m1 === m2 && d1 === d2
switch (type) {
case 'year':
return y1 === y2
case 'month':
return y1 === y2 && m1 === m2
case 'day':
return y1 === y2 && m1 === m2 && d1 === d2
default:
return false
}
}
const generateDateObj = (type) => {
const dateObj = []
let length = 0
posts.forEach(post => {
return posts.reduce((dateObj, post) => {
const date = post.date.clone()
const year = date.year()
const month = date.month() + 1
const day = date.date()
const lastData = dateObj[length - 1]
const lastData = dateObj[dateObj.length - 1]
if (!lastData || !compareFunc(type, lastData.year, lastData.month, lastData.day, year, month, day)) {
const name = type === 'year' ? year : type === 'month' ? `${year}-${month}` : `${year}-${month}-${day}`
length = dateObj.push({
dateObj.push({
name,
year,
month,
@@ -40,19 +40,19 @@ hexo.extend.helper.register('findArchiveLength', function (func) {
} else {
lastData.count++
}
})
return dateObj
return dateObj
}, [])
}
const data = func('createArchiveObj', () => {
const yearObj = yearly ? generateDateObj('year') : []
const monthObj = monthly ? generateDateObj('month') : []
const dayObj = daily ? generateDateObj('day') : []
const fullObj = [...yearObj, ...monthObj, ...dayObj]
return fullObj
const data = this.fragment_cache('createArchiveObj', () => {
const dateObjs = []
if (yearly) dateObjs.push(...generateDateObj('year'))
if (monthly) dateObjs.push(...generateDateObj('month'))
if (daily) dateObjs.push(...generateDateObj('day'))
return dateObjs
})
const name = month ? day ? `${year}-${month}-${day}` : `${year}-${month}` : year
const name = month ? (day ? `${year}-${month}-${day}` : `${year}-${month}`) : year
return data.find(item => item.name === name).count
})

View File

@@ -7,87 +7,99 @@
hexo.extend.helper.register('inject_head_js', function () {
const { darkmode, aside } = this.theme
const start = darkmode.start ? darkmode.start : 6
const end = darkmode.end ? darkmode.end : 18
const start = darkmode.start || 6
const end = darkmode.end || 18
const { theme_color } = hexo.theme.config
const themeColorLight = (theme_color && theme_color.enable && theme_color.meta_theme_color_light) || '#ffffff'
const themeColorDark = (theme_color && theme_color.enable && theme_color.meta_theme_color_dark) || '#0d0d0d'
const localStore = `
win.saveToLocal = {
set: function setWithExpiry(key, value, ttl) {
if (ttl === 0) return
const now = new Date()
const expiryDay = ttl * 86400000
const item = {
value: value,
expiry: now.getTime() + expiryDay,
const createLocalStore = () => {
return `
win.saveToLocal = {
set: (key, value, ttl) => {
if (ttl === 0) return
const now = Date.now()
const expiry = now + ttl * 86400000
const item = {
value,
expiry
}
localStorage.setItem(key, JSON.stringify(item))
},
get: key => {
const itemStr = localStorage.getItem(key)
if (!itemStr) {
return undefined
}
const item = JSON.parse(itemStr)
const now = Date.now()
if (now > item.expiry) {
localStorage.removeItem(key)
return undefined
}
return item.value
}
localStorage.setItem(key, JSON.stringify(item))
},
get: function getWithExpiry(key) {
const itemStr = localStorage.getItem(key)
if (!itemStr) {
return undefined
}
const item = JSON.parse(itemStr)
const now = new Date()
if (now.getTime() > item.expiry) {
localStorage.removeItem(key)
return undefined
}
return item.value
}
}
`
`
}
// https://stackoverflow.com/questions/16839698/jquery-getscript-alternative-in-native-javascript
const getScript = `
win.getScript = url => new Promise((resolve, reject) => {
const script = document.createElement('script')
script.src = url
script.async = true
script.onerror = reject
script.onload = script.onreadystatechange = function() {
const loadState = this.readyState
if (loadState && loadState !== 'loaded' && loadState !== 'complete') return
script.onload = script.onreadystatechange = null
resolve()
}
document.head.appendChild(script)
})
`
const createGetScript = () => {
return `
win.getScript = (url, attr = {}) => new Promise((resolve, reject) => {
const script = document.createElement('script')
script.src = url
script.async = true
script.onerror = reject
script.onload = script.onreadystatechange = function() {
const loadState = this.readyState
if (loadState && loadState !== 'loaded' && loadState !== 'complete') return
script.onload = script.onreadystatechange = null
resolve()
}
const getCSS = `
win.getCSS = (url,id = false) => new Promise((resolve, reject) => {
const link = document.createElement('link')
link.rel = 'stylesheet'
link.href = url
if (id) link.id = id
link.onerror = reject
link.onload = link.onreadystatechange = function() {
const loadState = this.readyState
if (loadState && loadState !== 'loaded' && loadState !== 'complete') return
link.onload = link.onreadystatechange = null
resolve()
}
document.head.appendChild(link)
})
`
Object.keys(attr).forEach(key => {
script.setAttribute(key, attr[key])
})
let darkmodeJs = ''
if (darkmode.enable) {
darkmodeJs = `
win.activateDarkMode = function () {
document.head.appendChild(script)
})
`
}
const createGetCSS = () => {
return `
win.getCSS = (url, id = false) => new Promise((resolve, reject) => {
const link = document.createElement('link')
link.rel = 'stylesheet'
link.href = url
if (id) link.id = id
link.onerror = reject
link.onload = link.onreadystatechange = function() {
const loadState = this.readyState
if (loadState && loadState !== 'loaded' && loadState !== 'complete') return
link.onload = link.onreadystatechange = null
resolve()
}
document.head.appendChild(link)
})
`
}
const createDarkmodeJs = () => {
if (!darkmode.enable) return ''
let darkmodeJs = `
win.activateDarkMode = () => {
document.documentElement.setAttribute('data-theme', 'dark')
if (document.querySelector('meta[name="theme-color"]') !== null) {
document.querySelector('meta[name="theme-color"]').setAttribute('content', '${themeColorDark}')
}
}
win.activateLightMode = function () {
win.activateLightMode = () => {
document.documentElement.setAttribute('data-theme', 'light')
if (document.querySelector('meta[name="theme-color"]') !== null) {
document.querySelector('meta[name="theme-color"]').setAttribute('content', '${themeColorLight}')
@@ -114,7 +126,7 @@ hexo.extend.helper.register('inject_head_js', function () {
const isNight = hour <= ${start} || hour >= ${end}
isNight ? activateDarkMode() : activateLightMode()
}
window.matchMedia('(prefers-color-scheme: dark)').addListener(function (e) {
window.matchMedia('(prefers-color-scheme: dark)').addListener(e => {
if (saveToLocal.get('theme') === undefined) {
e.matches ? activateDarkMode() : activateLightMode()
}
@@ -133,15 +145,18 @@ hexo.extend.helper.register('inject_head_js', function () {
`
} else {
darkmodeJs += `
if (t === 'dark') activateDarkMode()
else if (t === 'light') activateLightMode()
`
if (t === 'dark') activateDarkMode()
else if (t === 'light') activateLightMode()
`
}
return darkmodeJs
}
let asideStatus = ''
if (aside.enable && aside.button) {
asideStatus = `
const createAsideStatus = () => {
if (!aside.enable || !aside.button) return ''
return `
const asideStatus = saveToLocal.get('aside-status')
if (asideStatus !== undefined) {
if (asideStatus === 'hide') {
@@ -153,14 +168,16 @@ hexo.extend.helper.register('inject_head_js', function () {
`
}
const detectApple = `
const detectApple = () => {
if(/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)){
document.documentElement.classList.add('apple')
const createDetectApple = () => {
return `
const detectApple = () => {
if(/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)){
document.documentElement.classList.add('apple')
}
}
}
detectApple()
detectApple()
`
}
return `<script>(win=>{${localStore + getScript + getCSS + darkmodeJs + asideStatus + detectApple}})(window)</script>`
return `<script>(win=>{${createLocalStore() + createGetScript() + createGetCSS() + createDarkmodeJs() + createAsideStatus() + createDetectApple()}})(window)</script>`
})

View File

@@ -1,10 +1,3 @@
/**
* Butterfly
* @example
* page_description()
* cloudTags(source, minfontsize, maxfontsize, limit)
*/
'use strict'
const { stripHTML, escapeHTML, prettyUrls } = require('hexo-util')
@@ -60,12 +53,8 @@ hexo.extend.helper.register('md5', function (path) {
})
hexo.extend.helper.register('injectHtml', function (data) {
let result = ''
if (!data) return ''
for (let i = 0; i < data.length; i++) {
result += data[i]
}
return result
return data.join('')
})
hexo.extend.helper.register('findArchivesTitle', function (page, menu, date) {
@@ -95,8 +84,5 @@ hexo.extend.helper.register('findArchivesTitle', function (page, menu, date) {
hexo.extend.helper.register('isImgOrUrl', function (path) {
const imgTestReg = /\.(png|jpe?g|gif|svg|webp)(\?.*)?$/i
if (path.indexOf('//') !== -1 || imgTestReg.test(path)) {
return true
}
return false
return path.indexOf('//') !== -1 || imgTestReg.test(path)
})

View File

@@ -2,8 +2,9 @@
* Butterfly
* galleryGroup and gallery
* {% galleryGroup [name] [descr] [url] [img] %}
* {% gallery [lazyload],[rowHeight],[limit] %}
* {% gallery url,[url],[lazyload],[rowHeight],[limit] %}
*
* {% gallery [button],%}
* {% gallery url,[url],[button]%}
*/
'use strict'
@@ -11,14 +12,15 @@
const urlFor = require('hexo-util').url_for.bind(hexo)
const gallery = (args, content) => {
const { data, languages } = hexo.theme.i18n
args = args.join(' ').split(',')
let rowHeight, limit, lazyload, type, dataStr
let button = false
let type = 'data'
let dataStr = ''
if (args[0] === 'url') {
[type, dataStr, lazyload, rowHeight = 220, limit = 10] = args // url,[link],[lazyload],[rowHeight],[limit]
[type, dataStr, button] = args // url,[link],[lazyload]
} else {
[lazyload, rowHeight = 220, limit = 10] = args // [lazyload],[rowHeight],[limit]
[button] = args // [lazyload]
const regex = /!\[(.*?)\]\(([^\s]*)\s*(?:["'](.*?)["']?)?\s*\)/g
let m
const arr = []
@@ -36,14 +38,10 @@ const gallery = (args, content) => {
dataStr = JSON.stringify(arr)
}
type = type ? ' url' : ' data'
const lazyloadClass = lazyload === 'true' ? 'lazyload' : ''
return `<div class="gallery">
<div class="fj-gallery ${lazyloadClass + type}" data-rowHeight="${rowHeight}" data-limit="${limit}">
<span class="gallery-data">${dataStr}</span>
</div>
<button class="gallery-load-more"><span>${data[languages[0]].load_more}</span><i class="fa-solid fa-arrow-down"></i></button>
return `<div class="gallery-container" data-type="${type}" data-button="${button}">
<div class="gallery-data">${dataStr}</div>
<div class="gallery-items">
</div>
</div>`
}
@@ -52,8 +50,7 @@ const galleryGroup = args => {
const imgUrl = urlFor(img)
const urlLink = urlFor(url)
return `
<figure class="gallery-group">
return `<figure class="gallery-group">
<img class="gallery-group-img no-lightbox" src='${imgUrl}' alt="Group Image Gallery">
<figcaption>
<div class="gallery-group-name">${name}</div>

View File

@@ -16,6 +16,7 @@ const postTabs = (args, content) => {
let tabId = 0
let tabNav = ''
let tabContent = ''
let noDefault = true
!tabName && hexo.log.warn('Tabs block must have unique name!')
@@ -41,17 +42,21 @@ const postTabs = (args, content) => {
const icon = tabIcon.trim()
tabIcon.length > 0 && (tabIcon = `<i class="${icon}"${isOnlyicon}></i>`)
const toTop = '<button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button>'
const isActive = (tabActive > 0 && tabActive === tabId) || (tabActive === 0 && tabId === 1) ? ' active' : ''
tabNav += `<li class="tab${isActive}"><button type="button" data-href="#${tabHref}">${tabIcon + tabCaption.trim()}</button></li>`
tabContent += `<div class="tab-item-content${isActive}" id="${tabHref}">${postContent + toTop}</div>`
let isActive = ''
if ((tabActive > 0 && tabActive === tabId) || (tabActive === 0 && tabId === 1)) {
isActive = ' active'
noDefault = false
}
tabNav += `<button type="button" class="tab ${isActive}" data-href="${tabHref}">${tabIcon + tabCaption.trim()}</button>`
tabContent += `<div class="tab-item-content${isActive}" id="${tabHref}">${postContent}</div>`
}
tabNav = `<ul class="nav-tabs">${tabNav}</ul>`
const toTop = '<div class="tab-to-top"><button type="button" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div>'
tabNav = `<ul class="nav-tabs${noDefault ? ' no-default' : ''}">${tabNav}</ul>`
tabContent = `<div class="tab-contents">${tabContent}</div>`
return `<div class="tabs" id="${tabName.toLowerCase().split(' ').join('-')}">${tabNav + tabContent}</div>`
return `<div class="tabs" id="${tabName.toLowerCase().split(' ').join('-')}">${tabNav + tabContent + toTop}</div>`
}
hexo.extend.tag.register('tabs', postTabs, { ends: true })

View File

@@ -155,7 +155,7 @@ if hexo-config('enter_transitions')
&.open
for i in 1 2 3 4
> :nth-child({i})
animation: sidebarItem (i / 5)s
animation: sidebarItem (i / 5s)
.scroll-down-effects
animation: scroll-down-effect 1.5s infinite
@@ -169,16 +169,16 @@ if hexo-config('avatar.effect') == true
@keyframes scroll-down-effect
0%
top: 0
opacity: .4
transform: translate(0, 0)
50%
top: -16px
opacity: 1
transform: translate(0, -16px)
100%
top: 0
opacity: .4
transform: translate(0, 0)
@keyframes header-effect
0%
@@ -198,12 +198,12 @@ if hexo-config('avatar.effect') == true
@keyframes bottom-top
0%
margin-top: 50px
opacity: 0
transform: translateY(50px)
100%
margin-top: 0
opacity: 1
transform: translateY(0)
@keyframes titleScale
0%

View File

@@ -13,6 +13,16 @@ wordWrap = $highlight_enable && !$highlight_line_number && hexo-config('code_wor
--hlscrollbar-bg: $highlight-scrollbar
--hlexpand-bg: linear-gradient(180deg, rgba($highlight-background, .6), rgba($highlight-background, .9))
[data-theme='dark']
--hl-color: alpha(#FFFFFF, .7)
--hl-bg: lighten(#121212, 2)
--hltools-bg: lighten(#121212, 3)
--hltools-color: #90a4ae
--hlnumber-bg: lighten(#121212, 2)
--hlnumber-color: alpha(#FFFFFF, .4)
--hlscrollbar-bg: lighten(#121212, 5)
--hlexpand-bg: linear-gradient(180deg, rgba(lighten(#121212, 2), .6), rgba(lighten(#121212, 2), .9))
if $highlight_enable
@require 'highlight/index'
@@ -94,8 +104,13 @@ $code-block
color: var(--hltools-color)
font-size: $code-font-size
&.closed ~ *
display: none
&.closed
& ~ *
display: none
.expand
transition: all .3s
transform: rotate(-90deg) !important
.expand
position: absolute
@@ -106,10 +121,6 @@ $code-block
& + .code-lang
left: 1.7em
&.closed
transition: all .3s
transform: rotate(-90deg) !important
.code-lang
position: absolute
left: 14px

View File

@@ -179,6 +179,13 @@
> .parent
> a
&.expand
i
transform: rotate(-90deg)
& + .child
display: block
.card-category-list
&-name
width: 70% !important
@@ -187,15 +194,12 @@
width: calc(100% - 70% - 20px)
text-align: right
i
float: right
margin-right: -.5em
padding: .5em
transition: transform .3s
transform: rotate(0)
&.expand
transform: rotate(-90deg)
i
float: right
margin-right: -.5em
padding: .5em
transition: transform .3s
transform: rotate(0)
if hexo-config('aside.card_categories.expand') == false
> .child
@@ -220,16 +224,19 @@
+maxWidth900()
position: fixed
right: -100%
right: 55px
bottom: 30px
z-index: 100
max-width: $toc-mobile-maxWidth
max-height: calc(100% - 60px)
width: $toc-mobile-width
opacity: 0
transition: initial
transition: transform .3s
transform: scale(0)
transform-origin: right bottom
&.open
transform: scale(1)
.toc-percentage
float: right
margin-top: -9px
@@ -242,6 +249,7 @@
overflow-y: overlay
margin: 0 -24px
max-height: calc(100vh - 120px)
width: calc(100% + 48px)
+maxWidth900()
max-height: calc(100vh - 140px)

View File

@@ -8,7 +8,7 @@
font-weight: 700
font-size: 1.43em
#comment-switch
.comment-switch
display: inline-block
if hexo-config('comments.text')
@@ -30,7 +30,7 @@
.second-comment
color: $comments-switch-second-text
.switch-btn
#switch-btn
position: relative
display: inline-block
margin: -4px 8px 0
@@ -53,12 +53,24 @@
content: ''
transition: .4s
&.move
background-color: $comments-switch-second-text
&:before
transform: translateX(20px)
.comment-wrap
> div:nth-child(2)
display: none
> div
animation: tabshow .5s
&:nth-child(2)
display: none
&.move
#switch-btn
background-color: $comments-switch-second-text
&:before
transform: translateX(20px)
.comment-wrap
> div
&:first-child
display: none
&:last-child
display: block

View File

@@ -51,7 +51,6 @@
#site_social_icons
display: none
margin: 0 auto
width: 300px
text-align: center
+maxWidth768()
@@ -65,7 +64,7 @@
#scroll-down
position: absolute
bottom: 0
bottom: 10px
width: 100%
cursor: pointer
@@ -73,7 +72,7 @@
position: relative
width: 100%
color: var(--light-grey)
font-size: 30px
font-size: 20px
// page
&.not-home-page

View File

@@ -72,6 +72,7 @@ beautify()
#article-container
word-wrap: break-word
overflow-wrap: break-word
text-align: justify
a
color: $theme-link-color
@@ -226,6 +227,9 @@ beautify()
color: $light-blue
font-weight: bold
i
margin-right: 3px
&-info
padding-left: 6px

View File

@@ -6,6 +6,10 @@
opacity: 0
transition: all .5s
&.rightside-show
opacity: .8
transform: translate(-58px, 0)
#rightside-config-hide
height: 0
opacity: 0

View File

@@ -13,7 +13,8 @@
right: -($sidebar-width)
z-index: 103
overflow-x: hidden
overflow-y: auto
overflow-y: scroll
padding-left: 5px
width: $sidebar-width
height: 100%
background: var(--sidebar-bg)
@@ -32,7 +33,7 @@
margin: 20px auto
.menus_items
padding: 0 10px 40px
padding: 0 5px
.site-page
@extend .limit-one-line
@@ -53,7 +54,7 @@
& > i:last-child
position: absolute
top: .78em
right: 18px
right: 13px
transition: transform .3s
&.hide
@@ -65,4 +66,5 @@
.menus_item_child
margin: 0
padding-left: 25px
list-style: none

View File

@@ -28,15 +28,7 @@ if hexo-config('darkmode.enable') || hexo-config('display_mode') == 'dark'
--blockquote-bg: lighten(#121212, 10)
--reward-pop: lighten(#121212, 10)
--toc-link-color: alpha(#FFFFFF, .6)
--hl-color: alpha(#FFFFFF, .7)
--hl-bg: lighten(#121212, 2)
--hltools-bg: lighten(#121212, 3)
--hltools-color: #90a4ae
--hlnumber-bg: lighten(#121212, 2)
--hlnumber-color: alpha(#FFFFFF, .4)
--hlscrollbar-bg: lighten(#121212, 5)
--hlexpand-bg: linear-gradient(180deg, rgba(lighten(#121212, 2), .6), rgba(lighten(#121212, 2), .9))
--scrollbar-color: lighten(#121212, 5)
--scrollbar-color: lighten(#121212, 25)
--timeline-bg: lighten(#121212, 5)
--zoom-bg: #121212
--mark-bg: alpha($dark-black, .6)
@@ -78,11 +70,11 @@ if hexo-config('darkmode.enable') || hexo-config('display_mode') == 'dark'
box-shadow: 0 5px 6px -5px rgba(133, 133, 133, 0)
#post-comment
#comment-switch
.comment-switch
if hexo-config('comments.text')
background: #2c2c2c !important
.switch-btn
#switch-btn
filter: brightness(.8)
// note

View File

@@ -145,18 +145,7 @@ if hexo-config('readmode')
background: transparent
> .tab
border-bottom: 0
button
border-top: none !important
background: transparent
&:hover
background: none !important
&.active
button
text-decoration: underline
border-top: none !important
> .tab-contents .tab-item-content.active
animation: none

View File

@@ -94,25 +94,20 @@
overflow: auto
padding: 0 0 16px
.gallery
.gallery-container
margin: 0 0 16px
text-align: center
.fj-gallery
opacity: 0
img
display: initial
margin: 0
width: 100%
height: 100%
.img-alt
display: none
&.lazyload
+ button
display: inline-block
.gallery-data
display: none
.gallery-data
display: none
button
display: none
margin-top: 25px
padding: 10px
width: 9em
@@ -123,17 +118,88 @@
font-size: 1.1em
transition: all .3s
& > *
transition: all .4s
i
width: 0
opacity: 0
&:hover
background: var(--btn-hover-color)
i
margin-left: 2px
width: 20px
opacity: 1
.loading-container
display: inline-block
overflow: hidden
width: 154px
height: 154px
.loading-item
position: relative
width: 100%
height: 100%
backface-visibility: hidden
transform: translateZ(0) scale(1)
transform-origin: 0 0
div
position: absolute
width: 30.8px
height: 30.8px
border-radius: 50%
background: #e15b64
transform: translate(61.6px, 61.6px) scale(1)
animation: loading-ball 1.92s infinite cubic-bezier(0, .5, .5, 1)
&:nth-child(1)
background: #f47e60
transform: translate(113.96px, 61.6px) scale(1)
animation: loading-ball-r .48s infinite cubic-bezier(0, .5, .5, 1), loading-ball-c 1.92s infinite step-start
&:nth-child(2)
background: #e15b64
animation-delay: -.48s
&:nth-child(3)
background: #f47e60
animation-delay: -.96s
&:nth-child(4)
background: #f8b26a
animation-delay: -1.44s
&:nth-child(5)
background: #abbd81
animation-delay: -1.92s
@keyframes loading-ball
0%
transform: translate(9.24px, 61.6px) scale(0)
25%
transform: translate(9.24px, 61.6px) scale(0)
50%
transform: translate(9.24px, 61.6px) scale(1)
75%
transform: translate(61.6px, 61.6px) scale(1)
100%
transform: translate(113.96px, 61.6px) scale(1)
@keyframes loading-ball-r
0%
transform: translate(113.96px, 61.6px) scale(1)
100%
transform: translate(113.96px, 61.6px) scale(0)
@keyframes loading-ball-c
0%
background: #e15b64
25%
background: #abbd81
50%
background: #f8b26a
75%
background: #f47e60
100%
background: #e15b64

View File

@@ -15,43 +15,36 @@
background: var(--tab-botton-bg)
> .tab
margin: 0
padding: 0
list-style: none
flex-grow: 1
padding: 8px 18px
border-top: 2px solid var(--tab-border-color)
background: var(--tab-botton-bg)
color: var(--tab-botton-color)
line-height: 2
transition: all .4s
+maxWidth768()
flex-grow: 1
button
display: block
padding: 8px 18px
width: 100%
border-top: 2px solid var(--tab-border-color)
background: var(--tab-botton-bg)
color: var(--tab-botton-color)
line-height: 2
transition: all .4s
i
width: 1.5em
i
width: 1.5em
&.active
button
border-top: 2px solid $tab-active-border-color
background: var(--tab-button-active-bg)
cursor: default
border-top: 2px solid $tab-active-border-color
background: var(--tab-button-active-bg)
cursor: default
&:not(.active)
button
&:hover
border-top: 2px solid var(--tab-button-hover-bg)
background: var(--tab-button-hover-bg)
&:hover
border-top: 2px solid var(--tab-button-hover-bg)
background: var(--tab-button-hover-bg)
&.no-default
& ~ .tab-to-top
display: none
> .tab-contents
.tab-item-content
position: relative
display: none
padding: 36px 24px
padding: 36px 24px 10px
+maxWidth768()
padding: 24px 14px
@@ -60,11 +53,16 @@
display: block
animation: tabshow .5s
.tab-to-top
position: absolute
right: 15px
display: block
color: $tab-to-top-color
> .tab-to-top
padding: 0 16px 10px 0
width: 100%
text-align: right
button
color: $tab-to-top-color
&:hover
color: $tab-to-top-hover-color
@keyframes tabshow
0%

View File

@@ -179,6 +179,7 @@ $tab-button-hover-bg = darken($tab-border-color, 8)
$tab-active-border-color = $theme-color
$tab-button-active-bg = $card-bg
$tab-to-top-color = #99a9bf
$tab-to-top-hover-color = $theme-color
// Tag Plugins - timeline
$timeline-default-color = $theme-color
// archor

View File

@@ -4,9 +4,7 @@ document.addEventListener('DOMContentLoaded', function () {
const adjustMenu = init => {
const getAllWidth = ele => {
let width = 0
ele.length && Array.from(ele).forEach(i => { width += i.offsetWidth })
return width
return Array.from(ele).reduce((width, i) => width + i.offsetWidth, 0)
}
if (init) {
@@ -16,15 +14,8 @@ document.addEventListener('DOMContentLoaded', function () {
$nav = document.getElementById('nav')
}
let hideMenuIndex = ''
if (window.innerWidth <= 768) hideMenuIndex = true
else hideMenuIndex = headerContentWidth > $nav.offsetWidth - 120
if (hideMenuIndex) {
$nav.classList.add('hide-menu')
} else {
$nav.classList.remove('hide-menu')
}
const hideMenuIndex = window.innerWidth <= 768 || headerContentWidth > $nav.offsetWidth - 120
$nav.classList.toggle('hide-menu', hideMenuIndex)
}
// 初始化header
@@ -56,17 +47,19 @@ document.addEventListener('DOMContentLoaded', function () {
* 首頁top_img底下的箭頭
*/
const scrollDownInIndex = () => {
const $scrollDownEle = document.getElementById('scroll-down')
$scrollDownEle && $scrollDownEle.addEventListener('click', function () {
const handleScrollToDest = () => {
btf.scrollToDest(document.getElementById('content-inner').offsetTop, 300)
})
}
const $scrollDownEle = document.getElementById('scroll-down')
$scrollDownEle && btf.addEventListenerPjax($scrollDownEle, 'click', handleScrollToDest)
}
/**
* 代碼
* 只適用於Hexo默認的代碼渲染
*/
const addHighlightTool = function () {
const addHighlightTool = () => {
const highLight = GLOBAL_CONFIG.highlight
if (!highLight) return
@@ -79,7 +72,7 @@ document.addEventListener('DOMContentLoaded', function () {
const isPrismjs = plugin === 'prismjs'
const highlightShrinkClass = isHighlightShrink === true ? 'closed' : ''
const highlightShrinkEle = isHighlightShrink !== undefined ? `<i class="fas fa-angle-down expand ${highlightShrinkClass}"></i>` : ''
const highlightShrinkEle = isHighlightShrink !== undefined ? '<i class="fas fa-angle-down expand"></i>' : ''
const highlightCopyEle = highlightCopy ? '<div class="copy-notice"></div><i class="fas fa-paste copy-button"></i>' : ''
const copy = (text, ctx) => {
@@ -103,7 +96,7 @@ document.addEventListener('DOMContentLoaded', function () {
}
// click events
const highlightCopyFn = (ele) => {
const highlightCopyFn = ele => {
const $buttonParent = ele.parentNode
$buttonParent.classList.add('copy-true')
const selection = window.getSelection()
@@ -118,14 +111,8 @@ document.addEventListener('DOMContentLoaded', function () {
$buttonParent.classList.remove('copy-true')
}
const highlightShrinkFn = (ele) => {
const $nextEle = [...ele.parentNode.children].slice(1)
ele.firstChild.classList.toggle('closed')
if (btf.isHidden($nextEle[$nextEle.length - 1])) {
$nextEle.forEach(e => { e.style.display = 'block' })
} else {
$nextEle.forEach(e => { e.style.display = 'none' })
}
const highlightShrinkFn = ele => {
ele.classList.toggle('closed')
}
const highlightToolsFn = function (e) {
@@ -138,14 +125,14 @@ document.addEventListener('DOMContentLoaded', function () {
this.classList.toggle('expand-done')
}
function createEle (lang, item, service) {
const createEle = (lang, item, service) => {
const fragment = document.createDocumentFragment()
if (isShowTool) {
const hlTools = document.createElement('div')
hlTools.className = `highlight-tools ${highlightShrinkClass}`
hlTools.innerHTML = highlightShrinkEle + lang + highlightCopyEle
hlTools.addEventListener('click', highlightToolsFn)
btf.addEventListenerPjax(hlTools, 'click', highlightToolsFn)
fragment.appendChild(hlTools)
}
@@ -153,7 +140,7 @@ document.addEventListener('DOMContentLoaded', function () {
const ele = document.createElement('div')
ele.className = 'code-expand-btn'
ele.innerHTML = '<i class="fas fa-angle-double-down"></i>'
ele.addEventListener('click', expandCode)
btf.addEventListenerPjax(ele, 'click', expandCode)
fragment.appendChild(ele)
}
@@ -177,7 +164,7 @@ document.addEventListener('DOMContentLoaded', function () {
}
})
} else {
$figureHighlight.forEach(function (item) {
$figureHighlight.forEach(item => {
if (highlightLang) {
let langName = item.getAttribute('class').split(' ')[1]
if (langName === 'plain' || langName === undefined) langName = 'Code'
@@ -193,16 +180,14 @@ document.addEventListener('DOMContentLoaded', function () {
/**
* PhotoFigcaption
*/
function addPhotoFigcaption () {
document.querySelectorAll('#article-container img').forEach(function (item) {
const parentEle = item.parentNode
const addPhotoFigcaption = () => {
document.querySelectorAll('#article-container img').forEach(item => {
const altValue = item.title || item.alt
if (altValue && !parentEle.parentNode.classList.contains('justified-gallery')) {
const ele = document.createElement('div')
ele.className = 'img-alt is-center'
ele.textContent = altValue
parentEle.insertBefore(ele, item.nextSibling)
}
if (!altValue) return
const ele = document.createElement('div')
ele.className = 'img-alt is-center'
ele.textContent = altValue
item.insertAdjacentElement('afterend', ele)
})
}
@@ -216,97 +201,173 @@ document.addEventListener('DOMContentLoaded', function () {
/**
* justified-gallery 圖庫排版
*/
const runJustifiedGallery = function (ele) {
const htmlStr = arr => {
let str = ''
const replaceDq = str => str.replace(/"/g, '&quot;') // replace double quotes to &quot;
arr.forEach(i => {
const alt = i.alt ? `alt="${replaceDq(i.alt)}"` : ''
const title = i.title ? `title="${replaceDq(i.title)}"` : ''
str += `<div class="fj-gallery-item"><img src="${i.url}" ${alt + title}"></div>`
})
return str
const fetchUrl = async (url) => {
const response = await fetch(url)
return await response.json()
}
const runJustifiedGallery = (item, data, isButton = false, tabs) => {
const dataLength = data.length
const ig = new InfiniteGrid.JustifiedInfiniteGrid(item, {
gap: 5,
isConstantSize: true,
sizeRange: [150, 600],
useResizeObserver: true,
observeChildren: true,
useTransform: true
// useRecycle: false
})
if (tabs) {
btf.addGlobalFn('igOfTabs', () => { ig.destroy() }, false, tabs)
}
const lazyloadFn = (i, arr, limit) => {
const loadItem = limit
const arrLength = arr.length
if (arrLength > loadItem) i.insertAdjacentHTML('beforeend', htmlStr(arr.splice(0, loadItem)))
else {
i.insertAdjacentHTML('beforeend', htmlStr(arr))
i.classList.remove('lazyload')
}
return arrLength > loadItem ? loadItem : arrLength
}
const replaceDq = str => str.replace(/"/g, '&quot;') // replace double quotes to &quot;
const fetchUrl = async (url) => {
const response = await fetch(url)
return await response.json()
}
const getItems = (nextGroupKey, count) => {
const nextItems = []
const startCount = (nextGroupKey - 1) * count
const runJustifiedGallery = (item, arr) => {
if (!item.classList.contains('lazyload')) item.innerHTML = htmlStr(arr)
else {
const limit = item.getAttribute('data-limit')
lazyloadFn(item, arr, limit)
const clickBtnFn = () => {
const lastItemLength = lazyloadFn(item, arr, limit)
fjGallery(item, 'appendImages', item.querySelectorAll(`.fj-gallery-item:nth-last-child(-n+${lastItemLength})`))
btf.loadLightbox(item.querySelectorAll('img'))
lastItemLength < limit && item.nextElementSibling.removeEventListener('click', clickBtnFn)
for (let i = 0; i < count; ++i) {
const num = startCount + i
if (num >= dataLength) {
break
}
item.nextElementSibling.addEventListener('click', clickBtnFn)
const item = data[num]
const alt = item.alt ? `alt="${replaceDq(item.alt)}"` : ''
const title = item.title ? `title="${replaceDq(item.title)}"` : ''
nextItems.push(`<div class="item ">
<img src="${item.url}" data-grid-maintained-target="true" ${alt + title} />
</div>`)
}
btf.initJustifiedGallery(item)
btf.loadLightbox(item.querySelectorAll('img'))
return nextItems
}
const addJustifiedGallery = () => {
ele.forEach(item => {
item.classList.contains('url')
? fetchUrl(item.textContent).then(res => { runJustifiedGallery(item, res) })
: runJustifiedGallery(item, JSON.parse(item.textContent))
})
const buttonText = GLOBAL_CONFIG.infinitegrid.buttonText
const addButton = item => {
const button = document.createElement('button')
button.textContent = buttonText
const buttonFn = e => {
e.target.removeEventListener('click', buttonFn)
e.target.remove()
btf.setLoading.add(item)
appendItem(ig.getGroups().length + 1, 10)
}
button.addEventListener('click', buttonFn)
item.insertAdjacentElement('afterend', button)
}
if (window.fjGallery) {
addJustifiedGallery()
return
const appendItem = (nextGroupKey, count) => {
ig.append(getItems(nextGroupKey, count), nextGroupKey)
}
getCSS(`${GLOBAL_CONFIG.source.justifiedGallery.css}`)
getScript(`${GLOBAL_CONFIG.source.justifiedGallery.js}`).then(addJustifiedGallery)
const maxGroupKey = Math.ceil(dataLength / 10)
const completeFn = e => {
const { updated, isResize, mounted } = e
if (!updated.length || !mounted.length || isResize) {
return
}
btf.loadLightbox(item.querySelectorAll('img:not(.medium-zoom-image)'))
if (ig.getGroups().length === maxGroupKey) {
btf.setLoading.remove(item)
ig.off('renderComplete', completeFn)
return
}
if (isButton) {
btf.setLoading.remove(item)
addButton(item)
}
}
const requestAppendFn = btf.debounce(e => {
const nextGroupKey = (+e.groupKey || 0) + 1
appendItem(nextGroupKey, 10)
if (nextGroupKey === maxGroupKey) {
ig.off('requestAppend', requestAppendFn)
}
}, 300)
btf.setLoading.add(item)
ig.on('renderComplete', completeFn)
if (isButton) {
appendItem(1, 10)
} else {
ig.on('requestAppend', requestAppendFn)
ig.renderItems()
}
btf.addGlobalFn('justifiedGallery', () => { ig.destroy() })
}
const addJustifiedGallery = async (ele, tabs = false) => {
const init = async () => {
for (const item of ele) {
if (btf.isHidden(item)) continue
if (tabs && item.classList.contains('loaded')) {
item.querySelector('.gallery-items').innerHTML = ''
const button = item.querySelector(':scope > button')
const loadingContainer = item.querySelector(':scope > .loading-container')
button && button.remove()
loadingContainer && loadingContainer.remove()
}
const isButton = item.getAttribute('data-button') === 'true'
const text = item.firstElementChild.textContent
item.classList.add('loaded')
const content = item.getAttribute('data-type') === 'url' ? await fetchUrl(text) : JSON.parse(text)
runJustifiedGallery(item.lastElementChild, content, isButton, tabs)
}
}
if (typeof InfiniteGrid === 'function') {
init()
} else {
await getScript(`${GLOBAL_CONFIG.infinitegrid.js}`)
init()
}
}
/**
* rightside scroll percent
*/
const rightsideScrollPercent = currentTop => {
const perNum = btf.getScrollPercent(currentTop, document.body)
const $goUp = document.getElementById('go-up')
if (perNum < 95) {
$goUp.classList.add('show-percent')
$goUp.querySelector('.scroll-percent').textContent = perNum
const scrollPercent = btf.getScrollPercent(currentTop, document.body)
const goUpElement = document.getElementById('go-up')
if (scrollPercent < 95) {
goUpElement.classList.add('show-percent')
goUpElement.querySelector('.scroll-percent').textContent = scrollPercent
} else {
$goUp.classList.remove('show-percent')
goUpElement.classList.remove('show-percent')
}
}
/**
* 滾動處理
*/
const scrollFn = function () {
const scrollFn = () => {
const $rightside = document.getElementById('rightside')
const innerHeight = window.innerHeight + 56
let initTop = 0
let isChatShow = true
const $header = document.getElementById('page-header')
const isChatBtn = typeof chatBtn !== 'undefined'
const isShowPercent = GLOBAL_CONFIG.percent.rightside
// 當滾動條小于 56 的時候
if (document.body.scrollHeight <= innerHeight) {
$rightside.style.cssText = 'opacity: 1; transform: translateX(-58px)'
$rightside.classList.add('rightside-show')
return
}
@@ -317,50 +378,51 @@ document.addEventListener('DOMContentLoaded', function () {
return result
}
let flag = ''
const scrollTask = btf.throttle(() => {
const currentTop = window.scrollY || document.documentElement.scrollTop
const isDown = scrollDirection(currentTop)
if (currentTop > 56) {
if (flag === '') {
$header.classList.add('nav-fixed')
$rightside.classList.add('rightside-show')
}
if (isDown) {
if ($header.classList.contains('nav-visible')) $header.classList.remove('nav-visible')
if (isChatBtn && isChatShow === true) {
window.chatBtn.hide()
isChatShow = false
if (flag !== 'down') {
$header.classList.remove('nav-visible')
isChatBtn && window.chatBtn.hide()
flag = 'down'
}
} else {
if (!$header.classList.contains('nav-visible')) $header.classList.add('nav-visible')
if (isChatBtn && isChatShow === false) {
window.chatBtn.show()
isChatShow = true
if (flag !== 'up') {
$header.classList.add('nav-visible')
isChatBtn && window.chatBtn.show()
flag = 'up'
}
}
$header.classList.add('nav-fixed')
if (window.getComputedStyle($rightside).getPropertyValue('opacity') === '0') {
$rightside.style.cssText = 'opacity: 0.8; transform: translateX(-58px)'
}
} else {
flag = ''
if (currentTop === 0) {
$header.classList.remove('nav-fixed', 'nav-visible')
}
$rightside.style.cssText = "opacity: ''; transform: ''"
$rightside.classList.remove('rightside-show')
}
isShowPercent && rightsideScrollPercent(currentTop)
if (document.body.scrollHeight <= innerHeight) {
$rightside.style.cssText = 'opacity: 0.8; transform: translateX(-58px)'
$rightside.classList.add('rightside-show')
}
}, 200)
}, 300)
window.scrollCollect = scrollTask
window.addEventListener('scroll', scrollCollect)
btf.addEventListenerPjax(window, 'scroll', scrollTask, { passive: true })
}
/**
* toc,anchor
*/
const scrollFnToDo = function () {
const scrollFnToDo = () => {
const isToc = GLOBAL_CONFIG_SITE.isToc
const isAnchor = GLOBAL_CONFIG.isAnchor
const $article = document.getElementById('article-container')
@@ -371,37 +433,24 @@ document.addEventListener('DOMContentLoaded', function () {
if (isToc) {
const $cardTocLayout = document.getElementById('card-toc')
$cardToc = $cardTocLayout.getElementsByClassName('toc-content')[0]
$cardToc = $cardTocLayout.querySelector('.toc-content')
$tocLink = $cardToc.querySelectorAll('.toc-link')
$tocPercentage = $cardTocLayout.querySelector('.toc-percentage')
isExpand = $cardToc.classList.contains('is-expand')
window.mobileToc = {
open: () => {
$cardTocLayout.style.cssText = 'animation: toc-open .3s; opacity: 1; right: 55px'
},
// toc元素點擊
const tocItemClickFn = e => {
const target = e.target.closest('.toc-link')
if (!target) return
close: () => {
$cardTocLayout.style.animation = 'toc-close .2s'
setTimeout(() => {
$cardTocLayout.style.cssText = "opacity:''; animation: ''; right: ''"
}, 100)
e.preventDefault()
btf.scrollToDest(btf.getEleTop(document.getElementById(decodeURI(target.getAttribute('href')).replace('#', ''))), 300)
if (window.innerWidth < 900) {
$cardTocLayout.classList.remove('open')
}
}
// toc元素點擊
$cardToc.addEventListener('click', e => {
e.preventDefault()
const target = e.target.classList
if (target.contains('toc-content')) return
const $target = target.contains('toc-link')
? e.target
: e.target.parentElement
btf.scrollToDest(btf.getEleTop(document.getElementById(decodeURI($target.getAttribute('href')).replace('#', ''))), 300)
if (window.innerWidth < 900) {
window.mobileToc.close()
}
})
btf.addEventListenerPjax($cardToc, 'click', tocItemClickFn)
autoScrollToc = item => {
const activePosition = item.getBoundingClientRect().top
@@ -416,9 +465,9 @@ document.addEventListener('DOMContentLoaded', function () {
}
// find head position & add active class
const list = $article.querySelectorAll('h1,h2,h3,h4,h5,h6')
const $articleList = $article.querySelectorAll('h1,h2,h3,h4,h5,h6')
let detectItem = ''
const findHeadPosition = function (top) {
const findHeadPosition = top => {
if (top === 0) {
return false
}
@@ -426,7 +475,7 @@ document.addEventListener('DOMContentLoaded', function () {
let currentId = ''
let currentIndex = ''
list.forEach(function (ele, index) {
$articleList.forEach((ele, index) => {
if (top > btf.getEleTop(ele) - 80) {
const id = ele.id
currentId = id ? '#' + encodeURI(id) : ''
@@ -464,7 +513,7 @@ document.addEventListener('DOMContentLoaded', function () {
}
// main of scroll
window.tocScrollFn = btf.throttle(() => {
const tocScrollFn = btf.throttle(() => {
const currentTop = window.scrollY || document.documentElement.scrollTop
if (isToc && GLOBAL_CONFIG.percent.toc) {
$tocPercentage.textContent = btf.getScrollPercent(currentTop, $article)
@@ -472,21 +521,22 @@ document.addEventListener('DOMContentLoaded', function () {
findHeadPosition(currentTop)
}, 100)
window.addEventListener('scroll', tocScrollFn)
btf.addEventListenerPjax(window, 'scroll', tocScrollFn, { passive: true })
}
const modeChangeFn = mode => {
if (!window.themeChange) {
const handleThemeChange = mode => {
const globalFn = window.globalFn || {}
const themeChange = globalFn.themeChange || {}
if (!themeChange) {
return
}
const turnMode = item => window.themeChange[item](mode)
Object.keys(window.themeChange).forEach(item => {
if (['disqus', 'disqusjs'].includes(item)) {
setTimeout(() => turnMode(item), 300)
Object.keys(themeChange).forEach(key => {
const themeChangeFn = themeChange[key]
if (['disqus', 'disqusjs'].includes(key)) {
setTimeout(() => themeChangeFn(mode), 300)
} else {
turnMode(item)
themeChangeFn(mode)
}
})
}
@@ -495,7 +545,7 @@ document.addEventListener('DOMContentLoaded', function () {
* Rightside
*/
const rightSideFn = {
switchReadMode: () => { // read-mode
readmode: () => { // read mode
const $body = document.body
$body.classList.add('read-mode')
const newEle = document.createElement('button')
@@ -511,74 +561,53 @@ document.addEventListener('DOMContentLoaded', function () {
newEle.addEventListener('click', clickFn)
},
switchDarkMode: () => { // Switch Between Light And Dark Mode
darkmode: () => { // switch between light and dark mode
const willChangeMode = document.documentElement.getAttribute('data-theme') === 'dark' ? 'light' : 'dark'
if (willChangeMode === 'dark') {
activateDarkMode()
saveToLocal.set('theme', 'dark', 2)
GLOBAL_CONFIG.Snackbar !== undefined && btf.snackbarShow(GLOBAL_CONFIG.Snackbar.day_to_night)
} else {
activateLightMode()
saveToLocal.set('theme', 'light', 2)
GLOBAL_CONFIG.Snackbar !== undefined && btf.snackbarShow(GLOBAL_CONFIG.Snackbar.night_to_day)
}
modeChangeFn(willChangeMode)
saveToLocal.set('theme', willChangeMode, 2)
handleThemeChange(willChangeMode)
},
showOrHideBtn: (e) => { // rightside 點擊設置 按鈕 展開
const rightsideHideClassList = document.getElementById('rightside-config-hide').classList
rightsideHideClassList.toggle('show')
if (e.classList.contains('show')) {
rightsideHideClassList.add('status')
'rightside-config': item => { // Show or hide rightside-hide-btn
const hideLayout = item.firstElementChild
if (hideLayout.classList.contains('show')) {
hideLayout.classList.add('status')
setTimeout(() => {
rightsideHideClassList.remove('status')
hideLayout.classList.remove('status')
}, 300)
}
e.classList.toggle('show')
hideLayout.classList.toggle('show')
},
scrollToTop: () => { // Back to top
'go-up': () => { // Back to top
btf.scrollToDest(0, 500)
},
hideAsideBtn: () => { // Hide aside
'hide-aside-btn': () => { // Hide aside
const $htmlDom = document.documentElement.classList
const saveStatus = $htmlDom.contains('hide-aside') ? 'show' : 'hide'
saveToLocal.set('aside-status', saveStatus, 2)
$htmlDom.toggle('hide-aside')
},
runMobileToc: () => {
if (window.getComputedStyle(document.getElementById('card-toc')).getPropertyValue('opacity') === '0') window.mobileToc.open()
else window.mobileToc.close()
'mobile-toc-button': () => { // Show mobile toc
document.getElementById('card-toc').classList.toggle('open')
},
toggleChatDisplay: () => {
'chat-btn': () => { // Show chat
window.chatBtnFn()
},
translateLink: () => { // switch between traditional and simplified chinese
window.translateFn.translatePage()
}
}
document.getElementById('rightside').addEventListener('click', function (e) {
const $target = e.target.id ? e.target : e.target.parentNode
switch ($target.id) {
case 'go-up':
rightSideFn.scrollToTop()
break
case 'rightside_config':
rightSideFn.showOrHideBtn($target)
break
case 'mobile-toc-button':
rightSideFn.runMobileToc()
break
case 'readmode':
rightSideFn.switchReadMode()
break
case 'darkmode':
rightSideFn.switchDarkMode()
break
case 'hide-aside-btn':
rightSideFn.hideAsideBtn()
break
case 'chat-btn':
rightSideFn.toggleChatDisplay()
break
default:
break
const $target = e.target.closest('[id]')
if ($target && rightSideFn[$target.id]) {
rightSideFn[$target.id](this)
}
})
@@ -587,24 +616,35 @@ document.addEventListener('DOMContentLoaded', function () {
* 側邊欄sub-menu 展開/收縮
*/
const clickFnOfSubMenu = () => {
document.querySelectorAll('#sidebar-menus .site-page.group').forEach(function (item) {
item.addEventListener('click', function () {
this.classList.toggle('hide')
})
})
const handleClickOfSubMenu = e => {
const target = e.target.closest('.site-page.group')
if (!target) return
target.classList.toggle('hide')
}
document.querySelector('#sidebar-menus .menus_items').addEventListener('click', handleClickOfSubMenu)
}
/**
* 手机端目录点击
*/
const openMobileMenu = () => {
const handleClick = () => { sidebarFn.open() }
btf.addEventListenerPjax(document.getElementById('toggle-menu'), 'click', handleClick)
}
/**
* 複製時加上版權信息
*/
const addCopyright = () => {
const copyright = GLOBAL_CONFIG.copyright
document.body.oncopy = (e) => {
const { limitCount, languages } = GLOBAL_CONFIG.copyright
const handleCopy = (e) => {
e.preventDefault()
const copyFont = window.getSelection(0).toString()
let textFont = copyFont
if (copyFont.length > copyright.limitCount) {
textFont = `${copyFont}\n\n\n${copyright.languages.author}\n${copyright.languages.link}${window.location.href}\n${copyright.languages.source}\n${copyright.languages.info}`
if (copyFont.length > limitCount) {
textFont = `${copyFont}\n\n\n${languages.author}\n${languages.link}${window.location.href}\n${languages.source}\n${languages.info}`
}
if (e.clipboardData) {
return e.clipboardData.setData('text', textFont)
@@ -612,6 +652,8 @@ document.addEventListener('DOMContentLoaded', function () {
return window.clipboardData.setData('text', textFont)
}
}
document.body.addEventListener('copy', handleCopy)
}
/**
@@ -640,114 +682,119 @@ document.addEventListener('DOMContentLoaded', function () {
* table overflow
*/
const addTableWrap = () => {
const $table = document.querySelectorAll('#article-container :not(.highlight) > table, #article-container > table')
if ($table.length) {
$table.forEach(item => {
const $table = document.querySelectorAll('#article-container table')
if (!$table.length) return
$table.forEach(item => {
if (!item.closest('.highlight')) {
btf.wrap(item, 'div', { class: 'table-wrap' })
})
}
}
})
}
/**
* tag-hide
*/
const clickFnOfTagHide = function () {
const $hideInline = document.querySelectorAll('#article-container .hide-button')
if ($hideInline.length) {
$hideInline.forEach(function (item) {
item.addEventListener('click', function (e) {
const $this = this
$this.classList.add('open')
const $fjGallery = $this.nextElementSibling.querySelectorAll('.fj-gallery')
$fjGallery.length && btf.initJustifiedGallery($fjGallery)
})
})
const clickFnOfTagHide = () => {
const hideButtons = document.querySelectorAll('#article-container .hide-button')
if (!hideButtons.length) return
const handleClick = function (e) {
const $this = this
$this.classList.add('open')
const $fjGallery = $this.nextElementSibling.querySelectorAll('.gallery-container')
$fjGallery.length && addJustifiedGallery($fjGallery)
}
}
const tabsFn = {
clickFnOfTabs: function () {
document.querySelectorAll('#article-container .tab > button').forEach(function (item) {
item.addEventListener('click', function (e) {
const $this = this
const $tabItem = $this.parentNode
if (!$tabItem.classList.contains('active')) {
const $tabContent = $tabItem.parentNode.nextElementSibling
const $siblings = btf.siblings($tabItem, '.active')[0]
$siblings && $siblings.classList.remove('active')
$tabItem.classList.add('active')
const tabId = $this.getAttribute('data-href').replace('#', '')
const childList = [...$tabContent.children]
childList.forEach(item => {
if (item.id === tabId) item.classList.add('active')
else item.classList.remove('active')
})
const $isTabJustifiedGallery = $tabContent.querySelectorAll(`#${tabId} .fj-gallery`)
if ($isTabJustifiedGallery.length > 0) {
btf.initJustifiedGallery($isTabJustifiedGallery)
}
}
})
})
},
backToTop: () => {
document.querySelectorAll('#article-container .tabs .tab-to-top').forEach(function (item) {
item.addEventListener('click', function () {
btf.scrollToDest(btf.getEleTop(btf.getParents(this, '.tabs')), 300)
})
})
}
}
const toggleCardCategory = function () {
const $cardCategory = document.querySelectorAll('#aside-cat-list .card-category-list-item.parent i')
if ($cardCategory.length) {
$cardCategory.forEach(function (item) {
item.addEventListener('click', function (e) {
e.preventDefault()
const $this = this
$this.classList.toggle('expand')
const $parentEle = $this.parentNode.nextElementSibling
if (btf.isHidden($parentEle)) {
$parentEle.style.display = 'block'
} else {
$parentEle.style.display = 'none'
}
})
})
}
}
const switchComments = function () {
let switchDone = false
const $switchBtn = document.querySelector('#comment-switch > .switch-btn')
$switchBtn && $switchBtn.addEventListener('click', function () {
this.classList.toggle('move')
document.querySelectorAll('#post-comment > .comment-wrap > div').forEach(function (item) {
if (btf.isHidden(item)) {
item.style.cssText = 'display: block;animation: tabshow .5s'
} else {
item.style.cssText = "display: none;animation: ''"
}
})
if (!switchDone && typeof loadOtherComment === 'function') {
switchDone = true
loadOtherComment()
}
hideButtons.forEach(item => {
item.addEventListener('click', handleClick, { once: true })
})
}
const addPostOutdateNotice = function () {
const data = GLOBAL_CONFIG.noticeOutdate
const tabsFn = () => {
const navTabsElement = document.querySelectorAll('#article-container .tabs')
if (!navTabsElement.length) return
const removeAndAddActiveClass = (elements, detect) => {
Array.from(elements).forEach(element => {
element.classList.remove('active')
if (element === detect || element.id === detect) {
element.classList.add('active')
}
})
}
const addTabNavEventListener = (item, isJustifiedGallery) => {
const navClickHandler = function (e) {
const target = e.target.closest('button')
if (target.classList.contains('active')) return
removeAndAddActiveClass(this.children, target)
this.classList.remove('no-default')
const tabId = target.getAttribute('data-href')
const tabContent = this.nextElementSibling
removeAndAddActiveClass(tabContent.children, tabId)
if (isJustifiedGallery) {
btf.removeGlobalFnEvent('igOfTabs', this)
const justifiedGalleryItems = tabContent.querySelectorAll(`:scope > #${tabId} .gallery-container`)
justifiedGalleryItems.length && addJustifiedGallery(justifiedGalleryItems, this)
}
}
btf.addEventListenerPjax(item.firstElementChild, 'click', navClickHandler)
}
const addTabToTopEventListener = item => {
const btnClickHandler = (e) => {
const target = e.target.closest('button')
if (!target) return
btf.scrollToDest(btf.getEleTop(item), 300)
}
btf.addEventListenerPjax(item.lastElementChild, 'click', btnClickHandler)
}
navTabsElement.forEach(item => {
const isJustifiedGallery = !!item.querySelectorAll('.gallery-container')
addTabNavEventListener(item, isJustifiedGallery)
addTabToTopEventListener(item)
})
}
const toggleCardCategory = () => {
const cardCategory = document.querySelector('#aside-cat-list.expandBtn')
if (!cardCategory) return
const handleToggleBtn = (e) => {
const target = e.target
if (target.nodeName === 'I') {
e.preventDefault()
target.parentNode.classList.toggle('expand')
}
}
btf.addEventListenerPjax(cardCategory, 'click', handleToggleBtn, true)
}
const switchComments = () => {
const switchBtn = document.getElementById('switch-btn')
if (!switchBtn) return
let switchDone = false
const commentContainer = document.getElementById('post-comment')
const handleSwitchBtn = () => {
commentContainer.classList.toggle('move')
if (!switchDone) {
switchDone = true
loadOtherComment()
}
}
btf.addEventListenerPjax(switchBtn, 'click', handleSwitchBtn)
}
const addPostOutdateNotice = () => {
const { limitDay, messagePrev, messageNext, position } = GLOBAL_CONFIG.noticeOutdate
const diffDay = btf.diffDate(GLOBAL_CONFIG_SITE.postUpdate)
if (diffDay >= data.limitDay) {
if (diffDay >= limitDay) {
const ele = document.createElement('div')
ele.className = 'post-outdate-notice'
ele.textContent = data.messagePrev + ' ' + diffDay + ' ' + data.messageNext
ele.textContent = `${messagePrev} ${diffDay} ${messageNext}`
const $targetEle = document.getElementById('article-container')
if (data.position === 'top') {
if (position === 'top') {
$targetEle.insertBefore(ele, $targetEle.firstChild)
} else {
$targetEle.appendChild(ele)
@@ -774,7 +821,7 @@ document.addEventListener('DOMContentLoaded', function () {
const unRefreshFn = function () {
window.addEventListener('resize', () => {
adjustMenu(false)
btf.isHidden(document.getElementById('toggle-menu')) && mobileSidebarOpen && sidebarFn.close()
mobileSidebarOpen && btf.isHidden(document.getElementById('toggle-menu')) && sidebarFn.close()
})
document.getElementById('menu-mask').addEventListener('click', e => { sidebarFn.close() })
@@ -786,7 +833,7 @@ document.addEventListener('DOMContentLoaded', function () {
if (GLOBAL_CONFIG.autoDarkmode) {
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
if (saveToLocal.get('theme') !== undefined) return
e.matches ? modeChangeFn('dark') : modeChangeFn('light')
e.matches ? handleThemeChange('dark') : handleThemeChange('light')
})
}
}
@@ -810,16 +857,16 @@ document.addEventListener('DOMContentLoaded', function () {
GLOBAL_CONFIG.isPhotoFigcaption && addPhotoFigcaption()
scrollFn()
const $jgEle = document.querySelectorAll('#article-container .fj-gallery')
$jgEle.length && runJustifiedGallery($jgEle)
btf.removeGlobalFnEvent('justifiedGallery')
const galleryContainer = document.querySelectorAll('#article-container .gallery-container')
galleryContainer.length && addJustifiedGallery(galleryContainer)
runLightbox()
addTableWrap()
clickFnOfTagHide()
tabsFn.clickFnOfTabs()
tabsFn.backToTop()
tabsFn()
switchComments()
document.getElementById('toggle-menu').addEventListener('click', () => { sidebarFn.open() })
openMobileMenu()
}
refreshFn()

View File

@@ -39,7 +39,7 @@ window.addEventListener('load', () => {
}
const searchClickFn = () => {
document.querySelector('#search-button > .search').addEventListener('click', openSearch)
btf.addEventListenerPjax(document.querySelector('#search-button > .search'), 'click', openSearch)
}
const searchFnOnce = () => {

View File

@@ -330,7 +330,7 @@ window.addEventListener('load', () => {
}
const searchClickFn = () => {
document.querySelector('#search-button > .search').addEventListener('click', openSearch)
btf.addEventListenerPjax(document.querySelector('#search-button > .search'), 'click', openSearch)
}
const searchFnOnce = () => {

View File

@@ -21,6 +21,7 @@ document.addEventListener('DOMContentLoaded', function () {
return Traditionalized(txt)
} else return txt
}
function translateBody (fobj) {
let objs
if (typeof fobj === 'object') objs = fobj.childNodes
@@ -107,9 +108,15 @@ document.addEventListener('DOMContentLoaded', function () {
setLang()
setTimeout(translateBody, translateDelay)
}
translateButtonObject.addEventListener('click', translatePage, false)
}
}
window.translateFn = {
translatePage,
Traditionalized,
Simplized
}
translateInitialization()
document.addEventListener('pjax:complete', translateInitialization)
})

View File

@@ -1,38 +1,35 @@
const btf = {
debounce: function (func, wait, immediate) {
debounce: (func, wait = 0, immediate = false) => {
let timeout
return function () {
const context = this
const args = arguments
const later = function () {
return (...args) => {
const later = () => {
timeout = null
if (!immediate) func.apply(context, args)
if (!immediate) func(...args)
}
const callNow = immediate && !timeout
clearTimeout(timeout)
timeout = setTimeout(later, wait)
if (callNow) func.apply(context, args)
if (callNow) func(...args)
}
},
throttle: function (func, wait, options) {
throttle: function (func, wait, options = {}) {
let timeout, context, args
let previous = 0
if (!options) options = {}
const later = function () {
const later = () => {
previous = options.leading === false ? 0 : new Date().getTime()
timeout = null
func.apply(context, args)
if (!timeout) context = args = null
}
const throttled = function () {
const throttled = (...params) => {
const now = new Date().getTime()
if (!previous && options.leading === false) previous = now
const remaining = wait - (now - previous)
context = this
args = arguments
args = params
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout)
@@ -155,27 +152,6 @@ const btf = {
ele.style.animation = text
},
getParents: (elem, selector) => {
for (; elem && elem !== document; elem = elem.parentNode) {
if (elem.matches(selector)) return elem
}
return null
},
siblings: (ele, selector) => {
return [...ele.parentNode.children].filter((child) => {
if (selector) {
return child !== ele && child.matches(selector)
}
return child !== ele
})
},
/**
* @param {*} selector
* @param {*} eleType the type of create element
* @param {*} options object key: value
*/
wrap: (selector, eleType, options) => {
const createEle = document.createElement(eleType)
for (const [key, value] of Object.entries(options)) {
@@ -185,13 +161,6 @@ const btf = {
createEle.appendChild(selector)
},
unwrap: el => {
const parent = el.parentNode
if (parent && parent !== document.body) {
parent.replaceChild(el, parent)
}
},
isHidden: ele => ele.offsetHeight === 0 && ele.offsetWidth === 0,
getEleTop: ele => {
@@ -214,7 +183,7 @@ const btf = {
}
if (service === 'fancybox') {
ele.forEach(i => {
Array.from(ele).forEach(i => {
if (i.parentNode.tagName !== 'A') {
const dataSrc = i.dataset.lazySrc || i.src
const dataCaption = i.title || i.alt || ''
@@ -257,22 +226,20 @@ const btf = {
}
},
initJustifiedGallery: function (selector) {
const runJustifiedGallery = i => {
if (!btf.isHidden(i)) {
fjGallery(i, {
itemSelector: '.fj-gallery-item',
rowHeight: i.getAttribute('data-rowHeight'),
gutter: 4,
onJustify: function () {
this.$container.style.opacity = '1'
}
})
}
setLoading: {
add: ele => {
const html = `
<div class="loading-container">
<div class="loading-item">
<div></div><div></div><div></div><div></div><div></div>
</div>
</div>
`
ele.insertAdjacentHTML('afterend', html)
},
remove: ele => {
ele.nextElementSibling.remove()
}
if (Array.from(selector).length === 0) runJustifiedGallery(selector)
else selector.forEach(i => { runJustifiedGallery(i) })
},
updateAnchor: (anchor) => {
@@ -297,11 +264,33 @@ const btf = {
return percentage
},
addModeChange: (name, fn) => {
if (window.themeChange && window.themeChange[name]) return
window.themeChange = {
...window.themeChange,
[name]: fn
}
addGlobalFn: (key, fn, name = false, parent = window) => {
const globalFn = parent.globalFn || {}
const keyObj = globalFn[key] || {}
if (name && keyObj[name]) return
name = name || Object.keys(keyObj).length
keyObj[name] = fn
globalFn[key] = keyObj
parent.globalFn = globalFn
},
addEventListenerPjax: (ele, event, fn, option = false) => {
ele.addEventListener(event, fn, option)
btf.addGlobalFn('pjax', () => {
ele.removeEventListener(event, fn, option)
})
},
removeGlobalFnEvent: (key, parent = window) => {
const { globalFn = {} } = parent
const keyObj = globalFn[key] || {}
const keyArr = Object.keys(keyObj)
if (!keyArr.length) return
keyArr.forEach(i => {
keyObj[i]()
})
delete parent.globalFn[key]
}
}