breaking changes: 移除博天api

breaking changes: 移除 waline 的 avatar 和 avatar cdn 配置
feat: anchor 不再限制 post 頁開啟,可以在任何頁面開啟
feat: 文章標題支持點擊跳轉到此標題開始閲讀 closed #653
feat: toc可以設置全部展開 closed #709
feat: 增加 新的評論系統 giscus
feat: 支持新的評論名寫法,主題會處理評論名字大小寫,舊的會兼容
feat: 友情鏈接列表增加 fetch url 獲取
improvement: 鼠標移到最新評論內容,增加 title 顯示
fix: 修復 rightside 遮擋內容,導致內容無法點擊的 bug
fix: 修復 mermaid 在某些頁面(有元素 id 為 mermaid 時) 會無法加載的 bug
fix: 修復 搜索框不會自動 focus 的 bug
This commit is contained in:
Jerry
2021-11-14 17:50:11 +08:00
Unverified
parent f7c50586ce
commit ffeab5e20c
30 changed files with 363 additions and 225 deletions

View File

@@ -14,7 +14,7 @@
![](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/theme-butterfly-readme.png) ![](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/theme-butterfly-readme.png)
Demo: 👍 [Butterfly](https://butterfly.js.org/) || 🤞 [MYW](https://immyw.com/) Demo: 👍 [Butterfly](https://butterfly.js.org/) || 🤞 [CrazyWong](https://crazywong.com/)
Docs: 📖 [Butterfly Docs](https://butterfly.js.org/posts/21cfbf15/) Docs: 📖 [Butterfly Docs](https://butterfly.js.org/posts/21cfbf15/)
@@ -79,7 +79,7 @@ npm i hexo-theme-butterfly
- [x] Related articles - [x] Related articles
- [x] Displays outdated notice for a post - [x] Displays outdated notice for a post
- [x] Share (AddThis/Sharejs/Addtoany) - [x] Share (AddThis/Sharejs/Addtoany)
- [X] Comment (Disqus/Disqusjs/Livere/Gitalk/Valine/Waline/Utterances/Facebook Comments/Twikoo) - [X] Comment (Disqus/Disqusjs/Livere/Gitalk/Valine/Waline/Utterances/Facebook Comments/Twikoo/Giscus)
- [x] Multiple Comment System Support - [x] Multiple Comment System Support
- [x] Online Chats (Chatra/Tidio/Daovoice/Gitter/Crisp) - [x] Online Chats (Chatra/Tidio/Daovoice/Gitter/Crisp)
- [x] Web analytics - [x] Web analytics

View File

@@ -14,7 +14,7 @@
![](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/theme-butterfly-readme.png) ![](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/theme-butterfly-readme.png)
預覽: 👍 [Butterfly](https://butterfly.js.org/) || 🤞 [MYW](https://immyw.com/) 預覽: 👍 [Butterfly](https://butterfly.js.org/) || 🤞 [CrazyWong](https://crazywong.com/)
文檔: 📖 [Butterfly Docs](https://butterfly.js.org/posts/21cfbf15/) 文檔: 📖 [Butterfly Docs](https://butterfly.js.org/posts/21cfbf15/)
@@ -79,7 +79,7 @@ theme: butterfly
- [x] 顯示相關文章 - [x] 顯示相關文章
- [x] 過期文章提醒 - [x] 過期文章提醒
- [x] 多種分享系統AddThis/Sharejs/Addtoany - [x] 多種分享系統AddThis/Sharejs/Addtoany
- [X] 多種評論系統Disqus/Disqusjs/Livere/Gitalk/Valine/Waline/Utterances/Facebook Comments/Twikoo - [X] 多種評論系統Disqus/Disqusjs/Livere/Gitalk/Valine/Waline/Utterances/Facebook Comments/Twikoo/Giscus
- [x] 支持雙評論部署 - [x] 支持雙評論部署
- [x] 多種在線聊天Chatra/Tidio/Daovoice/Gitter/Crisp - [x] 多種在線聊天Chatra/Tidio/Daovoice/Gitter/Crisp
- [x] 多種分析系統 - [x] 多種分析系統

View File

@@ -171,6 +171,7 @@ toc:
post: true post: true
page: false page: false
number: true number: true
expand: false
style_simple: false # for post style_simple: false # for post
post_copyright: post_copyright:
@@ -253,10 +254,7 @@ addtoany:
comments: comments:
# Up to two comments system, the first will be shown as default # Up to two comments system, the first will be shown as default
# Choose: Disqus/Disqusjs/Livere/Gitalk/Valine/Waline/Utterances/Facebook Comments/Twikoo use: # Valine,Disqus
use:
# - Valine
# - Disqus
text: true # Display the comment name next to the button text: true # Display the comment name next to the button
# lazyload: The comment system will be load when comment element enters the browser's viewport. # lazyload: The comment system will be load when comment element enters the browser's viewport.
# If you set it to true, the comment count will be invalid # If you set it to true, the comment count will be invalid
@@ -308,8 +306,6 @@ valine:
# https://waline.js.org/ # https://waline.js.org/
waline: waline:
serverURL: # Waline server address url serverURL: # Waline server address url
avatar: monsterid # gravatar style https://zh-tw.gravatar.com/site/implement/images/#default-image
avatarCDN: # Gravatar CDN baseURL
bg: # waline background bg: # waline background
visitor: false visitor: false
option: option:
@@ -341,6 +337,17 @@ twikoo:
visitor: false visitor: false
option: option:
# Giscus
# https://giscus.app/
giscus:
repo:
repo_id:
category_id:
theme:
light: light
dark: dark
option:
# Chat Services # Chat Services
# -------------------------------------- # --------------------------------------
@@ -583,12 +590,11 @@ subtitle:
effect: true effect: true
# loop (循環打字) # loop (循環打字)
loop: true loop: true
# source調用第三方服務 # source 調用第三方服務
# source: false 關閉調用 # source: false 關閉調用
# source: 1 調用搏天 api 的隨機語錄(簡體) # source: 1 調用一言網的一句話(簡體) https://hitokoto.cn/
# source: 2 調用一言網的一句話(簡體) # source: 2 調用一句網(簡體) http://yijuzhan.com/
# source: 3 調用一句網(簡體) # source: 3 調用今日詩詞(簡體) https://www.jinrishici.com/
# source: 4 調用今日詩詞(簡體)
# subtitle 會先顯示 source , 再顯示 sub 的內容 # subtitle 會先顯示 source , 再顯示 sub 的內容
source: false source: false
# 如果關閉打字效果subtitle 只會顯示 sub 的第一行文字 # 如果關閉打字效果subtitle 只會顯示 sub 的第一行文字
@@ -836,6 +842,7 @@ CDN:
utterances: utterances:
twikoo: twikoo:
waline: waline:
giscus:
# share # share
addtoany: addtoany:

View File

@@ -119,5 +119,5 @@ script.
}, },
isPhotoFigcaption: !{theme.photofigcaption}, isPhotoFigcaption: !{theme.photofigcaption},
islazyload: !{theme.lazyload.enable}, islazyload: !{theme.lazyload.enable},
isanchor: !{theme.anchor} isAnchor: !{theme.anchor}
} }

