mirror of
https://github.com/jerryc127/hexo-theme-butterfly.git
synced 2025-09-15 12:58:48 +08:00
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:
parent
f7c50586ce
commit
ffeab5e20c
@ -14,7 +14,7 @@
|
||||
|
||||

|
||||
|
||||
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/)
|
||||
|
||||
@ -79,7 +79,7 @@ npm i hexo-theme-butterfly
|
||||
- [x] Related articles
|
||||
- [x] Displays outdated notice for a post
|
||||
- [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] Online Chats (Chatra/Tidio/Daovoice/Gitter/Crisp)
|
||||
- [x] Web analytics
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
|
||||

|
||||
|
||||
預覽: 👍 [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/)
|
||||
|
||||
@ -79,7 +79,7 @@ theme: butterfly
|
||||
- [x] 顯示相關文章
|
||||
- [x] 過期文章提醒
|
||||
- [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] 多種在線聊天(Chatra/Tidio/Daovoice/Gitter/Crisp)
|
||||
- [x] 多種分析系統
|
||||
|
||||
29
_config.yml
29
_config.yml
@ -171,6 +171,7 @@ toc:
|
||||
post: true
|
||||
page: false
|
||||
number: true
|
||||
expand: false
|
||||
style_simple: false # for post
|
||||
|
||||
post_copyright:
|
||||
@ -253,10 +254,7 @@ addtoany:
|
||||
|
||||
comments:
|
||||
# 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
|
||||
# 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
|
||||
@ -308,8 +306,6 @@ valine:
|
||||
# https://waline.js.org/
|
||||
waline:
|
||||
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
|
||||
visitor: false
|
||||
option:
|
||||
@ -341,6 +337,17 @@ twikoo:
|
||||
visitor: false
|
||||
option:
|
||||
|
||||
# Giscus
|
||||
# https://giscus.app/
|
||||
giscus:
|
||||
repo:
|
||||
repo_id:
|
||||
category_id:
|
||||
theme:
|
||||
light: light
|
||||
dark: dark
|
||||
option:
|
||||
|
||||
# Chat Services
|
||||
# --------------------------------------
|
||||
|
||||
@ -583,12 +590,11 @@ subtitle:
|
||||
effect: true
|
||||
# loop (循環打字)
|
||||
loop: true
|
||||
# source調用第三方服務
|
||||
# source 調用第三方服務
|
||||
# source: false 關閉調用
|
||||
# source: 1 調用搏天 api 的隨機語錄(簡體)
|
||||
# source: 2 調用一言網的一句話(簡體)
|
||||
# source: 3 調用一句網(簡體)
|
||||
# source: 4 調用今日詩詞(簡體)
|
||||
# source: 1 調用一言網的一句話(簡體) https://hitokoto.cn/
|
||||
# source: 2 調用一句網(簡體) http://yijuzhan.com/
|
||||
# source: 3 調用今日詩詞(簡體) https://www.jinrishici.com/
|
||||
# subtitle 會先顯示 source , 再顯示 sub 的內容
|
||||
source: false
|
||||
# 如果關閉打字效果,subtitle 只會顯示 sub 的第一行文字
|
||||
@ -836,6 +842,7 @@ CDN:
|
||||
utterances:
|
||||
twikoo:
|
||||
waline:
|
||||
giscus:
|
||||
|
||||
# share
|
||||
addtoany:
|
||||
|
||||
@ -119,5 +119,5 @@ script.
|
||||
},
|
||||
isPhotoFigcaption: !{theme.photofigcaption},
|
||||
islazyload: !{theme.lazyload.enable},
|
||||
isanchor: !{theme.anchor}
|
||||
isAnchor: !{theme.anchor}
|
||||
}
|
||||
|
||||
@ -1,18 +1,57 @@
|
||||
#article-container
|
||||
.flink
|
||||
if site.data.link
|
||||
each i in site.data.link
|
||||
if i.class_name
|
||||
h2!= 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
|
||||
if page.flink_url
|
||||
script.
|
||||
(()=>{
|
||||
const replaceSymbol = (str) => {
|
||||
return str.replace(/[\p{P}\p{S}]/gu, '-')
|
||||
}
|
||||
|
||||
let result = ''
|
||||
fetch('!{url_for(page.flink_url)}')
|
||||
.then(response => response.json())
|
||||
.then(str => {
|
||||
for(let i = 0; i < str.length; i++){
|
||||
const replaceClassName = replaceSymbol(str[i].class_name)
|
||||
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
|
||||
|
||||
|
||||
49
layout/includes/third-party/comments/giscus.pug
vendored
Normal file
49
layout/includes/third-party/comments/giscus.pug
vendored
Normal 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()
|
||||
}
|
||||
}
|
||||
@ -33,6 +33,8 @@ hr
|
||||
#twikoo-wrap
|
||||
when 'Waline'
|
||||
#waline-wrap
|
||||
when 'Giscus'
|
||||
#giscus-wrap
|
||||
when 'Facebook Comments'
|
||||
.fb-comments(data-colorscheme = theme.display_mode === 'dark' ? 'dark' : 'light'
|
||||
data-numposts= theme.facebook_comments.pageSize || 10
|
||||
|
||||
2
layout/includes/third-party/comments/js.pug
vendored
2
layout/includes/third-party/comments/js.pug
vendored
@ -16,5 +16,7 @@ each name in theme.comments.use
|
||||
!=partial('includes/third-party/comments/twikoo', {}, {cache: true})
|
||||
when 'Waline'
|
||||
!=partial('includes/third-party/comments/waline', {}, {cache: true})
|
||||
when 'Giscus'
|
||||
!=partial('includes/third-party/comments/giscus', {}, {cache: true})
|
||||
when 'Facebook Comments'
|
||||
!=partial('includes/third-party/comments/facebook_comments', {}, {cache: true})
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
- const { serverURL, avatar, avatarCDN, visitor, option } = theme.waline
|
||||
- const { serverURL, visitor, option } = theme.waline
|
||||
|
||||
script.
|
||||
function loadWaline () {
|
||||
@ -6,8 +6,6 @@ script.
|
||||
const waline = new Waline(Object.assign({
|
||||
el: '#waline-wrap',
|
||||
serverURL: '!{serverURL}',
|
||||
avatar: '#{avatar}',
|
||||
avatarCDN: '!{avatarCDN || "https://sdn.geekzu.org/avatar/"}',
|
||||
path: location.pathname,
|
||||
visitor: !{visitor},
|
||||
dark: 'html[data-theme="dark"]'
|
||||
|
||||
3
layout/includes/third-party/math/mermaid.pug
vendored
3
layout/includes/third-party/math/mermaid.pug
vendored
@ -3,6 +3,7 @@ script.
|
||||
const $mermaidWrap = document.querySelectorAll('#article-container .mermaid-wrap')
|
||||
if ($mermaidWrap.length) {
|
||||
window.runMermaid = () => {
|
||||
window.loadMermaid = true
|
||||
const theme = document.documentElement.getAttribute('data-theme') === 'dark' ? '!{theme.mermaid.theme.dark}' : '!{theme.mermaid.theme.light}'
|
||||
|
||||
Array.from($mermaidWrap).forEach((item, index) => {
|
||||
@ -17,7 +18,7 @@ script.
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
@ -49,7 +49,7 @@ script.
|
||||
}
|
||||
|
||||
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></div>`
|
||||
}
|
||||
|
||||
@ -75,7 +75,7 @@ script.
|
||||
}
|
||||
|
||||
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></div>`
|
||||
}
|
||||
|
||||
@ -60,7 +60,7 @@ script.
|
||||
}
|
||||
|
||||
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></div>`
|
||||
}
|
||||
|
||||
@ -37,7 +37,7 @@ script.
|
||||
}
|
||||
|
||||
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></div>`
|
||||
}
|
||||
|
||||
@ -16,13 +16,6 @@ script.
|
||||
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 => {
|
||||
let result = ''
|
||||
|
||||
@ -36,7 +29,7 @@ script.
|
||||
}
|
||||
|
||||
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></div>`
|
||||
}
|
||||
@ -59,7 +52,7 @@ script.
|
||||
const walineArray = res.comments.map(e => {
|
||||
return {
|
||||
'content': changeContent(e.comment),
|
||||
'avatar': getIcon(e.QQAvatar,e.mail),
|
||||
'avatar': e.avatar,
|
||||
'nick': e.nick,
|
||||
'url': e.url + '#' + e.objectId,
|
||||
'date': e.updatedAt,
|
||||
|
||||
36
layout/includes/third-party/subtitle.pug
vendored
36
layout/includes/third-party/subtitle.pug
vendored
@ -3,38 +3,6 @@
|
||||
|
||||
case source
|
||||
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.
|
||||
function subtitleType () {
|
||||
fetch('https://v1.hitokoto.cn')
|
||||
@ -67,7 +35,7 @@ case source
|
||||
subtitleType()
|
||||
}
|
||||
|
||||
when 3
|
||||
when 2
|
||||
script.
|
||||
function subtitleType () {
|
||||
getScript('https://yijuzhan.com/api/word.php?m=js').then(() => {
|
||||
@ -99,7 +67,7 @@ case source
|
||||
subtitleType()
|
||||
}
|
||||
|
||||
when 4
|
||||
when 3
|
||||
script.
|
||||
function subtitleType () {
|
||||
getScript('https://sdk.jinrishici.com/v2/browser/jinrishici.js').then(() => {
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
- 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
|
||||
.item-headline
|
||||
@ -7,7 +9,7 @@
|
||||
span.toc-percentage
|
||||
|
||||
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
|
||||
.toc-content!=toc(page.content, {list_number: tocNumber})
|
||||
.toc-content(class=tocExpandClass)!=toc(page.content, {list_number: tocNumber})
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "hexo-theme-butterfly",
|
||||
"version": "4.0.0-b9",
|
||||
"version": "4.0.0-b10",
|
||||
"description": "A Simple and Card UI Design theme for Hexo",
|
||||
"main": "package.json",
|
||||
"scripts": {
|
||||
@ -20,13 +20,13 @@
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/jerryc127/hexo-theme-butterfly/issues",
|
||||
"email": "i@immyw.com"
|
||||
"email": "my@crazywong.com"
|
||||
},
|
||||
"dependencies": {
|
||||
"hexo-renderer-stylus": "^2.0.1",
|
||||
"hexo-renderer-pug": "^2.0.0"
|
||||
},
|
||||
"homepage": "https://butterfly.js.org/",
|
||||
"author": "Jerry <i@immyw.com>",
|
||||
"author": "Jerry <my@crazywong.com>",
|
||||
"license": "Apache-2.0"
|
||||
}
|
||||
|
||||
@ -1,12 +1,18 @@
|
||||
/**
|
||||
* Butterfly
|
||||
* CDN
|
||||
* 1. Merge CDN
|
||||
* 2. Capitalize the first letter of comment name
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
hexo.extend.filter.register('before_generate', () => {
|
||||
const themeConfig = hexo.theme.config
|
||||
|
||||
/**
|
||||
* Merge CDN
|
||||
*/
|
||||
|
||||
const defaultCDN = {
|
||||
main_css: '/css/index.css',
|
||||
main: '/js/main.js',
|
||||
@ -24,6 +30,7 @@ hexo.extend.filter.register('before_generate', () => {
|
||||
utterances: 'https://utteranc.es/client.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',
|
||||
giscus: 'https://giscus.app/client.js',
|
||||
|
||||
// share
|
||||
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))
|
||||
|
||||
/**
|
||||
* 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
|
||||
})
|
||||
@ -22,8 +22,6 @@ function timeLineFn (args, content) {
|
||||
matches.push(match[2])
|
||||
}
|
||||
|
||||
console.log(matches)
|
||||
|
||||
for (let i = 0; i < matches.length; i += 2) {
|
||||
const tlChildTitle = hexo.render.renderSync({ text: matches[i], engine: 'markdown' })
|
||||
const tlChildContent = hexo.render.renderSync({ text: matches[i + 1], engine: 'markdown' })
|
||||
|
||||
@ -117,7 +117,7 @@ if hexo-config('enter_transitions')
|
||||
|
||||
#site-title,
|
||||
#site-subtitle
|
||||
animation: titlescale 1s
|
||||
animation: titleScale 1s
|
||||
|
||||
#nav.show
|
||||
animation: headerNoOpacity 1s
|
||||
@ -187,7 +187,7 @@ if hexo-config('avatar.effect') == true
|
||||
margin-top: 0
|
||||
opacity: 1
|
||||
|
||||
@keyframes titlescale
|
||||
@keyframes titleScale
|
||||
0%
|
||||
opacity: 0
|
||||
transform: scale(.7)
|
||||
|
||||
@ -32,6 +32,7 @@
|
||||
--timeline-bg: $timeline-content-bg
|
||||
--timeline-border-color: $timeline-border-color
|
||||
--pseudo-hover: $pseudo-hover
|
||||
--headline-presudo: #a0a0a0
|
||||
|
||||
body
|
||||
position: relative
|
||||
|
||||
@ -265,16 +265,17 @@
|
||||
+maxWidth900()
|
||||
max-height: calc(100vh - 140px)
|
||||
|
||||
.toc-child
|
||||
display: none
|
||||
&:not(.is-expand)
|
||||
.toc-child
|
||||
display: none
|
||||
|
||||
+maxWidth900()
|
||||
display: block !important
|
||||
+maxWidth900()
|
||||
display: block !important
|
||||
|
||||
.toc-item
|
||||
&.active
|
||||
.toc-child
|
||||
display: block
|
||||
.toc-item
|
||||
&.active
|
||||
.toc-child
|
||||
display: block
|
||||
|
||||
ol,
|
||||
li
|
||||
|
||||
@ -7,7 +7,7 @@ beautify()
|
||||
font-size: unit(fontsize, 'px')
|
||||
|
||||
&:hover
|
||||
padding-left: unit(fontsize + 12, 'px')
|
||||
padding-left: unit(fontsize + 18, 'px')
|
||||
|
||||
h1,
|
||||
h2,
|
||||
@ -102,6 +102,32 @@ beautify()
|
||||
font-family: Monaco, 'Ubuntu Mono', monospace
|
||||
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,
|
||||
ul
|
||||
ol,
|
||||
|
||||
@ -7,11 +7,7 @@
|
||||
transition: all .5s
|
||||
|
||||
#rightside-config-hide
|
||||
transition: transform .4s
|
||||
transform: translate(48px, 0)
|
||||
|
||||
&.show
|
||||
transform: translate(0, 0) !important
|
||||
display: none
|
||||
|
||||
& > div
|
||||
& > button,
|
||||
@ -39,3 +35,19 @@
|
||||
+maxWidth900()
|
||||
#hide-aside-btn
|
||||
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)
|
||||
|
||||
@ -9,7 +9,6 @@
|
||||
width: 600px
|
||||
border-radius: 8px
|
||||
background: var(--search-bg)
|
||||
animation: titlescale .5s
|
||||
|
||||
+maxWidth768()
|
||||
top: 0
|
||||
|
||||
@ -34,7 +34,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
open: () => {
|
||||
btf.sidebarPaddingR()
|
||||
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')
|
||||
mobileSidebarOpen = true
|
||||
},
|
||||
@ -42,7 +42,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
const $body = document.body
|
||||
$body.style.overflow = ''
|
||||
$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')
|
||||
mobileSidebarOpen = false
|
||||
}
|
||||
@ -315,82 +315,69 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
}
|
||||
|
||||
/**
|
||||
* toc
|
||||
* toc,anchor
|
||||
*/
|
||||
const tocFn = function () {
|
||||
const $cardTocLayout = document.getElementById('card-toc')
|
||||
const $cardToc = $cardTocLayout.getElementsByClassName('toc-content')[0]
|
||||
const $tocLink = $cardToc.querySelectorAll('.toc-link')
|
||||
const scrollFnToDo = function () {
|
||||
const isToc = GLOBAL_CONFIG_SITE.isToc
|
||||
const isAnchor = GLOBAL_CONFIG.isAnchor
|
||||
const $article = document.getElementById('article-container')
|
||||
const $tocPercentage = $cardTocLayout.querySelector('.toc-percentage')
|
||||
|
||||
// main of scroll
|
||||
window.tocScrollFn = function () {
|
||||
return btf.throttle(function () {
|
||||
const currentTop = window.scrollY || document.documentElement.scrollTop
|
||||
scrollPercent(currentTop)
|
||||
findHeadPosition(currentTop)
|
||||
}, 100)()
|
||||
}
|
||||
window.addEventListener('scroll', tocScrollFn)
|
||||
if (!($article && (isToc || isAnchor))) return
|
||||
|
||||
const scrollPercent = function (currentTop) {
|
||||
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
|
||||
}
|
||||
let $tocLink, $cardToc, scrollPercent, autoScrollToc, isExpand
|
||||
|
||||
// anchor
|
||||
const isAnchor = GLOBAL_CONFIG.isanchor
|
||||
const updateAnchor = function (anchor) {
|
||||
if (window.history.replaceState && 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)
|
||||
if (isToc) {
|
||||
const $cardTocLayout = document.getElementById('card-toc')
|
||||
$cardToc = $cardTocLayout.getElementsByClassName('toc-content')[0]
|
||||
$tocLink = $cardToc.querySelectorAll('.toc-link')
|
||||
const $tocPercentage = $cardTocLayout.querySelector('.toc-percentage')
|
||||
isExpand = $cardToc.classList.contains('is-expand')
|
||||
|
||||
scrollPercent = currentTop => {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
window.mobileToc = {
|
||||
open: () => {
|
||||
$cardTocLayout.style.cssText = 'animation: toc-open .3s; opacity: 1; right: 55px'
|
||||
},
|
||||
window.mobileToc = {
|
||||
open: () => {
|
||||
$cardTocLayout.style.cssText = 'animation: toc-open .3s; opacity: 1; right: 55px'
|
||||
},
|
||||
|
||||
close: () => {
|
||||
$cardTocLayout.style.animation = 'toc-close .2s'
|
||||
setTimeout(() => {
|
||||
$cardTocLayout.style.cssText = "opacity:''; animation: ''; right: ''"
|
||||
}, 100)
|
||||
close: () => {
|
||||
$cardTocLayout.style.animation = 'toc-close .2s'
|
||||
setTimeout(() => {
|
||||
$cardTocLayout.style.cssText = "opacity:''; animation: ''; right: ''"
|
||||
}, 100)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// toc元素點擊
|
||||
$cardToc.addEventListener('click', (e) => {
|
||||
e.preventDefault()
|
||||
const $target = e.target.classList.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()
|
||||
}
|
||||
})
|
||||
// toc元素點擊
|
||||
$cardToc.addEventListener('click', e => {
|
||||
e.preventDefault()
|
||||
const $target = e.target.classList.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()
|
||||
}
|
||||
})
|
||||
|
||||
const autoScrollToc = function (item) {
|
||||
const activePosition = item.getBoundingClientRect().top
|
||||
const sidebarScrollTop = $cardToc.scrollTop
|
||||
if (activePosition > (document.documentElement.clientHeight - 100)) {
|
||||
$cardToc.scrollTop = sidebarScrollTop + 150
|
||||
}
|
||||
if (activePosition < 100) {
|
||||
$cardToc.scrollTop = sidebarScrollTop - 150
|
||||
autoScrollToc = item => {
|
||||
const activePosition = item.getBoundingClientRect().top
|
||||
const sidebarScrollTop = $cardToc.scrollTop
|
||||
if (activePosition > (document.documentElement.clientHeight - 100)) {
|
||||
$cardToc.scrollTop = sidebarScrollTop + 150
|
||||
}
|
||||
if (activePosition < 100) {
|
||||
$cardToc.scrollTop = sidebarScrollTop - 150
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -398,7 +385,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
const list = $article.querySelectorAll('h1,h2,h3,h4,h5,h6')
|
||||
let detectItem = ''
|
||||
const findHeadPosition = function (top) {
|
||||
if ($tocLink.length === 0 || top === 0) {
|
||||
if (top === 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
@ -407,37 +394,50 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
|
||||
list.forEach(function (ele, index) {
|
||||
if (top > btf.getEleTop(ele) - 80) {
|
||||
currentId = '#' + encodeURI(ele.getAttribute('id'))
|
||||
const id = ele.id
|
||||
currentId = id ? '#' + encodeURI(id) : ''
|
||||
currentIndex = index
|
||||
}
|
||||
})
|
||||
|
||||
if (detectItem === currentIndex) return
|
||||
|
||||
if (isAnchor) updateAnchor(currentId)
|
||||
|
||||
if (currentId === '') {
|
||||
$cardToc.querySelectorAll('.active').forEach(i => { i.classList.remove('active') })
|
||||
detectItem = currentIndex
|
||||
return
|
||||
}
|
||||
if (isAnchor) btf.updateAnchor(currentId)
|
||||
|
||||
detectItem = currentIndex
|
||||
|
||||
$cardToc.querySelectorAll('.active').forEach(item => { item.classList.remove('active') })
|
||||
const currentActive = $tocLink[currentIndex]
|
||||
currentActive.classList.add('active')
|
||||
if (isToc) {
|
||||
$cardToc.querySelectorAll('.active').forEach(i => { i.classList.remove('active') })
|
||||
|
||||
setTimeout(() => {
|
||||
autoScrollToc(currentActive)
|
||||
}, 0)
|
||||
if (currentId === '') {
|
||||
return
|
||||
}
|
||||
|
||||
let parent = currentActive.parentNode
|
||||
const currentActive = $tocLink[currentIndex]
|
||||
currentActive.classList.add('active')
|
||||
|
||||
for (; !parent.matches('.toc'); parent = parent.parentNode) {
|
||||
if (parent.matches('li')) parent.classList.add('active')
|
||||
setTimeout(() => {
|
||||
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
|
||||
typeof utterancesTheme === 'function' && utterancesTheme()
|
||||
typeof changeGiscusTheme === 'function' && changeGiscusTheme()
|
||||
typeof FB === 'object' && window.loadFBComment()
|
||||
window.DISQUS && document.getElementById('disqus_thread').children.length && setTimeout(() => window.disqusReset(), 200)
|
||||
typeof runMermaid === 'function' && window.runMermaid()
|
||||
},
|
||||
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
|
||||
btf.scrollToDest(0, 500)
|
||||
@ -749,7 +757,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
toggleCardCategory()
|
||||
}
|
||||
|
||||
GLOBAL_CONFIG_SITE.isToc && tocFn()
|
||||
scrollFnToDo()
|
||||
GLOBAL_CONFIG_SITE.isHome && scrollDownInIndex()
|
||||
addHighlightTool()
|
||||
GLOBAL_CONFIG.isPhotoFigcaption && addPhotoFigcaption()
|
||||
|
||||
@ -3,9 +3,10 @@ window.addEventListener('load', () => {
|
||||
const bodyStyle = document.body.style
|
||||
bodyStyle.width = '100%'
|
||||
bodyStyle.overflow = 'hidden'
|
||||
document.querySelector('#algolia-search .search-dialog').style.display = 'block'
|
||||
document.querySelector('#algolia-search .ais-SearchBox-input').focus()
|
||||
btf.fadeIn(document.getElementById('search-mask'), 0.5)
|
||||
btf.animateIn(document.getElementById('search-mask'), 'to_show 0.5s')
|
||||
btf.animateIn(document.querySelector('#algolia-search .search-dialog'), 'titleScale 0.5s')
|
||||
setTimeout(() => { document.querySelector('#algolia-search .ais-SearchBox-input').focus() }, 100)
|
||||
|
||||
// shortcut: ESC
|
||||
document.addEventListener('keydown', function f (event) {
|
||||
if (event.code === 'Escape') {
|
||||
@ -19,10 +20,8 @@ window.addEventListener('load', () => {
|
||||
const bodyStyle = document.body.style
|
||||
bodyStyle.width = ''
|
||||
bodyStyle.overflow = ''
|
||||
const $searchDialog = document.querySelector('#algolia-search .search-dialog')
|
||||
$searchDialog.style.animation = 'search_close .5s'
|
||||
setTimeout(() => { $searchDialog.style.cssText = "display: none; animation: ''" }, 500)
|
||||
btf.fadeOut(document.getElementById('search-mask'), 0.5)
|
||||
btf.animateOut(document.querySelector('#algolia-search .search-dialog'), 'search_close .5s')
|
||||
btf.animateOut(document.getElementById('search-mask'), 'to_hide 0.5s')
|
||||
}
|
||||
|
||||
const searchClickFn = () => {
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
window.addEventListener('load', () => {
|
||||
let loadFlag = false
|
||||
const openSearch = function () {
|
||||
document.body.style.cssText = 'width: 100%;overflow: hidden'
|
||||
document.querySelector('#local-search .search-dialog').style.display = 'block'
|
||||
document.querySelector('#local-search-input input').focus()
|
||||
btf.fadeIn(document.getElementById('search-mask'), 0.5)
|
||||
const openSearch = () => {
|
||||
const bodyStyle = document.body.style
|
||||
bodyStyle.width = '100%'
|
||||
bodyStyle.overflow = 'hidden'
|
||||
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) {
|
||||
search(GLOBAL_CONFIG.localSearch.path)
|
||||
loadFlag = true
|
||||
@ -18,12 +20,12 @@ window.addEventListener('load', () => {
|
||||
})
|
||||
}
|
||||
|
||||
const closeSearch = function () {
|
||||
document.body.style.cssText = "width: '';overflow: ''"
|
||||
const $searchDialog = document.querySelector('#local-search .search-dialog')
|
||||
$searchDialog.style.animation = 'search_close .5s'
|
||||
setTimeout(() => { $searchDialog.style.cssText = "display: none; animation: ''" }, 500)
|
||||
btf.fadeOut(document.getElementById('search-mask'), 0.5)
|
||||
const closeSearch = () => {
|
||||
const bodyStyle = document.body.style
|
||||
bodyStyle.width = ''
|
||||
bodyStyle.overflow = ''
|
||||
btf.animateOut(document.querySelector('#local-search .search-dialog'), 'search_close .5s')
|
||||
btf.animateOut(document.getElementById('search-mask'), 'to_hide 0.5s')
|
||||
}
|
||||
|
||||
// click function
|
||||
|
||||
@ -58,16 +58,14 @@ const btf = {
|
||||
}
|
||||
},
|
||||
|
||||
snackbarShow: (text, showAction, duration) => {
|
||||
const sa = (typeof showAction !== 'undefined') ? showAction : false
|
||||
const dur = (typeof duration !== 'undefined') ? duration : 2000
|
||||
const position = GLOBAL_CONFIG.Snackbar.position
|
||||
const bg = document.documentElement.getAttribute('data-theme') === 'light' ? GLOBAL_CONFIG.Snackbar.bgLight : GLOBAL_CONFIG.Snackbar.bgDark
|
||||
snackbarShow: (text, showAction = false, duration = 2000) => {
|
||||
const { position, bgLight, bgDark } = GLOBAL_CONFIG.Snackbar
|
||||
const bg = document.documentElement.getAttribute('data-theme') === 'light' ? bgLight : bgDark
|
||||
Snackbar.show({
|
||||
text: text,
|
||||
backgroundColor: bg,
|
||||
showAction: sa,
|
||||
duration: dur,
|
||||
showAction: showAction,
|
||||
duration: duration,
|
||||
pos: position
|
||||
})
|
||||
},
|
||||
@ -151,16 +149,18 @@ const btf = {
|
||||
})
|
||||
},
|
||||
|
||||
fadeIn: (ele, time) => {
|
||||
ele.style.cssText = `display:block;animation: to_show ${time}s`
|
||||
animateIn: (ele, text) => {
|
||||
ele.style.display = 'block'
|
||||
ele.style.animation = text
|
||||
},
|
||||
|
||||
fadeOut: (ele, time) => {
|
||||
animateOut: (ele, text) => {
|
||||
ele.addEventListener('animationend', function f () {
|
||||
ele.style.cssText = "display: none; animation: '' "
|
||||
ele.style.display = ''
|
||||
ele.style.animation = ''
|
||||
ele.removeEventListener('animationend', f)
|
||||
})
|
||||
ele.style.animation = `to_hide ${time}s`
|
||||
ele.style.animation = text
|
||||
},
|
||||
|
||||
getParents: (elem, selector) => {
|
||||
@ -180,7 +180,6 @@ const btf = {
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} selector
|
||||
* @param {*} eleType the type of create element
|
||||
* @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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user