View File

@@ -1,18 +1,57 @@
#article-container #article-container
.flink .flink
if site.data.link if page.flink_url
each i in site.data.link script.
if i.class_name (()=>{
h2!= i.class_name const replaceSymbol = (str) => {
if i.class_desc return str.replace(/[\p{P}\p{S}]/gu, '-')
.flink-desc!=i.class_desc }
.flink-list
each item in i.link_list let result = ''
.flink-list-item fetch('!{url_for(page.flink_url)}')
a(href=url_for(item.link) title=item.name target="_blank") .then(response => response.json())
.flink-item-icon .then(str => {
img.no-lightbox(src=url_for(item.avatar) onerror=`this.onerror=null;this.src='` + url_for(theme.error_img.flink) + `'` alt=item.name ) for(let i = 0; i < str.length; i++){
.flink-item-name= item.name const replaceClassName = replaceSymbol(str[i].class_name)
.flink-item-desc(title=item.descr)= item.descr const className = str[i].class_name ? `<h2 id='${replaceClassName}'><a href="#${replaceClassName}" class="headerlink" title="${str[i].class_name}"></a>${str[i].class_name}</h2>` : ''
const classDesc = str[i].class_desc ? `<div class='flink-desc'>${str[i].class_desc}</div>` : ''
let listResult = ''
const lists = str[i].link_list
for(let j = 0; j < lists.length; j++){
listResult += `
<div class="flink-list-item">
<a href='${lists[j].link}' title='${lists[j].name}' target='_blank'>
<div class='flink-item-icon'>
<img class='no-lightbox' src='${lists[j].avatar}' onerror='this.onerror=null;this.src="!{url_for(theme.error_img.flink)}"' alt='${lists[j].name}' />
</div>
<div class='flink-item-name'>${lists[j].name}</div>
<div class='flink-item-desc' title='${lists[j].descr}'>${lists[j].descr}</div>
</a>
</div>`
}
result += `${className}${classDesc} <div class='flink-list'>${listResult}</div>`
}
document.querySelector('.flink').insertAdjacentHTML('afterbegin', result)
})
})()
else
if site.data.link
each i in site.data.link
if i.class_name
!=markdown(`## ${i.class_name}`)
if i.class_desc
.flink-desc!=i.class_desc
.flink-list
each item in i.link_list
.flink-list-item
a(href=url_for(item.link) title=item.name target="_blank")
.flink-item-icon
img.no-lightbox(src=url_for(item.avatar) onerror=`this.onerror=null;this.src='` + url_for(theme.error_img.flink) + `'` alt=item.name )
.flink-item-name= item.name
.flink-item-desc(title=item.descr)= item.descr
!= page.content != page.content

View File

@@ -0,0 +1,49 @@
- const { repo, repo_id, category_id, option } = theme.giscus
- const themes = theme.giscus.theme
script.
function loadGiscus () {
let nowTheme = document.documentElement.getAttribute('data-theme') === 'dark' ? '!{themes.dark}' : '!{themes.light}'
const config = Object.assign({
src: '!{theme.CDN.giscus}',
'data-repo': '!{repo}',
'data-repo-id': '!{repo_id}',
'data-category-id': '!{category_id}',
'data-mapping': 'pathname',
'data-theme': nowTheme,
'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 () {
const theme = document.documentElement.getAttribute('data-theme') === 'dark' ? '!{themes.dark}' : '!{themes.light}'
function sendMessage(message) {
const iframe = document.querySelector('iframe.giscus-frame');
if (!iframe) return;
iframe.contentWindow.postMessage({ giscus: message }, 'https://giscus.app');
}
sendMessage({
setConfig: {
theme: theme
}
});
}
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()
}
}

View File

@@ -33,6 +33,8 @@ hr
#twikoo-wrap #twikoo-wrap
when 'Waline' when 'Waline'
#waline-wrap #waline-wrap
when 'Giscus'
#giscus-wrap
when 'Facebook Comments' when 'Facebook Comments'
.fb-comments(data-colorscheme = theme.display_mode === 'dark' ? 'dark' : 'light' .fb-comments(data-colorscheme = theme.display_mode === 'dark' ? 'dark' : 'light'
data-numposts= theme.facebook_comments.pageSize || 10 data-numposts= theme.facebook_comments.pageSize || 10

View File

@@ -16,5 +16,7 @@ each name in theme.comments.use
!=partial('includes/third-party/comments/twikoo', {}, {cache: true}) !=partial('includes/third-party/comments/twikoo', {}, {cache: true})
when 'Waline' when 'Waline'
!=partial('includes/third-party/comments/waline', {}, {cache: true}) !=partial('includes/third-party/comments/waline', {}, {cache: true})
when 'Giscus'
!=partial('includes/third-party/comments/giscus', {}, {cache: true})
when 'Facebook Comments' when 'Facebook Comments'
!=partial('includes/third-party/comments/facebook_comments', {}, {cache: true}) !=partial('includes/third-party/comments/facebook_comments', {}, {cache: true})

View File

@@ -1,4 +1,4 @@
- const { serverURL, avatar, avatarCDN, visitor, option } = theme.waline - const { serverURL, visitor, option } = theme.waline
script. script.
function loadWaline () { function loadWaline () {
@@ -6,8 +6,6 @@ script.
const waline = new Waline(Object.assign({ const waline = new Waline(Object.assign({
el: '#waline-wrap', el: '#waline-wrap',
serverURL: '!{serverURL}', serverURL: '!{serverURL}',
avatar: '#{avatar}',
avatarCDN: '!{avatarCDN || "https://sdn.geekzu.org/avatar/"}',
path: location.pathname, path: location.pathname,
visitor: !{visitor}, visitor: !{visitor},
dark: 'html[data-theme="dark"]' dark: 'html[data-theme="dark"]'

View File

@@ -3,6 +3,7 @@ script.
const $mermaidWrap = document.querySelectorAll('#article-container .mermaid-wrap') const $mermaidWrap = document.querySelectorAll('#article-container .mermaid-wrap')
if ($mermaidWrap.length) { if ($mermaidWrap.length) {
window.runMermaid = () => { window.runMermaid = () => {
window.loadMermaid = true
const theme = document.documentElement.getAttribute('data-theme') === 'dark' ? '!{theme.mermaid.theme.dark}' : '!{theme.mermaid.theme.light}' const theme = document.documentElement.getAttribute('data-theme') === 'dark' ? '!{theme.mermaid.theme.dark}' : '!{theme.mermaid.theme.light}'
Array.from($mermaidWrap).forEach((item, index) => { Array.from($mermaidWrap).forEach((item, index) => {
@@ -17,7 +18,7 @@ script.
} }
const loadMermaid = () => { const loadMermaid = () => {
window.mermaid ? runMermaid() : getScript('!{theme.CDN.mermaid}').then(runMermaid) window.loadMermaid ? runMermaid() : getScript('!{theme.CDN.mermaid}').then(runMermaid)
} }
window.pjax ? loadMermaid() : document.addEventListener('DOMContentLoaded', loadMermaid) window.pjax ? loadMermaid() : document.addEventListener('DOMContentLoaded', loadMermaid)

View File

@@ -49,7 +49,7 @@ script.
} }
result += `<div class='content'> result += `<div class='content'>
<a class='comment' href='${array[i].url}'>${array[i].content}</a> <a class='comment' href='${array[i].url}' title='${array[i].content}'>${array[i].content}</a>
<div class='name'><span>${array[i].nick}</span><time> / ${btf.diffDate(array[i].date, true)}</time></div> <div class='name'><span>${array[i].nick}</span><time> / ${btf.diffDate(array[i].date, true)}</time></div>
</div></div>` </div></div>`
} }

View File

@@ -75,7 +75,7 @@ script.
} }
result += `<div class='content'> result += `<div class='content'>
<a class='comment' href='${array[i].url}'>${array[i].content}</a> <a class='comment' href='${array[i].url}' title='${array[i].content}'>${array[i].content}</a>
<div class='name'><span>${array[i].nick} / </span><time datetime="${array[i].date}">${btf.diffDate(array[i].date, true)}</time></div> <div class='name'><span>${array[i].nick} / </span><time datetime="${array[i].date}">${btf.diffDate(array[i].date, true)}</time></div>
</div></div>` </div></div>`
} }

View File

@@ -60,7 +60,7 @@ script.
} }
result += `<div class='content'> result += `<div class='content'>
<a class='comment' href='${array[i].url}'>${array[i].content}</a> <a class='comment' href='${array[i].url}' title='${array[i].content}'>${array[i].content}</a>
<div class='name'><span>${array[i].nick} / </span><time datetime="${array[i].date}">${btf.diffDate(array[i].date, true)}</time></div> <div class='name'><span>${array[i].nick} / </span><time datetime="${array[i].date}">${btf.diffDate(array[i].date, true)}</time></div>
</div></div>` </div></div>`
} }

View File

@@ -37,7 +37,7 @@ script.
} }
result += `<div class='content'> result += `<div class='content'>
<a class='comment' href='${array[i].url}'>${array[i].content}</a> <a class='comment' href='${array[i].url}' title='${array[i].content}'>${array[i].content}</a>
<div class='name'><span>${array[i].nick} / </span><time datetime="${array[i].date}">${btf.diffDate(array[i].date, true)}</time></div> <div class='name'><span>${array[i].nick} / </span><time datetime="${array[i].date}">${btf.diffDate(array[i].date, true)}</time></div>
</div></div>` </div></div>`
} }

View File

@@ -16,13 +16,6 @@ script.
return content return content
} }
const getIcon = (ava,mail) => {
if (ava) return ava
let defaultIcon = '!{ avatar ? `?d=${avatar}` : ''}'
let iconUrl = "!{avatarCDN ? avatarCDN : 'https://gravatar.loli.net/avatar/'}" + mail + defaultIcon
return iconUrl
}
const generateHtml = array => { const generateHtml = array => {
let result = '' let result = ''
@@ -36,7 +29,7 @@ script.
} }
result += `<div class='content'> result += `<div class='content'>
<a class='comment' href='${array[i].url}'>${array[i].content}</a> <a class='comment' href='${array[i].url}' title='${array[i].content}'>${array[i].content}</a>
<div class='name'><span>${array[i].nick} / </span><time datetime="${array[i].date}">${btf.diffDate(array[i].date, true)}</time></div> <div class='name'><span>${array[i].nick} / </span><time datetime="${array[i].date}">${btf.diffDate(array[i].date, true)}</time></div>
</div></div>` </div></div>`
} }
@@ -59,7 +52,7 @@ script.
const walineArray = res.comments.map(e => { const walineArray = res.comments.map(e => {
return { return {
'content': changeContent(e.comment), 'content': changeContent(e.comment),
'avatar': getIcon(e.QQAvatar,e.mail), 'avatar': e.avatar,
'nick': e.nick, 'nick': e.nick,
'url': e.url + '#' + e.objectId, 'url': e.url + '#' + e.objectId,
'date': e.updatedAt, 'date': e.updatedAt,

View File

@@ -3,38 +3,6 @@
case source case source
when 1 when 1
script.
function subtitleType () {
fetch('https://api.btstu.cn/yan/api.php?charset=utf-8&encode=json')
.then(response => response.json())
.then(data => {
if (!{effect}) {
const sub = !{JSON.stringify(subContent)}
sub.unshift(data.text)
window.typed = new Typed('#subtitle', {
strings: sub,
startDelay: 300,
typeSpeed: 150,
loop: !{loop},
backSpeed: 50,
})
} else {
document.getElementById('subtitle').innerHTML = data.text
}
})
}
if (!{effect}) {
if (typeof Typed === 'function') {
subtitleType()
} else {
getScript('!{url_for(theme.CDN.typed)}').then(subtitleType)
}
} else {
subtitleType()
}
when 2
script. script.
function subtitleType () { function subtitleType () {
fetch('https://v1.hitokoto.cn') fetch('https://v1.hitokoto.cn')
@@ -67,7 +35,7 @@ case source
subtitleType() subtitleType()
} }
when 3 when 2
script. script.
function subtitleType () { function subtitleType () {
getScript('https://yijuzhan.com/api/word.php?m=js').then(() => { getScript('https://yijuzhan.com/api/word.php?m=js').then(() => {
@@ -99,7 +67,7 @@ case source
subtitleType() subtitleType()
} }
when 4 when 3
script. script.
function subtitleType () { function subtitleType () {
getScript('https://sdk.jinrishici.com/v2/browser/jinrishici.js').then(() => { getScript('https://sdk.jinrishici.com/v2/browser/jinrishici.js').then(() => {

View File

@@ -1,4 +1,6 @@
- let tocNumber = page.toc_number !== undefined ? page.toc_number : theme.toc.number - let tocNumber = page.toc_number !== undefined ? page.toc_number : theme.toc.number
- let tocExpand = page.toc_expand !== undefined ? page.toc_expand : theme.toc.expand
- let tocExpandClass = tocExpand ? 'is-expand' : ''
#card-toc.card-widget #card-toc.card-widget
.item-headline .item-headline
@@ -7,7 +9,7 @@
span.toc-percentage span.toc-percentage
if (page.encrypt == true) if (page.encrypt == true)
.toc-content.toc-div-class(style="display:none")!=toc(page.origin, {list_number: tocNumber}) .toc-content.toc-div-class(class=tocExpandClass style="display:none")!=toc(page.origin, {list_number: tocNumber})
else else
.toc-content!=toc(page.content, {list_number: tocNumber}) .toc-content(class=tocExpandClass)!=toc(page.content, {list_number: tocNumber})

View File

@@ -1,6 +1,6 @@
{ {
"name": "hexo-theme-butterfly", "name": "hexo-theme-butterfly",
"version": "4.0.0-b9", "version": "4.0.0-b10",
"description": "A Simple and Card UI Design theme for Hexo", "description": "A Simple and Card UI Design theme for Hexo",
"main": "package.json", "main": "package.json",
"scripts": { "scripts": {
@@ -20,13 +20,13 @@
}, },
"bugs": { "bugs": {
"url": "https://github.com/jerryc127/hexo-theme-butterfly/issues", "url": "https://github.com/jerryc127/hexo-theme-butterfly/issues",
"email": "i@immyw.com" "email": "my@crazywong.com"
}, },
"dependencies": { "dependencies": {
"hexo-renderer-stylus": "^2.0.1", "hexo-renderer-stylus": "^2.0.1",
"hexo-renderer-pug": "^2.0.0" "hexo-renderer-pug": "^2.0.0"
}, },
"homepage": "https://butterfly.js.org/", "homepage": "https://butterfly.js.org/",
"author": "Jerry <i@immyw.com>", "author": "Jerry <my@crazywong.com>",
"license": "Apache-2.0" "license": "Apache-2.0"
} }

View File

@@ -1,12 +1,18 @@
/** /**
* Butterfly * Butterfly
* CDN * 1. Merge CDN
* 2. Capitalize the first letter of comment name
*/ */
'use strict' 'use strict'
hexo.extend.filter.register('before_generate', () => { hexo.extend.filter.register('before_generate', () => {
const themeConfig = hexo.theme.config const themeConfig = hexo.theme.config
/**
* Merge CDN
*/
const defaultCDN = { const defaultCDN = {
main_css: '/css/index.css', main_css: '/css/index.css',
main: '/js/main.js', main: '/js/main.js',
@@ -24,6 +30,7 @@ hexo.extend.filter.register('before_generate', () => {
utterances: 'https://utteranc.es/client.js', utterances: 'https://utteranc.es/client.js',
twikoo: 'https://cdn.jsdelivr.net/npm/twikoo/dist/twikoo.all.min.js', twikoo: 'https://cdn.jsdelivr.net/npm/twikoo/dist/twikoo.all.min.js',
waline: 'https://cdn.jsdelivr.net/npm/@waline/client/dist/Waline.min.js', waline: 'https://cdn.jsdelivr.net/npm/@waline/client/dist/Waline.min.js',
giscus: 'https://giscus.app/client.js',
// share // share
addtoany: 'https://static.addtoany.com/menu/page.js', addtoany: 'https://static.addtoany.com/menu/page.js',
@@ -101,4 +108,18 @@ hexo.extend.filter.register('before_generate', () => {
} }
themeConfig.CDN = Object.assign(defaultCDN, deleteNullValue(themeConfig.CDN)) themeConfig.CDN = Object.assign(defaultCDN, deleteNullValue(themeConfig.CDN))
/**
* Capitalize the first letter of comment name
*/
let { use } = themeConfig.comments
if (typeof use === 'string') {
use = use.split(',')
}
const newArray = use.map(item => item.toLowerCase().replace(/^\S/, s => s.toUpperCase()))
themeConfig.comments.use = newArray
}) })

View File

@@ -22,8 +22,6 @@ function timeLineFn (args, content) {
matches.push(match[2]) matches.push(match[2])
} }
console.log(matches)
for (let i = 0; i < matches.length; i += 2) { for (let i = 0; i < matches.length; i += 2) {
const tlChildTitle = hexo.render.renderSync({ text: matches[i], engine: 'markdown' }) const tlChildTitle = hexo.render.renderSync({ text: matches[i], engine: 'markdown' })
const tlChildContent = hexo.render.renderSync({ text: matches[i + 1], engine: 'markdown' }) const tlChildContent = hexo.render.renderSync({ text: matches[i + 1], engine: 'markdown' })

View File

@@ -117,7 +117,7 @@ if hexo-config('enter_transitions')
#site-title, #site-title,
#site-subtitle #site-subtitle
animation: titlescale 1s animation: titleScale 1s
#nav.show #nav.show
animation: headerNoOpacity 1s animation: headerNoOpacity 1s
@@ -187,7 +187,7 @@ if hexo-config('avatar.effect') == true
margin-top: 0 margin-top: 0
opacity: 1 opacity: 1
@keyframes titlescale @keyframes titleScale
0% 0%
opacity: 0 opacity: 0
transform: scale(.7) transform: scale(.7)

View File

@@ -32,6 +32,7 @@
--timeline-bg: $timeline-content-bg --timeline-bg: $timeline-content-bg
--timeline-border-color: $timeline-border-color --timeline-border-color: $timeline-border-color
--pseudo-hover: $pseudo-hover --pseudo-hover: $pseudo-hover
--headline-presudo: #a0a0a0
body body
position: relative position: relative

View File

@@ -265,16 +265,17 @@
+maxWidth900() +maxWidth900()
max-height: calc(100vh - 140px) max-height: calc(100vh - 140px)
.toc-child &:not(.is-expand)
display: none .toc-child
display: none
+maxWidth900() +maxWidth900()
display: block !important display: block !important
.toc-item .toc-item
&.active &.active
.toc-child .toc-child
display: block display: block
ol, ol,
li li

View File

@@ -7,7 +7,7 @@ beautify()
font-size: unit(fontsize, 'px') font-size: unit(fontsize, 'px')
&:hover &:hover
padding-left: unit(fontsize + 12, 'px') padding-left: unit(fontsize + 18, 'px')
h1, h1,
h2, h2,
@@ -102,6 +102,32 @@ beautify()
font-family: Monaco, 'Ubuntu Mono', monospace font-family: Monaco, 'Ubuntu Mono', monospace
line-height: 1em line-height: 1em
if hexo-config('anchor')
a.headerlink
&:after
@extend .fontawesomeIcon
float: right
color: var(--headline-presudo)
content: '\f0c1'
font-size: .95em
opacity: 0
transition: all .3s
&:hover
&:after
color: var(--pseudo-hover)
h1,
h2,
h3,
h4,
h5,
h6
&:hover
a.headerlink
&:after
opacity: 1
ol, ol,
ul ul
ol, ol,

View File

@@ -7,11 +7,7 @@
transition: all .5s transition: all .5s
#rightside-config-hide #rightside-config-hide
transition: transform .4s display: none
transform: translate(48px, 0)
&.show
transform: translate(0, 0) !important
& > div & > div
& > button, & > button,
@@ -39,3 +35,19 @@
+maxWidth900() +maxWidth900()
#hide-aside-btn #hide-aside-btn
display: none display: none
@keyframes rightside-item-in
0%
transform: translate(48px, 0)
100%
transform: translate(0, 0)
@keyframes rightside-item-out
0%
display: block
transform: translate(0, 0)
100%
display: none
transform: translate(48px, 0)

View File

@@ -9,7 +9,6 @@
width: 600px width: 600px
border-radius: 8px border-radius: 8px
background: var(--search-bg) background: var(--search-bg)
animation: titlescale .5s
+maxWidth768() +maxWidth768()
top: 0 top: 0

View File

@@ -34,7 +34,7 @@ document.addEventListener('DOMContentLoaded', function () {
open: () => { open: () => {
btf.sidebarPaddingR() btf.sidebarPaddingR()
document.body.style.overflow = 'hidden' document.body.style.overflow = 'hidden'
btf.fadeIn(document.getElementById('menu-mask'), 0.5) btf.animateIn(document.getElementById('menu-mask'), 'to_show 0.5s')
document.getElementById('sidebar-menus').classList.add('open') document.getElementById('sidebar-menus').classList.add('open')
mobileSidebarOpen = true mobileSidebarOpen = true
}, },
@@ -42,7 +42,7 @@ document.addEventListener('DOMContentLoaded', function () {
const $body = document.body const $body = document.body
$body.style.overflow = '' $body.style.overflow = ''
$body.style.paddingRight = '' $body.style.paddingRight = ''
btf.fadeOut(document.getElementById('menu-mask'), 0.5) btf.animateOut(document.getElementById('menu-mask'), 'to_hide 0.5s')
document.getElementById('sidebar-menus').classList.remove('open') document.getElementById('sidebar-menus').classList.remove('open')
mobileSidebarOpen = false mobileSidebarOpen = false
} }
@@ -315,82 +315,69 @@ document.addEventListener('DOMContentLoaded', function () {
} }
/** /**
* toc * toc,anchor
*/ */
const tocFn = function () { const scrollFnToDo = function () {
const $cardTocLayout = document.getElementById('card-toc') const isToc = GLOBAL_CONFIG_SITE.isToc
const $cardToc = $cardTocLayout.getElementsByClassName('toc-content')[0] const isAnchor = GLOBAL_CONFIG.isAnchor
const $tocLink = $cardToc.querySelectorAll('.toc-link')
const $article = document.getElementById('article-container') const $article = document.getElementById('article-container')
const $tocPercentage = $cardTocLayout.querySelector('.toc-percentage')
// main of scroll if (!($article && (isToc || isAnchor))) return
window.tocScrollFn = function () {
return btf.throttle(function () {
const currentTop = window.scrollY || document.documentElement.scrollTop
scrollPercent(currentTop)
findHeadPosition(currentTop)
}, 100)()
}
window.addEventListener('scroll', tocScrollFn)
const scrollPercent = function (currentTop) { let $tocLink, $cardToc, scrollPercent, autoScrollToc, isExpand
const docHeight = $article.clientHeight
const winHeight = document.documentElement.clientHeight
const headerHeight = $article.offsetTop
const contentMath = (docHeight > winHeight) ? (docHeight - winHeight) : (document.documentElement.scrollHeight - winHeight)
const scrollPercent = (currentTop - headerHeight) / (contentMath)
const scrollPercentRounded = Math.round(scrollPercent * 100)
const percentage = (scrollPercentRounded > 100) ? 100 : (scrollPercentRounded <= 0) ? 0 : scrollPercentRounded
$tocPercentage.textContent = percentage
}
// anchor if (isToc) {
const isAnchor = GLOBAL_CONFIG.isanchor const $cardTocLayout = document.getElementById('card-toc')
const updateAnchor = function (anchor) { $cardToc = $cardTocLayout.getElementsByClassName('toc-content')[0]
if (window.history.replaceState && anchor !== window.location.hash) { $tocLink = $cardToc.querySelectorAll('.toc-link')
if (!anchor) anchor = location.pathname const $tocPercentage = $cardTocLayout.querySelector('.toc-percentage')
const title = GLOBAL_CONFIG_SITE.title isExpand = $cardToc.classList.contains('is-expand')
window.history.replaceState({
url: location.href, scrollPercent = currentTop => {
title: title const docHeight = $article.clientHeight
}, title, anchor) const winHeight = document.documentElement.clientHeight
const headerHeight = $article.offsetTop
const contentMath = (docHeight > winHeight) ? (docHeight - winHeight) : (document.documentElement.scrollHeight - winHeight)
const scrollPercent = (currentTop - headerHeight) / (contentMath)
const scrollPercentRounded = Math.round(scrollPercent * 100)
const percentage = (scrollPercentRounded > 100) ? 100 : (scrollPercentRounded <= 0) ? 0 : scrollPercentRounded
$tocPercentage.textContent = percentage
} }
}
window.mobileToc = { window.mobileToc = {
open: () => { open: () => {
$cardTocLayout.style.cssText = 'animation: toc-open .3s; opacity: 1; right: 55px' $cardTocLayout.style.cssText = 'animation: toc-open .3s; opacity: 1; right: 55px'
}, },
close: () => { close: () => {
$cardTocLayout.style.animation = 'toc-close .2s' $cardTocLayout.style.animation = 'toc-close .2s'
setTimeout(() => { setTimeout(() => {
$cardTocLayout.style.cssText = "opacity:''; animation: ''; right: ''" $cardTocLayout.style.cssText = "opacity:''; animation: ''; right: ''"
}, 100) }, 100)
}
} }
}
// toc元素點擊 // toc元素點擊
$cardToc.addEventListener('click', (e) => { $cardToc.addEventListener('click', e => {
e.preventDefault() e.preventDefault()
const $target = e.target.classList.contains('toc-link') const $target = e.target.classList.contains('toc-link')
? e.target ? e.target
: e.target.parentElement : e.target.parentElement
btf.scrollToDest(btf.getEleTop(document.getElementById(decodeURI($target.getAttribute('href')).replace('#', ''))), 300) btf.scrollToDest(btf.getEleTop(document.getElementById(decodeURI($target.getAttribute('href')).replace('#', ''))), 300)
if (window.innerWidth < 900) { if (window.innerWidth < 900) {
window.mobileToc.close() window.mobileToc.close()
} }
}) })
const autoScrollToc = function (item) { autoScrollToc = item => {
const activePosition = item.getBoundingClientRect().top const activePosition = item.getBoundingClientRect().top
const sidebarScrollTop = $cardToc.scrollTop const sidebarScrollTop = $cardToc.scrollTop
if (activePosition > (document.documentElement.clientHeight - 100)) { if (activePosition > (document.documentElement.clientHeight - 100)) {
$cardToc.scrollTop = sidebarScrollTop + 150 $cardToc.scrollTop = sidebarScrollTop + 150
} }
if (activePosition < 100) { if (activePosition < 100) {
$cardToc.scrollTop = sidebarScrollTop - 150 $cardToc.scrollTop = sidebarScrollTop - 150
}
} }
} }
@@ -398,7 +385,7 @@ document.addEventListener('DOMContentLoaded', function () {
const list = $article.querySelectorAll('h1,h2,h3,h4,h5,h6') const list = $article.querySelectorAll('h1,h2,h3,h4,h5,h6')
let detectItem = '' let detectItem = ''
const findHeadPosition = function (top) { const findHeadPosition = function (top) {
if ($tocLink.length === 0 || top === 0) { if (top === 0) {
return false return false
} }
@@ -407,37 +394,50 @@ document.addEventListener('DOMContentLoaded', function () {
list.forEach(function (ele, index) { list.forEach(function (ele, index) {
if (top > btf.getEleTop(ele) - 80) { if (top > btf.getEleTop(ele) - 80) {
currentId = '#' + encodeURI(ele.getAttribute('id')) const id = ele.id
currentId = id ? '#' + encodeURI(id) : ''
currentIndex = index currentIndex = index
} }
}) })
if (detectItem === currentIndex) return if (detectItem === currentIndex) return
if (isAnchor) updateAnchor(currentId) if (isAnchor) btf.updateAnchor(currentId)
if (currentId === '') {
$cardToc.querySelectorAll('.active').forEach(i => { i.classList.remove('active') })
detectItem = currentIndex
return
}
detectItem = currentIndex detectItem = currentIndex
$cardToc.querySelectorAll('.active').forEach(item => { item.classList.remove('active') }) if (isToc) {
const currentActive = $tocLink[currentIndex] $cardToc.querySelectorAll('.active').forEach(i => { i.classList.remove('active') })
currentActive.classList.add('active')
setTimeout(() => { if (currentId === '') {
autoScrollToc(currentActive) return
}, 0) }
let parent = currentActive.parentNode const currentActive = $tocLink[currentIndex]
currentActive.classList.add('active')
for (; !parent.matches('.toc'); parent = parent.parentNode) { setTimeout(() => {
if (parent.matches('li')) parent.classList.add('active') autoScrollToc(currentActive)
}, 0)
if (isExpand) return
let parent = currentActive.parentNode
for (; !parent.matches('.toc'); parent = parent.parentNode) {
if (parent.matches('li')) parent.classList.add('active')
}
} }
} }
// main of scroll
window.tocScrollFn = function () {
return btf.throttle(function () {
const currentTop = window.scrollY || document.documentElement.scrollTop
isToc && scrollPercent(currentTop)
findHeadPosition(currentTop)
}, 100)()
}
window.addEventListener('scroll', tocScrollFn)
} }
/** /**
@@ -473,12 +473,20 @@ document.addEventListener('DOMContentLoaded', function () {
} }
// handle some cases // handle some cases
typeof utterancesTheme === 'function' && utterancesTheme() typeof utterancesTheme === 'function' && utterancesTheme()
typeof changeGiscusTheme === 'function' && changeGiscusTheme()
typeof FB === 'object' && window.loadFBComment() typeof FB === 'object' && window.loadFBComment()
window.DISQUS && document.getElementById('disqus_thread').children.length && setTimeout(() => window.disqusReset(), 200) window.DISQUS && document.getElementById('disqus_thread').children.length && setTimeout(() => window.disqusReset(), 200)
typeof runMermaid === 'function' && window.runMermaid() typeof runMermaid === 'function' && window.runMermaid()
}, },
showOrHideBtn: () => { // rightside 點擊設置 按鈕 展開 showOrHideBtn: () => { // rightside 點擊設置 按鈕 展開
document.getElementById('rightside-config-hide').classList.toggle('show') const target = document.getElementById('rightside-config-hide')
if (window.rightSideIn) {
window.rightSideIn = false
btf.animateOut(target, 'rightside-item-out 0.5s')
} else {
window.rightSideIn = true
btf.animateIn(target, 'rightside-item-in 0.5s')
}
}, },
scrollToTop: () => { // Back to top scrollToTop: () => { // Back to top
btf.scrollToDest(0, 500) btf.scrollToDest(0, 500)
@@ -749,7 +757,7 @@ document.addEventListener('DOMContentLoaded', function () {
toggleCardCategory() toggleCardCategory()
} }
GLOBAL_CONFIG_SITE.isToc && tocFn() scrollFnToDo()
GLOBAL_CONFIG_SITE.isHome && scrollDownInIndex() GLOBAL_CONFIG_SITE.isHome && scrollDownInIndex()
addHighlightTool() addHighlightTool()
GLOBAL_CONFIG.isPhotoFigcaption && addPhotoFigcaption() GLOBAL_CONFIG.isPhotoFigcaption && addPhotoFigcaption()

View File

@@ -3,9 +3,10 @@ window.addEventListener('load', () => {
const bodyStyle = document.body.style const bodyStyle = document.body.style
bodyStyle.width = '100%' bodyStyle.width = '100%'
bodyStyle.overflow = 'hidden' bodyStyle.overflow = 'hidden'
document.querySelector('#algolia-search .search-dialog').style.display = 'block' btf.animateIn(document.getElementById('search-mask'), 'to_show 0.5s')
document.querySelector('#algolia-search .ais-SearchBox-input').focus() btf.animateIn(document.querySelector('#algolia-search .search-dialog'), 'titleScale 0.5s')
btf.fadeIn(document.getElementById('search-mask'), 0.5) setTimeout(() => { document.querySelector('#algolia-search .ais-SearchBox-input').focus() }, 100)
// shortcut: ESC // shortcut: ESC
document.addEventListener('keydown', function f (event) { document.addEventListener('keydown', function f (event) {
if (event.code === 'Escape') { if (event.code === 'Escape') {
@@ -19,10 +20,8 @@ window.addEventListener('load', () => {
const bodyStyle = document.body.style const bodyStyle = document.body.style
bodyStyle.width = '' bodyStyle.width = ''
bodyStyle.overflow = '' bodyStyle.overflow = ''
const $searchDialog = document.querySelector('#algolia-search .search-dialog') btf.animateOut(document.querySelector('#algolia-search .search-dialog'), 'search_close .5s')
$searchDialog.style.animation = 'search_close .5s' btf.animateOut(document.getElementById('search-mask'), 'to_hide 0.5s')
setTimeout(() => { $searchDialog.style.cssText = "display: none; animation: ''" }, 500)
btf.fadeOut(document.getElementById('search-mask'), 0.5)
} }
const searchClickFn = () => { const searchClickFn = () => {

View File

@@ -1,10 +1,12 @@
window.addEventListener('load', () => { window.addEventListener('load', () => {
let loadFlag = false let loadFlag = false
const openSearch = function () { const openSearch = () => {
document.body.style.cssText = 'width: 100%;overflow: hidden' const bodyStyle = document.body.style
document.querySelector('#local-search .search-dialog').style.display = 'block' bodyStyle.width = '100%'
document.querySelector('#local-search-input input').focus() bodyStyle.overflow = 'hidden'
btf.fadeIn(document.getElementById('search-mask'), 0.5) btf.animateIn(document.getElementById('search-mask'), 'to_show 0.5s')
btf.animateIn(document.querySelector('#local-search .search-dialog'), 'titleScale 0.5s')
setTimeout(() => { document.querySelector('#local-search-input input').focus() }, 100)
if (!loadFlag) { if (!loadFlag) {
search(GLOBAL_CONFIG.localSearch.path) search(GLOBAL_CONFIG.localSearch.path)
loadFlag = true loadFlag = true
@@ -18,12 +20,12 @@ window.addEventListener('load', () => {
}) })
} }
const closeSearch = function () { const closeSearch = () => {
document.body.style.cssText = "width: '';overflow: ''" const bodyStyle = document.body.style
const $searchDialog = document.querySelector('#local-search .search-dialog') bodyStyle.width = ''
$searchDialog.style.animation = 'search_close .5s' bodyStyle.overflow = ''
setTimeout(() => { $searchDialog.style.cssText = "display: none; animation: ''" }, 500) btf.animateOut(document.querySelector('#local-search .search-dialog'), 'search_close .5s')
btf.fadeOut(document.getElementById('search-mask'), 0.5) btf.animateOut(document.getElementById('search-mask'), 'to_hide 0.5s')
} }
// click function // click function

View File

@@ -58,16 +58,14 @@ const btf = {
} }
}, },
snackbarShow: (text, showAction, duration) => { snackbarShow: (text, showAction = false, duration = 2000) => {
const sa = (typeof showAction !== 'undefined') ? showAction : false const { position, bgLight, bgDark } = GLOBAL_CONFIG.Snackbar
const dur = (typeof duration !== 'undefined') ? duration : 2000 const bg = document.documentElement.getAttribute('data-theme') === 'light' ? bgLight : bgDark
const position = GLOBAL_CONFIG.Snackbar.position
const bg = document.documentElement.getAttribute('data-theme') === 'light' ? GLOBAL_CONFIG.Snackbar.bgLight : GLOBAL_CONFIG.Snackbar.bgDark
Snackbar.show({ Snackbar.show({
text: text, text: text,
backgroundColor: bg, backgroundColor: bg,
showAction: sa, showAction: showAction,
duration: dur, duration: duration,
pos: position pos: position
}) })
}, },
@@ -151,16 +149,18 @@ const btf = {
}) })
}, },
fadeIn: (ele, time) => { animateIn: (ele, text) => {
ele.style.cssText = `display:block;animation: to_show ${time}s` ele.style.display = 'block'
ele.style.animation = text
}, },
fadeOut: (ele, time) => { animateOut: (ele, text) => {
ele.addEventListener('animationend', function f () { ele.addEventListener('animationend', function f () {
ele.style.cssText = "display: none; animation: '' " ele.style.display = ''
ele.style.animation = ''
ele.removeEventListener('animationend', f) ele.removeEventListener('animationend', f)
}) })
ele.style.animation = `to_hide ${time}s` ele.style.animation = text
}, },
getParents: (elem, selector) => { getParents: (elem, selector) => {
@@ -180,7 +180,6 @@ const btf = {
}, },
/** /**
*
* @param {*} selector * @param {*} selector
* @param {*} eleType the type of create element * @param {*} eleType the type of create element
* @param {*} options object key: value * @param {*} options object key: value
@@ -263,5 +262,16 @@ const btf = {
}) })
} }
}) })
},
updateAnchor: (anchor) => {
if (anchor !== window.location.hash) {
if (!anchor) anchor = location.pathname
const title = GLOBAL_CONFIG_SITE.title
window.history.replaceState({
url: location.href,
title: title
}, title, anchor)
}
} }
} }