Auto deploy pages
This commit is contained in:
parent
5826032057
commit
1c643b9ad9
File diff suppressed because one or more lines are too long
@ -0,0 +1,160 @@
|
|||||||
|
<!DOCTYPE html><html lang="en" data-theme="light"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0,viewport-fit=cover"><title>October 2024 | Hexo</title><meta name="author" content="John Doe"><meta name="copyright" content="John Doe"><meta name="format-detection" content="telephone=no"><meta name="theme-color" content="#ffffff"><meta property="og:type" content="website">
|
||||||
|
<meta property="og:title" content="October 2024">
|
||||||
|
<meta property="og:url" content="https://blog.linloir.cn/archives/2024/10/">
|
||||||
|
<meta property="og:site_name" content="Hexo">
|
||||||
|
<meta property="og:locale" content="en_US">
|
||||||
|
<meta property="og:image" content="https://blog.linloir.cn/img/butterfly-icon.png">
|
||||||
|
<meta property="article:author" content="John Doe">
|
||||||
|
<meta name="twitter:card" content="summary">
|
||||||
|
<meta name="twitter:image" content="https://blog.linloir.cn/img/butterfly-icon.png"><link rel="shortcut icon" href="/img/favicon.png"><link rel="canonical" href="https://blog.linloir.cn/archives/2024/10/"><link rel="preconnect" href="//cdn.jsdelivr.net"/><link rel="preconnect" href="//busuanzi.ibruce.info"/><link rel="stylesheet" href="/css/index.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free/css/all.min.css"><script>
|
||||||
|
(() => {
|
||||||
|
|
||||||
|
const saveToLocal = {
|
||||||
|
set: (key, value, ttl) => {
|
||||||
|
if (!ttl) return
|
||||||
|
const expiry = Date.now() + ttl * 86400000
|
||||||
|
localStorage.setItem(key, JSON.stringify({ value, expiry }))
|
||||||
|
},
|
||||||
|
get: key => {
|
||||||
|
const itemStr = localStorage.getItem(key)
|
||||||
|
if (!itemStr) return undefined
|
||||||
|
const { value, expiry } = JSON.parse(itemStr)
|
||||||
|
if (Date.now() > expiry) {
|
||||||
|
localStorage.removeItem(key)
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.btf = {
|
||||||
|
saveToLocal,
|
||||||
|
getScript: (url, attr = {}) => new Promise((resolve, reject) => {
|
||||||
|
const script = document.createElement('script')
|
||||||
|
script.src = url
|
||||||
|
script.async = true
|
||||||
|
Object.entries(attr).forEach(([key, val]) => script.setAttribute(key, val))
|
||||||
|
script.onload = script.onreadystatechange = () => {
|
||||||
|
if (!script.readyState || /loaded|complete/.test(script.readyState)) resolve()
|
||||||
|
}
|
||||||
|
script.onerror = reject
|
||||||
|
document.head.appendChild(script)
|
||||||
|
}),
|
||||||
|
getCSS: (url, id) => new Promise((resolve, reject) => {
|
||||||
|
const link = document.createElement('link')
|
||||||
|
link.rel = 'stylesheet'
|
||||||
|
link.href = url
|
||||||
|
if (id) link.id = id
|
||||||
|
link.onload = link.onreadystatechange = () => {
|
||||||
|
if (!link.readyState || /loaded|complete/.test(link.readyState)) resolve()
|
||||||
|
}
|
||||||
|
link.onerror = reject
|
||||||
|
document.head.appendChild(link)
|
||||||
|
}),
|
||||||
|
addGlobalFn: (key, fn, name = false, parent = window) => {
|
||||||
|
if (!false && key.startsWith('pjax')) return
|
||||||
|
const globalFn = parent.globalFn || {}
|
||||||
|
globalFn[key] = globalFn[key] || {}
|
||||||
|
if (name && globalFn[key][name]) return
|
||||||
|
globalFn[key][name || Object.keys(globalFn[key]).length] = fn
|
||||||
|
parent.globalFn = globalFn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const activateDarkMode = () => {
|
||||||
|
document.documentElement.setAttribute('data-theme', 'dark')
|
||||||
|
if (document.querySelector('meta[name="theme-color"]') !== null) {
|
||||||
|
document.querySelector('meta[name="theme-color"]').setAttribute('content', '#0d0d0d')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const activateLightMode = () => {
|
||||||
|
document.documentElement.setAttribute('data-theme', 'light')
|
||||||
|
if (document.querySelector('meta[name="theme-color"]') !== null) {
|
||||||
|
document.querySelector('meta[name="theme-color"]').setAttribute('content', '#ffffff')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
btf.activateDarkMode = activateDarkMode
|
||||||
|
btf.activateLightMode = activateLightMode
|
||||||
|
|
||||||
|
const theme = saveToLocal.get('theme')
|
||||||
|
|
||||||
|
theme === 'dark' ? activateDarkMode() : theme === 'light' ? activateLightMode() : null
|
||||||
|
|
||||||
|
|
||||||
|
const asideStatus = saveToLocal.get('aside-status')
|
||||||
|
if (asideStatus !== undefined) {
|
||||||
|
document.documentElement.classList.toggle('hide-aside', asideStatus === 'hide')
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const detectApple = () => {
|
||||||
|
if (/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)) {
|
||||||
|
document.documentElement.classList.add('apple')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
detectApple()
|
||||||
|
|
||||||
|
})()
|
||||||
|
</script><script>const GLOBAL_CONFIG = {
|
||||||
|
root: '/',
|
||||||
|
algolia: undefined,
|
||||||
|
localSearch: undefined,
|
||||||
|
translate: undefined,
|
||||||
|
noticeOutdate: undefined,
|
||||||
|
highlight: {"plugin":"highlight.js","highlightCopy":true,"highlightLang":true,"highlightHeightLimit":false,"highlightFullpage":false,"highlightMacStyle":false},
|
||||||
|
copy: {
|
||||||
|
success: 'Copy Successful',
|
||||||
|
error: 'Copy Failed',
|
||||||
|
noSupport: 'Browser Not Supported'
|
||||||
|
},
|
||||||
|
relativeDate: {
|
||||||
|
homepage: false,
|
||||||
|
post: false
|
||||||
|
},
|
||||||
|
runtime: '',
|
||||||
|
dateSuffix: {
|
||||||
|
just: 'Just now',
|
||||||
|
min: 'minutes ago',
|
||||||
|
hour: 'hours ago',
|
||||||
|
day: 'days ago',
|
||||||
|
month: 'months ago'
|
||||||
|
},
|
||||||
|
copyright: undefined,
|
||||||
|
lightbox: 'null',
|
||||||
|
Snackbar: undefined,
|
||||||
|
infinitegrid: {
|
||||||
|
js: 'https://cdn.jsdelivr.net/npm/@egjs/infinitegrid/dist/infinitegrid.min.js',
|
||||||
|
buttonText: 'Load More'
|
||||||
|
},
|
||||||
|
isPhotoFigcaption: false,
|
||||||
|
islazyload: false,
|
||||||
|
isAnchor: false,
|
||||||
|
percent: {
|
||||||
|
toc: true,
|
||||||
|
rightside: false,
|
||||||
|
},
|
||||||
|
autoDarkmode: false
|
||||||
|
}</script><script id="config-diff">var GLOBAL_CONFIG_SITE = {
|
||||||
|
title: 'October 2024',
|
||||||
|
isPost: false,
|
||||||
|
isHome: false,
|
||||||
|
isHighlightShrink: false,
|
||||||
|
isToc: false,
|
||||||
|
postUpdate: '2024-10-09 16:07:29'
|
||||||
|
}</script><meta name="generator" content="Hexo 7.3.0"></head><body><div class="page" id="body-wrap"><header class="not-home-page" id="page-header"><nav id="nav"><span id="blog-info"><a class="nav-site-title" href="/"><span class="site-name">Hexo</span></a></span><div id="menus"></div></nav><div id="page-site-info"><h1 id="site-title">October 2024</h1></div></header><main class="layout" id="content-inner"><div id="archive"><div class="article-sort-title">All Articles - 1</div><div class="article-sort"><div class="article-sort-item year">2024</div><div class="article-sort-item no-article-cover"><div class="article-sort-item-info"><div class="article-sort-item-time"><i class="far fa-calendar-alt"></i><time class="post-meta-date-created" datetime="2024-10-09T16:07:19.760Z" title="Created 2024-10-09 16:07:19">2024-10-09</time></div><a class="article-sort-item-title" href="/2024/10/09/hello-world/" title="Hello World">Hello World</a></div></div></div><nav id="pagination"><div class="pagination"><span class="page-number current">1</span></div></nav></div><div class="aside-content" id="aside-content"><div class="card-widget card-info is-center"><div class="avatar-img"><img src="/img/butterfly-icon.png" onerror="this.onerror=null;this.src='/img/friend_404.gif'" alt="avatar"/></div><div class="author-info-name">John Doe</div><div class="author-info-description"></div><div class="site-data"><a href="/archives/"><div class="headline">Articles</div><div class="length-num">1</div></a><a href="/tags/"><div class="headline">Tags</div><div class="length-num">0</div></a><a href="/categories/"><div class="headline">Categories</div><div class="length-num">0</div></a></div><a id="card-info-btn" target="_blank" rel="noopener" href="https://github.com/xxxxxx"><i class="fab fa-github"></i><span>Follow Me</span></a></div><div class="card-widget card-announcement"><div class="item-headline"><i class="fas fa-bullhorn fa-shake"></i><span>Announcement</span></div><div class="announcement_content">This is my Blog</div></div><div class="sticky_layout"><div class="card-widget card-recent-post"><div class="item-headline"><i class="fas fa-history"></i><span>Recent Posts</span></div><div class="aside-list"><div class="aside-list-item no-cover"><div class="content"><a class="title" href="/2024/10/09/hello-world/" title="Hello World">Hello World</a><time datetime="2024-10-09T16:07:19.760Z" title="Created 2024-10-09 16:07:19">2024-10-09</time></div></div></div></div><div class="card-widget card-archives">
|
||||||
|
<div class="item-headline">
|
||||||
|
<i class="fas fa-archive"></i>
|
||||||
|
<span>Archives</span>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<ul class="card-archive-list">
|
||||||
|
|
||||||
|
<li class="card-archive-list-item">
|
||||||
|
<a class="card-archive-list-link" href="/archives/2024/10/">
|
||||||
|
<span class="card-archive-list-date">October 2024</span>
|
||||||
|
<span class="card-archive-list-count">1</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul></div><div class="card-widget card-webinfo"><div class="item-headline"><i class="fas fa-chart-line"></i><span>Website Info</span></div><div class="webinfo"><div class="webinfo-item"><div class="item-name">Article Count :</div><div class="item-count">1</div></div><div class="webinfo-item"><div class="item-name">Unique Visitors :</div><div class="item-count" id="busuanzi_value_site_uv"><i class="fa-solid fa-spinner fa-spin"></i></div></div><div class="webinfo-item"><div class="item-name">Page Views :</div><div class="item-count" id="busuanzi_value_site_pv"><i class="fa-solid fa-spinner fa-spin"></i></div></div><div class="webinfo-item"><div class="item-name">Last Update :</div><div class="item-count" id="last-push-date" data-lastPushDate="2024-10-09T16:07:29.012Z"><i class="fa-solid fa-spinner fa-spin"></i></div></div></div></div></div></div></main><footer id="footer"><div id="footer-wrap"><div class="copyright">©2019 - 2024 By John Doe</div><div class="framework-info"><span>Framework </span><a target="_blank" rel="noopener" href="https://hexo.io">Hexo</a><span class="footer-separator">|</span><span>Theme </span><a target="_blank" rel="noopener" href="https://github.com/jerryc127/hexo-theme-butterfly">Butterfly</a></div></div></footer></div><div id="rightside"><div id="rightside-config-hide"><button id="darkmode" type="button" title="Toggle Between Light and Dark Mode"><i class="fas fa-adjust"></i></button><button id="hide-aside-btn" type="button" title="Toggle Between Single-column and Double-column"><i class="fas fa-arrows-alt-h"></i></button></div><div id="rightside-config-show"><button id="rightside-config" type="button" title="Settings"><i class="fas fa-cog fa-spin"></i></button><button id="go-up" type="button" title="Back to Top"><span class="scroll-percent"></span><i class="fas fa-arrow-up"></i></button></div></div><div><script src="/js/utils.js"></script><script src="/js/main.js"></script><div class="js-pjax"></div><script async data-pjax src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script></div></body></html>
|
||||||
@ -0,0 +1,160 @@
|
|||||||
|
<!DOCTYPE html><html lang="en" data-theme="light"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0,viewport-fit=cover"><title>2024 | Hexo</title><meta name="author" content="John Doe"><meta name="copyright" content="John Doe"><meta name="format-detection" content="telephone=no"><meta name="theme-color" content="#ffffff"><meta property="og:type" content="website">
|
||||||
|
<meta property="og:title" content="2024">
|
||||||
|
<meta property="og:url" content="https://blog.linloir.cn/archives/2024/">
|
||||||
|
<meta property="og:site_name" content="Hexo">
|
||||||
|
<meta property="og:locale" content="en_US">
|
||||||
|
<meta property="og:image" content="https://blog.linloir.cn/img/butterfly-icon.png">
|
||||||
|
<meta property="article:author" content="John Doe">
|
||||||
|
<meta name="twitter:card" content="summary">
|
||||||
|
<meta name="twitter:image" content="https://blog.linloir.cn/img/butterfly-icon.png"><link rel="shortcut icon" href="/img/favicon.png"><link rel="canonical" href="https://blog.linloir.cn/archives/2024/"><link rel="preconnect" href="//cdn.jsdelivr.net"/><link rel="preconnect" href="//busuanzi.ibruce.info"/><link rel="stylesheet" href="/css/index.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free/css/all.min.css"><script>
|
||||||
|
(() => {
|
||||||
|
|
||||||
|
const saveToLocal = {
|
||||||
|
set: (key, value, ttl) => {
|
||||||
|
if (!ttl) return
|
||||||
|
const expiry = Date.now() + ttl * 86400000
|
||||||
|
localStorage.setItem(key, JSON.stringify({ value, expiry }))
|
||||||
|
},
|
||||||
|
get: key => {
|
||||||
|
const itemStr = localStorage.getItem(key)
|
||||||
|
if (!itemStr) return undefined
|
||||||
|
const { value, expiry } = JSON.parse(itemStr)
|
||||||
|
if (Date.now() > expiry) {
|
||||||
|
localStorage.removeItem(key)
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.btf = {
|
||||||
|
saveToLocal,
|
||||||
|
getScript: (url, attr = {}) => new Promise((resolve, reject) => {
|
||||||
|
const script = document.createElement('script')
|
||||||
|
script.src = url
|
||||||
|
script.async = true
|
||||||
|
Object.entries(attr).forEach(([key, val]) => script.setAttribute(key, val))
|
||||||
|
script.onload = script.onreadystatechange = () => {
|
||||||
|
if (!script.readyState || /loaded|complete/.test(script.readyState)) resolve()
|
||||||
|
}
|
||||||
|
script.onerror = reject
|
||||||
|
document.head.appendChild(script)
|
||||||
|
}),
|
||||||
|
getCSS: (url, id) => new Promise((resolve, reject) => {
|
||||||
|
const link = document.createElement('link')
|
||||||
|
link.rel = 'stylesheet'
|
||||||
|
link.href = url
|
||||||
|
if (id) link.id = id
|
||||||
|
link.onload = link.onreadystatechange = () => {
|
||||||
|
if (!link.readyState || /loaded|complete/.test(link.readyState)) resolve()
|
||||||
|
}
|
||||||
|
link.onerror = reject
|
||||||
|
document.head.appendChild(link)
|
||||||
|
}),
|
||||||
|
addGlobalFn: (key, fn, name = false, parent = window) => {
|
||||||
|
if (!false && key.startsWith('pjax')) return
|
||||||
|
const globalFn = parent.globalFn || {}
|
||||||
|
globalFn[key] = globalFn[key] || {}
|
||||||
|
if (name && globalFn[key][name]) return
|
||||||
|
globalFn[key][name || Object.keys(globalFn[key]).length] = fn
|
||||||
|
parent.globalFn = globalFn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const activateDarkMode = () => {
|
||||||
|
document.documentElement.setAttribute('data-theme', 'dark')
|
||||||
|
if (document.querySelector('meta[name="theme-color"]') !== null) {
|
||||||
|
document.querySelector('meta[name="theme-color"]').setAttribute('content', '#0d0d0d')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const activateLightMode = () => {
|
||||||
|
document.documentElement.setAttribute('data-theme', 'light')
|
||||||
|
if (document.querySelector('meta[name="theme-color"]') !== null) {
|
||||||
|
document.querySelector('meta[name="theme-color"]').setAttribute('content', '#ffffff')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
btf.activateDarkMode = activateDarkMode
|
||||||
|
btf.activateLightMode = activateLightMode
|
||||||
|
|
||||||
|
const theme = saveToLocal.get('theme')
|
||||||
|
|
||||||
|
theme === 'dark' ? activateDarkMode() : theme === 'light' ? activateLightMode() : null
|
||||||
|
|
||||||
|
|
||||||
|
const asideStatus = saveToLocal.get('aside-status')
|
||||||
|
if (asideStatus !== undefined) {
|
||||||
|
document.documentElement.classList.toggle('hide-aside', asideStatus === 'hide')
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const detectApple = () => {
|
||||||
|
if (/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)) {
|
||||||
|
document.documentElement.classList.add('apple')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
detectApple()
|
||||||
|
|
||||||
|
})()
|
||||||
|
</script><script>const GLOBAL_CONFIG = {
|
||||||
|
root: '/',
|
||||||
|
algolia: undefined,
|
||||||
|
localSearch: undefined,
|
||||||
|
translate: undefined,
|
||||||
|
noticeOutdate: undefined,
|
||||||
|
highlight: {"plugin":"highlight.js","highlightCopy":true,"highlightLang":true,"highlightHeightLimit":false,"highlightFullpage":false,"highlightMacStyle":false},
|
||||||
|
copy: {
|
||||||
|
success: 'Copy Successful',
|
||||||
|
error: 'Copy Failed',
|
||||||
|
noSupport: 'Browser Not Supported'
|
||||||
|
},
|
||||||
|
relativeDate: {
|
||||||
|
homepage: false,
|
||||||
|
post: false
|
||||||
|
},
|
||||||
|
runtime: '',
|
||||||
|
dateSuffix: {
|
||||||
|
just: 'Just now',
|
||||||
|
min: 'minutes ago',
|
||||||
|
hour: 'hours ago',
|
||||||
|
day: 'days ago',
|
||||||
|
month: 'months ago'
|
||||||
|
},
|
||||||
|
copyright: undefined,
|
||||||
|
lightbox: 'null',
|
||||||
|
Snackbar: undefined,
|
||||||
|
infinitegrid: {
|
||||||
|
js: 'https://cdn.jsdelivr.net/npm/@egjs/infinitegrid/dist/infinitegrid.min.js',
|
||||||
|
buttonText: 'Load More'
|
||||||
|
},
|
||||||
|
isPhotoFigcaption: false,
|
||||||
|
islazyload: false,
|
||||||
|
isAnchor: false,
|
||||||
|
percent: {
|
||||||
|
toc: true,
|
||||||
|
rightside: false,
|
||||||
|
},
|
||||||
|
autoDarkmode: false
|
||||||
|
}</script><script id="config-diff">var GLOBAL_CONFIG_SITE = {
|
||||||
|
title: '2024',
|
||||||
|
isPost: false,
|
||||||
|
isHome: false,
|
||||||
|
isHighlightShrink: false,
|
||||||
|
isToc: false,
|
||||||
|
postUpdate: '2024-10-09 16:07:29'
|
||||||
|
}</script><meta name="generator" content="Hexo 7.3.0"></head><body><div class="page" id="body-wrap"><header class="not-home-page" id="page-header"><nav id="nav"><span id="blog-info"><a class="nav-site-title" href="/"><span class="site-name">Hexo</span></a></span><div id="menus"></div></nav><div id="page-site-info"><h1 id="site-title">2024</h1></div></header><main class="layout" id="content-inner"><div id="archive"><div class="article-sort-title">All Articles - 1</div><div class="article-sort"><div class="article-sort-item year">2024</div><div class="article-sort-item no-article-cover"><div class="article-sort-item-info"><div class="article-sort-item-time"><i class="far fa-calendar-alt"></i><time class="post-meta-date-created" datetime="2024-10-09T16:07:19.760Z" title="Created 2024-10-09 16:07:19">2024-10-09</time></div><a class="article-sort-item-title" href="/2024/10/09/hello-world/" title="Hello World">Hello World</a></div></div></div><nav id="pagination"><div class="pagination"><span class="page-number current">1</span></div></nav></div><div class="aside-content" id="aside-content"><div class="card-widget card-info is-center"><div class="avatar-img"><img src="/img/butterfly-icon.png" onerror="this.onerror=null;this.src='/img/friend_404.gif'" alt="avatar"/></div><div class="author-info-name">John Doe</div><div class="author-info-description"></div><div class="site-data"><a href="/archives/"><div class="headline">Articles</div><div class="length-num">1</div></a><a href="/tags/"><div class="headline">Tags</div><div class="length-num">0</div></a><a href="/categories/"><div class="headline">Categories</div><div class="length-num">0</div></a></div><a id="card-info-btn" target="_blank" rel="noopener" href="https://github.com/xxxxxx"><i class="fab fa-github"></i><span>Follow Me</span></a></div><div class="card-widget card-announcement"><div class="item-headline"><i class="fas fa-bullhorn fa-shake"></i><span>Announcement</span></div><div class="announcement_content">This is my Blog</div></div><div class="sticky_layout"><div class="card-widget card-recent-post"><div class="item-headline"><i class="fas fa-history"></i><span>Recent Posts</span></div><div class="aside-list"><div class="aside-list-item no-cover"><div class="content"><a class="title" href="/2024/10/09/hello-world/" title="Hello World">Hello World</a><time datetime="2024-10-09T16:07:19.760Z" title="Created 2024-10-09 16:07:19">2024-10-09</time></div></div></div></div><div class="card-widget card-archives">
|
||||||
|
<div class="item-headline">
|
||||||
|
<i class="fas fa-archive"></i>
|
||||||
|
<span>Archives</span>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<ul class="card-archive-list">
|
||||||
|
|
||||||
|
<li class="card-archive-list-item">
|
||||||
|
<a class="card-archive-list-link" href="/archives/2024/10/">
|
||||||
|
<span class="card-archive-list-date">October 2024</span>
|
||||||
|
<span class="card-archive-list-count">1</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul></div><div class="card-widget card-webinfo"><div class="item-headline"><i class="fas fa-chart-line"></i><span>Website Info</span></div><div class="webinfo"><div class="webinfo-item"><div class="item-name">Article Count :</div><div class="item-count">1</div></div><div class="webinfo-item"><div class="item-name">Unique Visitors :</div><div class="item-count" id="busuanzi_value_site_uv"><i class="fa-solid fa-spinner fa-spin"></i></div></div><div class="webinfo-item"><div class="item-name">Page Views :</div><div class="item-count" id="busuanzi_value_site_pv"><i class="fa-solid fa-spinner fa-spin"></i></div></div><div class="webinfo-item"><div class="item-name">Last Update :</div><div class="item-count" id="last-push-date" data-lastPushDate="2024-10-09T16:07:29.012Z"><i class="fa-solid fa-spinner fa-spin"></i></div></div></div></div></div></div></main><footer id="footer"><div id="footer-wrap"><div class="copyright">©2019 - 2024 By John Doe</div><div class="framework-info"><span>Framework </span><a target="_blank" rel="noopener" href="https://hexo.io">Hexo</a><span class="footer-separator">|</span><span>Theme </span><a target="_blank" rel="noopener" href="https://github.com/jerryc127/hexo-theme-butterfly">Butterfly</a></div></div></footer></div><div id="rightside"><div id="rightside-config-hide"><button id="darkmode" type="button" title="Toggle Between Light and Dark Mode"><i class="fas fa-adjust"></i></button><button id="hide-aside-btn" type="button" title="Toggle Between Single-column and Double-column"><i class="fas fa-arrows-alt-h"></i></button></div><div id="rightside-config-show"><button id="rightside-config" type="button" title="Settings"><i class="fas fa-cog fa-spin"></i></button><button id="go-up" type="button" title="Back to Top"><span class="scroll-percent"></span><i class="fas fa-arrow-up"></i></button></div></div><div><script src="/js/utils.js"></script><script src="/js/main.js"></script><div class="js-pjax"></div><script async data-pjax src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script></div></body></html>
|
||||||
@ -0,0 +1,160 @@
|
|||||||
|
<!DOCTYPE html><html lang="en" data-theme="light"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0,viewport-fit=cover"><title>Archives | Hexo</title><meta name="author" content="John Doe"><meta name="copyright" content="John Doe"><meta name="format-detection" content="telephone=no"><meta name="theme-color" content="#ffffff"><meta property="og:type" content="website">
|
||||||
|
<meta property="og:title" content="Archives">
|
||||||
|
<meta property="og:url" content="https://blog.linloir.cn/archives/">
|
||||||
|
<meta property="og:site_name" content="Hexo">
|
||||||
|
<meta property="og:locale" content="en_US">
|
||||||
|
<meta property="og:image" content="https://blog.linloir.cn/img/butterfly-icon.png">
|
||||||
|
<meta property="article:author" content="John Doe">
|
||||||
|
<meta name="twitter:card" content="summary">
|
||||||
|
<meta name="twitter:image" content="https://blog.linloir.cn/img/butterfly-icon.png"><link rel="shortcut icon" href="/img/favicon.png"><link rel="canonical" href="https://blog.linloir.cn/archives/"><link rel="preconnect" href="//cdn.jsdelivr.net"/><link rel="preconnect" href="//busuanzi.ibruce.info"/><link rel="stylesheet" href="/css/index.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free/css/all.min.css"><script>
|
||||||
|
(() => {
|
||||||
|
|
||||||
|
const saveToLocal = {
|
||||||
|
set: (key, value, ttl) => {
|
||||||
|
if (!ttl) return
|
||||||
|
const expiry = Date.now() + ttl * 86400000
|
||||||
|
localStorage.setItem(key, JSON.stringify({ value, expiry }))
|
||||||
|
},
|
||||||
|
get: key => {
|
||||||
|
const itemStr = localStorage.getItem(key)
|
||||||
|
if (!itemStr) return undefined
|
||||||
|
const { value, expiry } = JSON.parse(itemStr)
|
||||||
|
if (Date.now() > expiry) {
|
||||||
|
localStorage.removeItem(key)
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.btf = {
|
||||||
|
saveToLocal,
|
||||||
|
getScript: (url, attr = {}) => new Promise((resolve, reject) => {
|
||||||
|
const script = document.createElement('script')
|
||||||
|
script.src = url
|
||||||
|
script.async = true
|
||||||
|
Object.entries(attr).forEach(([key, val]) => script.setAttribute(key, val))
|
||||||
|
script.onload = script.onreadystatechange = () => {
|
||||||
|
if (!script.readyState || /loaded|complete/.test(script.readyState)) resolve()
|
||||||
|
}
|
||||||
|
script.onerror = reject
|
||||||
|
document.head.appendChild(script)
|
||||||
|
}),
|
||||||
|
getCSS: (url, id) => new Promise((resolve, reject) => {
|
||||||
|
const link = document.createElement('link')
|
||||||
|
link.rel = 'stylesheet'
|
||||||
|
link.href = url
|
||||||
|
if (id) link.id = id
|
||||||
|
link.onload = link.onreadystatechange = () => {
|
||||||
|
if (!link.readyState || /loaded|complete/.test(link.readyState)) resolve()
|
||||||
|
}
|
||||||
|
link.onerror = reject
|
||||||
|
document.head.appendChild(link)
|
||||||
|
}),
|
||||||
|
addGlobalFn: (key, fn, name = false, parent = window) => {
|
||||||
|
if (!false && key.startsWith('pjax')) return
|
||||||
|
const globalFn = parent.globalFn || {}
|
||||||
|
globalFn[key] = globalFn[key] || {}
|
||||||
|
if (name && globalFn[key][name]) return
|
||||||
|
globalFn[key][name || Object.keys(globalFn[key]).length] = fn
|
||||||
|
parent.globalFn = globalFn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const activateDarkMode = () => {
|
||||||
|
document.documentElement.setAttribute('data-theme', 'dark')
|
||||||
|
if (document.querySelector('meta[name="theme-color"]') !== null) {
|
||||||
|
document.querySelector('meta[name="theme-color"]').setAttribute('content', '#0d0d0d')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const activateLightMode = () => {
|
||||||
|
document.documentElement.setAttribute('data-theme', 'light')
|
||||||
|
if (document.querySelector('meta[name="theme-color"]') !== null) {
|
||||||
|
document.querySelector('meta[name="theme-color"]').setAttribute('content', '#ffffff')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
btf.activateDarkMode = activateDarkMode
|
||||||
|
btf.activateLightMode = activateLightMode
|
||||||
|
|
||||||
|
const theme = saveToLocal.get('theme')
|
||||||
|
|
||||||
|
theme === 'dark' ? activateDarkMode() : theme === 'light' ? activateLightMode() : null
|
||||||
|
|
||||||
|
|
||||||
|
const asideStatus = saveToLocal.get('aside-status')
|
||||||
|
if (asideStatus !== undefined) {
|
||||||
|
document.documentElement.classList.toggle('hide-aside', asideStatus === 'hide')
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const detectApple = () => {
|
||||||
|
if (/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)) {
|
||||||
|
document.documentElement.classList.add('apple')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
detectApple()
|
||||||
|
|
||||||
|
})()
|
||||||
|
</script><script>const GLOBAL_CONFIG = {
|
||||||
|
root: '/',
|
||||||
|
algolia: undefined,
|
||||||
|
localSearch: undefined,
|
||||||
|
translate: undefined,
|
||||||
|
noticeOutdate: undefined,
|
||||||
|
highlight: {"plugin":"highlight.js","highlightCopy":true,"highlightLang":true,"highlightHeightLimit":false,"highlightFullpage":false,"highlightMacStyle":false},
|
||||||
|
copy: {
|
||||||
|
success: 'Copy Successful',
|
||||||
|
error: 'Copy Failed',
|
||||||
|
noSupport: 'Browser Not Supported'
|
||||||
|
},
|
||||||
|
relativeDate: {
|
||||||
|
homepage: false,
|
||||||
|
post: false
|
||||||
|
},
|
||||||
|
runtime: '',
|
||||||
|
dateSuffix: {
|
||||||
|
just: 'Just now',
|
||||||
|
min: 'minutes ago',
|
||||||
|
hour: 'hours ago',
|
||||||
|
day: 'days ago',
|
||||||
|
month: 'months ago'
|
||||||
|
},
|
||||||
|
copyright: undefined,
|
||||||
|
lightbox: 'null',
|
||||||
|
Snackbar: undefined,
|
||||||
|
infinitegrid: {
|
||||||
|
js: 'https://cdn.jsdelivr.net/npm/@egjs/infinitegrid/dist/infinitegrid.min.js',
|
||||||
|
buttonText: 'Load More'
|
||||||
|
},
|
||||||
|
isPhotoFigcaption: false,
|
||||||
|
islazyload: false,
|
||||||
|
isAnchor: false,
|
||||||
|
percent: {
|
||||||
|
toc: true,
|
||||||
|
rightside: false,
|
||||||
|
},
|
||||||
|
autoDarkmode: false
|
||||||
|
}</script><script id="config-diff">var GLOBAL_CONFIG_SITE = {
|
||||||
|
title: 'Archives',
|
||||||
|
isPost: false,
|
||||||
|
isHome: false,
|
||||||
|
isHighlightShrink: false,
|
||||||
|
isToc: false,
|
||||||
|
postUpdate: '2024-10-09 16:07:29'
|
||||||
|
}</script><meta name="generator" content="Hexo 7.3.0"></head><body><div class="page" id="body-wrap"><header class="not-home-page" id="page-header"><nav id="nav"><span id="blog-info"><a class="nav-site-title" href="/"><span class="site-name">Hexo</span></a></span><div id="menus"></div></nav><div id="page-site-info"><h1 id="site-title">Archives</h1></div></header><main class="layout" id="content-inner"><div id="archive"><div class="article-sort-title">All Articles - 1</div><div class="article-sort"><div class="article-sort-item year">2024</div><div class="article-sort-item no-article-cover"><div class="article-sort-item-info"><div class="article-sort-item-time"><i class="far fa-calendar-alt"></i><time class="post-meta-date-created" datetime="2024-10-09T16:07:19.760Z" title="Created 2024-10-09 16:07:19">2024-10-09</time></div><a class="article-sort-item-title" href="/2024/10/09/hello-world/" title="Hello World">Hello World</a></div></div></div><nav id="pagination"><div class="pagination"><span class="page-number current">1</span></div></nav></div><div class="aside-content" id="aside-content"><div class="card-widget card-info is-center"><div class="avatar-img"><img src="/img/butterfly-icon.png" onerror="this.onerror=null;this.src='/img/friend_404.gif'" alt="avatar"/></div><div class="author-info-name">John Doe</div><div class="author-info-description"></div><div class="site-data"><a href="/archives/"><div class="headline">Articles</div><div class="length-num">1</div></a><a href="/tags/"><div class="headline">Tags</div><div class="length-num">0</div></a><a href="/categories/"><div class="headline">Categories</div><div class="length-num">0</div></a></div><a id="card-info-btn" target="_blank" rel="noopener" href="https://github.com/xxxxxx"><i class="fab fa-github"></i><span>Follow Me</span></a></div><div class="card-widget card-announcement"><div class="item-headline"><i class="fas fa-bullhorn fa-shake"></i><span>Announcement</span></div><div class="announcement_content">This is my Blog</div></div><div class="sticky_layout"><div class="card-widget card-recent-post"><div class="item-headline"><i class="fas fa-history"></i><span>Recent Posts</span></div><div class="aside-list"><div class="aside-list-item no-cover"><div class="content"><a class="title" href="/2024/10/09/hello-world/" title="Hello World">Hello World</a><time datetime="2024-10-09T16:07:19.760Z" title="Created 2024-10-09 16:07:19">2024-10-09</time></div></div></div></div><div class="card-widget card-archives">
|
||||||
|
<div class="item-headline">
|
||||||
|
<i class="fas fa-archive"></i>
|
||||||
|
<span>Archives</span>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<ul class="card-archive-list">
|
||||||
|
|
||||||
|
<li class="card-archive-list-item">
|
||||||
|
<a class="card-archive-list-link" href="/archives/2024/10/">
|
||||||
|
<span class="card-archive-list-date">October 2024</span>
|
||||||
|
<span class="card-archive-list-count">1</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul></div><div class="card-widget card-webinfo"><div class="item-headline"><i class="fas fa-chart-line"></i><span>Website Info</span></div><div class="webinfo"><div class="webinfo-item"><div class="item-name">Article Count :</div><div class="item-count">1</div></div><div class="webinfo-item"><div class="item-name">Unique Visitors :</div><div class="item-count" id="busuanzi_value_site_uv"><i class="fa-solid fa-spinner fa-spin"></i></div></div><div class="webinfo-item"><div class="item-name">Page Views :</div><div class="item-count" id="busuanzi_value_site_pv"><i class="fa-solid fa-spinner fa-spin"></i></div></div><div class="webinfo-item"><div class="item-name">Last Update :</div><div class="item-count" id="last-push-date" data-lastPushDate="2024-10-09T16:07:29.012Z"><i class="fa-solid fa-spinner fa-spin"></i></div></div></div></div></div></div></main><footer id="footer"><div id="footer-wrap"><div class="copyright">©2019 - 2024 By John Doe</div><div class="framework-info"><span>Framework </span><a target="_blank" rel="noopener" href="https://hexo.io">Hexo</a><span class="footer-separator">|</span><span>Theme </span><a target="_blank" rel="noopener" href="https://github.com/jerryc127/hexo-theme-butterfly">Butterfly</a></div></div></footer></div><div id="rightside"><div id="rightside-config-hide"><button id="darkmode" type="button" title="Toggle Between Light and Dark Mode"><i class="fas fa-adjust"></i></button><button id="hide-aside-btn" type="button" title="Toggle Between Single-column and Double-column"><i class="fas fa-arrows-alt-h"></i></button></div><div id="rightside-config-show"><button id="rightside-config" type="button" title="Settings"><i class="fas fa-cog fa-spin"></i></button><button id="go-up" type="button" title="Back to Top"><span class="scroll-percent"></span><i class="fas fa-arrow-up"></i></button></div></div><div><script src="/js/utils.js"></script><script src="/js/main.js"></script><div class="js-pjax"></div><script async data-pjax src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script></div></body></html>
|
||||||
6129
css/index.css
Normal file
6129
css/index.css
Normal file
File diff suppressed because it is too large
Load Diff
0
css/var.css
Normal file
0
css/var.css
Normal file
BIN
img/404.jpg
Normal file
BIN
img/404.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
BIN
img/butterfly-icon.png
Normal file
BIN
img/butterfly-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 269 KiB |
BIN
img/error-page.png
Normal file
BIN
img/error-page.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
BIN
img/favicon.ico
Normal file
BIN
img/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
BIN
img/friend_404.gif
Normal file
BIN
img/friend_404.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 64 KiB |
160
index.html
160
index.html
@ -0,0 +1,160 @@
|
|||||||
|
<!DOCTYPE html><html lang="en" data-theme="light"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0,viewport-fit=cover"><title>Hexo</title><meta name="author" content="John Doe"><meta name="copyright" content="John Doe"><meta name="format-detection" content="telephone=no"><meta name="theme-color" content="#ffffff"><meta property="og:type" content="website">
|
||||||
|
<meta property="og:title" content="Hexo">
|
||||||
|
<meta property="og:url" content="https://blog.linloir.cn/">
|
||||||
|
<meta property="og:site_name" content="Hexo">
|
||||||
|
<meta property="og:locale" content="en_US">
|
||||||
|
<meta property="og:image" content="https://blog.linloir.cn/img/butterfly-icon.png">
|
||||||
|
<meta property="article:author" content="John Doe">
|
||||||
|
<meta name="twitter:card" content="summary">
|
||||||
|
<meta name="twitter:image" content="https://blog.linloir.cn/img/butterfly-icon.png"><link rel="shortcut icon" href="/img/favicon.png"><link rel="canonical" href="https://blog.linloir.cn/"><link rel="preconnect" href="//cdn.jsdelivr.net"/><link rel="preconnect" href="//busuanzi.ibruce.info"/><link rel="stylesheet" href="/css/index.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free/css/all.min.css"><script>
|
||||||
|
(() => {
|
||||||
|
|
||||||
|
const saveToLocal = {
|
||||||
|
set: (key, value, ttl) => {
|
||||||
|
if (!ttl) return
|
||||||
|
const expiry = Date.now() + ttl * 86400000
|
||||||
|
localStorage.setItem(key, JSON.stringify({ value, expiry }))
|
||||||
|
},
|
||||||
|
get: key => {
|
||||||
|
const itemStr = localStorage.getItem(key)
|
||||||
|
if (!itemStr) return undefined
|
||||||
|
const { value, expiry } = JSON.parse(itemStr)
|
||||||
|
if (Date.now() > expiry) {
|
||||||
|
localStorage.removeItem(key)
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.btf = {
|
||||||
|
saveToLocal,
|
||||||
|
getScript: (url, attr = {}) => new Promise((resolve, reject) => {
|
||||||
|
const script = document.createElement('script')
|
||||||
|
script.src = url
|
||||||
|
script.async = true
|
||||||
|
Object.entries(attr).forEach(([key, val]) => script.setAttribute(key, val))
|
||||||
|
script.onload = script.onreadystatechange = () => {
|
||||||
|
if (!script.readyState || /loaded|complete/.test(script.readyState)) resolve()
|
||||||
|
}
|
||||||
|
script.onerror = reject
|
||||||
|
document.head.appendChild(script)
|
||||||
|
}),
|
||||||
|
getCSS: (url, id) => new Promise((resolve, reject) => {
|
||||||
|
const link = document.createElement('link')
|
||||||
|
link.rel = 'stylesheet'
|
||||||
|
link.href = url
|
||||||
|
if (id) link.id = id
|
||||||
|
link.onload = link.onreadystatechange = () => {
|
||||||
|
if (!link.readyState || /loaded|complete/.test(link.readyState)) resolve()
|
||||||
|
}
|
||||||
|
link.onerror = reject
|
||||||
|
document.head.appendChild(link)
|
||||||
|
}),
|
||||||
|
addGlobalFn: (key, fn, name = false, parent = window) => {
|
||||||
|
if (!false && key.startsWith('pjax')) return
|
||||||
|
const globalFn = parent.globalFn || {}
|
||||||
|
globalFn[key] = globalFn[key] || {}
|
||||||
|
if (name && globalFn[key][name]) return
|
||||||
|
globalFn[key][name || Object.keys(globalFn[key]).length] = fn
|
||||||
|
parent.globalFn = globalFn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const activateDarkMode = () => {
|
||||||
|
document.documentElement.setAttribute('data-theme', 'dark')
|
||||||
|
if (document.querySelector('meta[name="theme-color"]') !== null) {
|
||||||
|
document.querySelector('meta[name="theme-color"]').setAttribute('content', '#0d0d0d')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const activateLightMode = () => {
|
||||||
|
document.documentElement.setAttribute('data-theme', 'light')
|
||||||
|
if (document.querySelector('meta[name="theme-color"]') !== null) {
|
||||||
|
document.querySelector('meta[name="theme-color"]').setAttribute('content', '#ffffff')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
btf.activateDarkMode = activateDarkMode
|
||||||
|
btf.activateLightMode = activateLightMode
|
||||||
|
|
||||||
|
const theme = saveToLocal.get('theme')
|
||||||
|
|
||||||
|
theme === 'dark' ? activateDarkMode() : theme === 'light' ? activateLightMode() : null
|
||||||
|
|
||||||
|
|
||||||
|
const asideStatus = saveToLocal.get('aside-status')
|
||||||
|
if (asideStatus !== undefined) {
|
||||||
|
document.documentElement.classList.toggle('hide-aside', asideStatus === 'hide')
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const detectApple = () => {
|
||||||
|
if (/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)) {
|
||||||
|
document.documentElement.classList.add('apple')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
detectApple()
|
||||||
|
|
||||||
|
})()
|
||||||
|
</script><script>const GLOBAL_CONFIG = {
|
||||||
|
root: '/',
|
||||||
|
algolia: undefined,
|
||||||
|
localSearch: undefined,
|
||||||
|
translate: undefined,
|
||||||
|
noticeOutdate: undefined,
|
||||||
|
highlight: {"plugin":"highlight.js","highlightCopy":true,"highlightLang":true,"highlightHeightLimit":false,"highlightFullpage":false,"highlightMacStyle":false},
|
||||||
|
copy: {
|
||||||
|
success: 'Copy Successful',
|
||||||
|
error: 'Copy Failed',
|
||||||
|
noSupport: 'Browser Not Supported'
|
||||||
|
},
|
||||||
|
relativeDate: {
|
||||||
|
homepage: false,
|
||||||
|
post: false
|
||||||
|
},
|
||||||
|
runtime: '',
|
||||||
|
dateSuffix: {
|
||||||
|
just: 'Just now',
|
||||||
|
min: 'minutes ago',
|
||||||
|
hour: 'hours ago',
|
||||||
|
day: 'days ago',
|
||||||
|
month: 'months ago'
|
||||||
|
},
|
||||||
|
copyright: undefined,
|
||||||
|
lightbox: 'null',
|
||||||
|
Snackbar: undefined,
|
||||||
|
infinitegrid: {
|
||||||
|
js: 'https://cdn.jsdelivr.net/npm/@egjs/infinitegrid/dist/infinitegrid.min.js',
|
||||||
|
buttonText: 'Load More'
|
||||||
|
},
|
||||||
|
isPhotoFigcaption: false,
|
||||||
|
islazyload: false,
|
||||||
|
isAnchor: false,
|
||||||
|
percent: {
|
||||||
|
toc: true,
|
||||||
|
rightside: false,
|
||||||
|
},
|
||||||
|
autoDarkmode: false
|
||||||
|
}</script><script id="config-diff">var GLOBAL_CONFIG_SITE = {
|
||||||
|
title: 'Hexo',
|
||||||
|
isPost: false,
|
||||||
|
isHome: true,
|
||||||
|
isHighlightShrink: false,
|
||||||
|
isToc: false,
|
||||||
|
postUpdate: '2024-10-09 16:07:29'
|
||||||
|
}</script><meta name="generator" content="Hexo 7.3.0"></head><body><div class="page" id="body-wrap"><header class="full_page" id="page-header"><nav id="nav"><span id="blog-info"><a class="nav-site-title" href="/"><span class="site-name">Hexo</span></a></span><div id="menus"></div></nav><div id="site-info"><h1 id="site-title">Hexo</h1></div><div id="scroll-down"><i class="fas fa-angle-down scroll-down-effects"></i></div></header><main class="layout" id="content-inner"><div class="recent-posts nc" id="recent-posts"><div class="recent-post-items"><div class="recent-post-item"><div class="recent-post-info no-cover"><a class="article-title" href="/2024/10/09/hello-world/" title="Hello World">Hello World</a><div class="article-meta-wrap"><span class="post-meta-date"><i class="far fa-calendar-alt"></i><span class="article-meta-label">Created</span><time datetime="2024-10-09T16:07:19.760Z" title="Created 2024-10-09 16:07:19">2024-10-09</time></span></div><div class="content">Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub. Quick StartCreate a new post1$ hexo new "My New Post" More info: Writing Run server1$ hexo server More info: Server Generate static files1$ hexo generate More info: Generating Deploy to remote sites1$ hexo deploy More info: Deployment </div></div></div></div><nav id="pagination"><div class="pagination"><span class="page-number current">1</span></div></nav></div><div class="aside-content" id="aside-content"><div class="card-widget card-info is-center"><div class="avatar-img"><img src="/img/butterfly-icon.png" onerror="this.onerror=null;this.src='/img/friend_404.gif'" alt="avatar"/></div><div class="author-info-name">John Doe</div><div class="author-info-description"></div><div class="site-data"><a href="/archives/"><div class="headline">Articles</div><div class="length-num">1</div></a><a href="/tags/"><div class="headline">Tags</div><div class="length-num">0</div></a><a href="/categories/"><div class="headline">Categories</div><div class="length-num">0</div></a></div><a id="card-info-btn" target="_blank" rel="noopener" href="https://github.com/xxxxxx"><i class="fab fa-github"></i><span>Follow Me</span></a></div><div class="card-widget card-announcement"><div class="item-headline"><i class="fas fa-bullhorn fa-shake"></i><span>Announcement</span></div><div class="announcement_content">This is my Blog</div></div><div class="sticky_layout"><div class="card-widget card-recent-post"><div class="item-headline"><i class="fas fa-history"></i><span>Recent Posts</span></div><div class="aside-list"><div class="aside-list-item no-cover"><div class="content"><a class="title" href="/2024/10/09/hello-world/" title="Hello World">Hello World</a><time datetime="2024-10-09T16:07:19.760Z" title="Created 2024-10-09 16:07:19">2024-10-09</time></div></div></div></div><div class="card-widget card-archives">
|
||||||
|
<div class="item-headline">
|
||||||
|
<i class="fas fa-archive"></i>
|
||||||
|
<span>Archives</span>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<ul class="card-archive-list">
|
||||||
|
|
||||||
|
<li class="card-archive-list-item">
|
||||||
|
<a class="card-archive-list-link" href="/archives/2024/10/">
|
||||||
|
<span class="card-archive-list-date">October 2024</span>
|
||||||
|
<span class="card-archive-list-count">1</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul></div><div class="card-widget card-webinfo"><div class="item-headline"><i class="fas fa-chart-line"></i><span>Website Info</span></div><div class="webinfo"><div class="webinfo-item"><div class="item-name">Article Count :</div><div class="item-count">1</div></div><div class="webinfo-item"><div class="item-name">Unique Visitors :</div><div class="item-count" id="busuanzi_value_site_uv"><i class="fa-solid fa-spinner fa-spin"></i></div></div><div class="webinfo-item"><div class="item-name">Page Views :</div><div class="item-count" id="busuanzi_value_site_pv"><i class="fa-solid fa-spinner fa-spin"></i></div></div><div class="webinfo-item"><div class="item-name">Last Update :</div><div class="item-count" id="last-push-date" data-lastPushDate="2024-10-09T16:07:29.012Z"><i class="fa-solid fa-spinner fa-spin"></i></div></div></div></div></div></div></main><footer id="footer"><div id="footer-wrap"><div class="copyright">©2019 - 2024 By John Doe</div><div class="framework-info"><span>Framework </span><a target="_blank" rel="noopener" href="https://hexo.io">Hexo</a><span class="footer-separator">|</span><span>Theme </span><a target="_blank" rel="noopener" href="https://github.com/jerryc127/hexo-theme-butterfly">Butterfly</a></div></div></footer></div><div id="rightside"><div id="rightside-config-hide"><button id="darkmode" type="button" title="Toggle Between Light and Dark Mode"><i class="fas fa-adjust"></i></button><button id="hide-aside-btn" type="button" title="Toggle Between Single-column and Double-column"><i class="fas fa-arrows-alt-h"></i></button></div><div id="rightside-config-show"><button id="rightside-config" type="button" title="Settings"><i class="fas fa-cog fa-spin"></i></button><button id="go-up" type="button" title="Back to Top"><span class="scroll-percent"></span><i class="fas fa-arrow-up"></i></button></div></div><div><script src="/js/utils.js"></script><script src="/js/main.js"></script><div class="js-pjax"></div><script async data-pjax src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script></div></body></html>
|
||||||
943
js/main.js
Normal file
943
js/main.js
Normal file
@ -0,0 +1,943 @@
|
|||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
let headerContentWidth, $nav
|
||||||
|
let mobileSidebarOpen = false
|
||||||
|
|
||||||
|
const adjustMenu = init => {
|
||||||
|
const getAllWidth = ele => Array.from(ele).reduce((width, i) => width + i.offsetWidth, 0)
|
||||||
|
|
||||||
|
if (init) {
|
||||||
|
const blogInfoWidth = getAllWidth(document.querySelector('#blog-info > a').children)
|
||||||
|
const menusWidth = getAllWidth(document.getElementById('menus').children)
|
||||||
|
headerContentWidth = blogInfoWidth + menusWidth
|
||||||
|
$nav = document.getElementById('nav')
|
||||||
|
}
|
||||||
|
|
||||||
|
const hideMenuIndex = window.innerWidth <= 768 || headerContentWidth > $nav.offsetWidth - 120
|
||||||
|
$nav.classList.toggle('hide-menu', hideMenuIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化header
|
||||||
|
const initAdjust = () => {
|
||||||
|
adjustMenu(true)
|
||||||
|
$nav.classList.add('show')
|
||||||
|
}
|
||||||
|
|
||||||
|
// sidebar menus
|
||||||
|
const sidebarFn = {
|
||||||
|
open: () => {
|
||||||
|
btf.overflowPaddingR.add()
|
||||||
|
btf.animateIn(document.getElementById('menu-mask'), 'to_show 0.5s')
|
||||||
|
document.getElementById('sidebar-menus').classList.add('open')
|
||||||
|
mobileSidebarOpen = true
|
||||||
|
},
|
||||||
|
close: () => {
|
||||||
|
btf.overflowPaddingR.remove()
|
||||||
|
btf.animateOut(document.getElementById('menu-mask'), 'to_hide 0.5s')
|
||||||
|
document.getElementById('sidebar-menus').classList.remove('open')
|
||||||
|
mobileSidebarOpen = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 首頁top_img底下的箭頭
|
||||||
|
*/
|
||||||
|
const scrollDownInIndex = () => {
|
||||||
|
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 = () => {
|
||||||
|
const highLight = GLOBAL_CONFIG.highlight
|
||||||
|
if (!highLight) return
|
||||||
|
|
||||||
|
const { highlightCopy, highlightLang, highlightHeightLimit, highlightFullpage, highlightMacStyle, plugin } = highLight
|
||||||
|
const isHighlightShrink = GLOBAL_CONFIG_SITE.isHighlightShrink
|
||||||
|
const isShowTool = highlightCopy || highlightLang || isHighlightShrink !== undefined || highlightFullpage || highlightMacStyle
|
||||||
|
const $figureHighlight = plugin === 'highlight.js' ? document.querySelectorAll('figure.highlight') : document.querySelectorAll('pre[class*="language-"]')
|
||||||
|
|
||||||
|
if (!((isShowTool || highlightHeightLimit) && $figureHighlight.length)) return
|
||||||
|
|
||||||
|
const isPrismjs = plugin === 'prismjs'
|
||||||
|
const highlightShrinkClass = isHighlightShrink === true ? 'closed' : ''
|
||||||
|
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 highlightMacStyleEle = '<div class="macStyle"><div class="mac-close"></div><div class="mac-minimize"></div><div class="mac-maximize"></div></div>'
|
||||||
|
const highlightFullpageEle = highlightFullpage ? '<i class="fa-solid fa-up-right-and-down-left-from-center fullpage-button"></i>' : ''
|
||||||
|
|
||||||
|
const alertInfo = (ele, text) => {
|
||||||
|
if (GLOBAL_CONFIG.Snackbar !== undefined) {
|
||||||
|
btf.snackbarShow(text)
|
||||||
|
} else {
|
||||||
|
ele.textContent = text
|
||||||
|
ele.style.opacity = 1
|
||||||
|
setTimeout(() => { ele.style.opacity = 0 }, 800)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const copy = async (text, ctx) => {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(text)
|
||||||
|
alertInfo(ctx, GLOBAL_CONFIG.copy.success)
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to copy: ', err)
|
||||||
|
alertInfo(ctx, GLOBAL_CONFIG.copy.noSupport)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// click events
|
||||||
|
const highlightCopyFn = (ele, clickEle) => {
|
||||||
|
const $buttonParent = ele.parentNode
|
||||||
|
$buttonParent.classList.add('copy-true')
|
||||||
|
const preCodeSelector = isPrismjs ? 'pre code' : 'table .code pre'
|
||||||
|
const codeElement = $buttonParent.querySelector(preCodeSelector)
|
||||||
|
if (!codeElement) return
|
||||||
|
copy(codeElement.innerText, clickEle.previousElementSibling)
|
||||||
|
$buttonParent.classList.remove('copy-true')
|
||||||
|
}
|
||||||
|
|
||||||
|
const highlightShrinkFn = ele => ele.classList.toggle('closed')
|
||||||
|
|
||||||
|
const codeFullpage = (item, clickEle) => {
|
||||||
|
const wrapEle = item.closest('figure.highlight')
|
||||||
|
const isFullpage = wrapEle.classList.toggle('code-fullpage')
|
||||||
|
|
||||||
|
document.body.style.overflow = isFullpage ? 'hidden' : ''
|
||||||
|
clickEle.classList.toggle('fa-down-left-and-up-right-to-center', isFullpage)
|
||||||
|
clickEle.classList.toggle('fa-up-right-and-down-left-from-center', !isFullpage)
|
||||||
|
}
|
||||||
|
|
||||||
|
const highlightToolsFn = e => {
|
||||||
|
const $target = e.target.classList
|
||||||
|
const currentElement = e.currentTarget
|
||||||
|
if ($target.contains('expand')) highlightShrinkFn(currentElement)
|
||||||
|
else if ($target.contains('copy-button')) highlightCopyFn(currentElement, e.target)
|
||||||
|
else if ($target.contains('fullpage-button')) codeFullpage(currentElement, e.target)
|
||||||
|
}
|
||||||
|
|
||||||
|
const expandCode = e => e.currentTarget.classList.toggle('expand-done')
|
||||||
|
|
||||||
|
// 獲取隱藏狀態下元素的真實高度
|
||||||
|
const getActualHeight = item => {
|
||||||
|
const hiddenElements = new Map()
|
||||||
|
|
||||||
|
const fix = () => {
|
||||||
|
let current = item
|
||||||
|
while (current !== document.body && current != null) {
|
||||||
|
if (window.getComputedStyle(current).display === 'none') {
|
||||||
|
hiddenElements.set(current, current.getAttribute('style') || '')
|
||||||
|
}
|
||||||
|
current = current.parentNode
|
||||||
|
}
|
||||||
|
|
||||||
|
const style = 'visibility: hidden !important; display: block !important;'
|
||||||
|
hiddenElements.forEach((originalStyle, elem) => {
|
||||||
|
elem.setAttribute('style', originalStyle ? originalStyle + ';' + style : style)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const restore = () => {
|
||||||
|
hiddenElements.forEach((originalStyle, elem) => {
|
||||||
|
if (originalStyle === '') elem.removeAttribute('style')
|
||||||
|
else elem.setAttribute('style', originalStyle)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fix()
|
||||||
|
const height = item.offsetHeight
|
||||||
|
restore()
|
||||||
|
return height
|
||||||
|
}
|
||||||
|
|
||||||
|
const createEle = (lang, item) => {
|
||||||
|
const fragment = document.createDocumentFragment()
|
||||||
|
|
||||||
|
if (isShowTool) {
|
||||||
|
const hlTools = document.createElement('div')
|
||||||
|
hlTools.className = `highlight-tools ${highlightShrinkClass}`
|
||||||
|
hlTools.innerHTML = highlightMacStyleEle + highlightShrinkEle + lang + highlightCopyEle + highlightFullpageEle
|
||||||
|
btf.addEventListenerPjax(hlTools, 'click', highlightToolsFn)
|
||||||
|
fragment.appendChild(hlTools)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (highlightHeightLimit && getActualHeight(item) > highlightHeightLimit + 30) {
|
||||||
|
const ele = document.createElement('div')
|
||||||
|
ele.className = 'code-expand-btn'
|
||||||
|
ele.innerHTML = '<i class="fas fa-angle-double-down"></i>'
|
||||||
|
btf.addEventListenerPjax(ele, 'click', expandCode)
|
||||||
|
fragment.appendChild(ele)
|
||||||
|
}
|
||||||
|
|
||||||
|
isPrismjs ? item.parentNode.insertBefore(fragment, item) : item.insertBefore(fragment, item.firstChild)
|
||||||
|
}
|
||||||
|
|
||||||
|
$figureHighlight.forEach(item => {
|
||||||
|
let langName = ''
|
||||||
|
if (isPrismjs) btf.wrap(item, 'figure', { class: 'highlight' })
|
||||||
|
|
||||||
|
if (!highlightLang) {
|
||||||
|
createEle('', item)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPrismjs) {
|
||||||
|
langName = item.getAttribute('data-language') || 'Code'
|
||||||
|
} else {
|
||||||
|
langName = item.getAttribute('class').split(' ')[1]
|
||||||
|
if (langName === 'plain' || langName === undefined) langName = 'Code'
|
||||||
|
}
|
||||||
|
createEle(`<div class="code-lang">${langName}</div>`, item)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PhotoFigcaption
|
||||||
|
*/
|
||||||
|
const addPhotoFigcaption = () => {
|
||||||
|
if (!GLOBAL_CONFIG.isPhotoFigcaption) return
|
||||||
|
document.querySelectorAll('#article-container img').forEach(item => {
|
||||||
|
const altValue = item.title || item.alt
|
||||||
|
if (!altValue) return
|
||||||
|
const ele = document.createElement('div')
|
||||||
|
ele.className = 'img-alt is-center'
|
||||||
|
ele.textContent = altValue
|
||||||
|
item.insertAdjacentElement('afterend', ele)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lightbox
|
||||||
|
*/
|
||||||
|
const runLightbox = () => {
|
||||||
|
btf.loadLightbox(document.querySelectorAll('#article-container img:not(.no-lightbox)'))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* justified-gallery 圖庫排版
|
||||||
|
*/
|
||||||
|
|
||||||
|
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
|
||||||
|
})
|
||||||
|
|
||||||
|
const replaceDq = str => str.replace(/"/g, '"') // replace double quotes to "
|
||||||
|
|
||||||
|
const getItems = (nextGroupKey, count) => {
|
||||||
|
const nextItems = []
|
||||||
|
const startCount = (nextGroupKey - 1) * count
|
||||||
|
|
||||||
|
for (let i = 0; i < count; ++i) {
|
||||||
|
const num = startCount + i
|
||||||
|
if (num >= dataLength) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
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>`)
|
||||||
|
}
|
||||||
|
return nextItems
|
||||||
|
}
|
||||||
|
|
||||||
|
const buttonText = GLOBAL_CONFIG.infinitegrid.buttonText
|
||||||
|
const addButton = item => {
|
||||||
|
const button = document.createElement('button')
|
||||||
|
button.innerHTML = buttonText + '<i class="fa-solid fa-arrow-down"></i>'
|
||||||
|
|
||||||
|
button.addEventListener('click', e => {
|
||||||
|
e.target.closest('button').remove()
|
||||||
|
btf.setLoading.add(item)
|
||||||
|
appendItem(ig.getGroups().length + 1, 10)
|
||||||
|
}, { once: true })
|
||||||
|
|
||||||
|
item.insertAdjacentElement('afterend', button)
|
||||||
|
}
|
||||||
|
|
||||||
|
const appendItem = (nextGroupKey, count) => {
|
||||||
|
ig.append(getItems(nextGroupKey, count), nextGroupKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxGroupKey = Math.ceil(dataLength / 10)
|
||||||
|
let isLayoutHidden = false
|
||||||
|
|
||||||
|
const completeFn = e => {
|
||||||
|
if (tabs) {
|
||||||
|
const parentNode = item.parentNode
|
||||||
|
|
||||||
|
if (isLayoutHidden) {
|
||||||
|
parentNode.style.visibility = 'visible'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.offsetHeight === 0) {
|
||||||
|
parentNode.style.visibility = 'hidden'
|
||||||
|
isLayoutHidden = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
!tabs && 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('pjaxSendOnce', () => { ig.destroy() })
|
||||||
|
}
|
||||||
|
|
||||||
|
const addJustifiedGallery = async (ele, tabs = false) => {
|
||||||
|
if (!ele.length) return
|
||||||
|
const init = async () => {
|
||||||
|
for (const item of ele) {
|
||||||
|
if (btf.isHidden(item) || item.classList.contains('loaded')) continue
|
||||||
|
|
||||||
|
const isButton = item.getAttribute('data-button') === 'true'
|
||||||
|
const children = item.firstElementChild
|
||||||
|
const text = children.textContent
|
||||||
|
children.textContent = ''
|
||||||
|
item.classList.add('loaded')
|
||||||
|
try {
|
||||||
|
const content = item.getAttribute('data-type') === 'url' ? await fetchUrl(text) : JSON.parse(text)
|
||||||
|
runJustifiedGallery(children, content, isButton, tabs)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Gallery data parsing failed:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof InfiniteGrid === 'function') {
|
||||||
|
init()
|
||||||
|
} else {
|
||||||
|
await btf.getScript(`${GLOBAL_CONFIG.infinitegrid.js}`)
|
||||||
|
init()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* rightside scroll percent
|
||||||
|
*/
|
||||||
|
const rightsideScrollPercent = currentTop => {
|
||||||
|
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 {
|
||||||
|
goUpElement.classList.remove('show-percent')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 滾動處理
|
||||||
|
*/
|
||||||
|
const scrollFn = () => {
|
||||||
|
const $rightside = document.getElementById('rightside')
|
||||||
|
const innerHeight = window.innerHeight + 56
|
||||||
|
let initTop = 0
|
||||||
|
const $header = document.getElementById('page-header')
|
||||||
|
const isChatBtn = typeof chatBtn !== 'undefined'
|
||||||
|
const isShowPercent = GLOBAL_CONFIG.percent.rightside
|
||||||
|
|
||||||
|
// 檢查文檔高度是否小於視窗高度
|
||||||
|
const checkDocumentHeight = () => {
|
||||||
|
if (document.body.scrollHeight <= innerHeight) {
|
||||||
|
$rightside.classList.add('rightside-show')
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果文檔高度小於視窗高度,直接返回
|
||||||
|
if (checkDocumentHeight()) return
|
||||||
|
|
||||||
|
// find the scroll direction
|
||||||
|
const scrollDirection = currentTop => {
|
||||||
|
const result = currentTop > initTop // true is down & false is up
|
||||||
|
initTop = currentTop
|
||||||
|
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 (flag !== 'down') {
|
||||||
|
$header.classList.remove('nav-visible')
|
||||||
|
isChatBtn && window.chatBtn.hide()
|
||||||
|
flag = 'down'
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (flag !== 'up') {
|
||||||
|
$header.classList.add('nav-visible')
|
||||||
|
isChatBtn && window.chatBtn.show()
|
||||||
|
flag = 'up'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
flag = ''
|
||||||
|
if (currentTop === 0) {
|
||||||
|
$header.classList.remove('nav-fixed', 'nav-visible')
|
||||||
|
}
|
||||||
|
$rightside.classList.remove('rightside-show')
|
||||||
|
}
|
||||||
|
|
||||||
|
isShowPercent && rightsideScrollPercent(currentTop)
|
||||||
|
checkDocumentHeight()
|
||||||
|
}, 300)
|
||||||
|
|
||||||
|
btf.addEventListenerPjax(window, 'scroll', scrollTask, { passive: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* toc,anchor
|
||||||
|
*/
|
||||||
|
const scrollFnToDo = () => {
|
||||||
|
const isToc = GLOBAL_CONFIG_SITE.isToc
|
||||||
|
const isAnchor = GLOBAL_CONFIG.isAnchor
|
||||||
|
const $article = document.getElementById('article-container')
|
||||||
|
|
||||||
|
if (!($article && (isToc || isAnchor))) return
|
||||||
|
|
||||||
|
let $tocLink, $cardToc, autoScrollToc, $tocPercentage, isExpand
|
||||||
|
|
||||||
|
if (isToc) {
|
||||||
|
const $cardTocLayout = document.getElementById('card-toc')
|
||||||
|
$cardToc = $cardTocLayout.querySelector('.toc-content')
|
||||||
|
$tocLink = $cardToc.querySelectorAll('.toc-link')
|
||||||
|
$tocPercentage = $cardTocLayout.querySelector('.toc-percentage')
|
||||||
|
isExpand = $cardToc.classList.contains('is-expand')
|
||||||
|
|
||||||
|
// toc元素點擊
|
||||||
|
const tocItemClickFn = e => {
|
||||||
|
const target = e.target.closest('.toc-link')
|
||||||
|
if (!target) return
|
||||||
|
|
||||||
|
e.preventDefault()
|
||||||
|
btf.scrollToDest(btf.getEleTop(document.getElementById(decodeURI(target.getAttribute('href')).replace('#', ''))), 300)
|
||||||
|
if (window.innerWidth < 900) {
|
||||||
|
$cardTocLayout.classList.remove('open')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
btf.addEventListenerPjax($cardToc, 'click', tocItemClickFn)
|
||||||
|
|
||||||
|
autoScrollToc = item => {
|
||||||
|
const sidebarHeight = $cardToc.clientHeight
|
||||||
|
const itemOffsetTop = item.offsetTop
|
||||||
|
const itemHeight = item.clientHeight
|
||||||
|
const scrollTop = $cardToc.scrollTop
|
||||||
|
const offset = itemOffsetTop - scrollTop
|
||||||
|
const middlePosition = (sidebarHeight - itemHeight) / 2
|
||||||
|
|
||||||
|
if (offset !== middlePosition) {
|
||||||
|
$cardToc.scrollTop = scrollTop + (offset - middlePosition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 處理 hexo-blog-encrypt 事件
|
||||||
|
$cardToc.style.display = 'block'
|
||||||
|
}
|
||||||
|
|
||||||
|
// find head position & add active class
|
||||||
|
const $articleList = $article.querySelectorAll('h1,h2,h3,h4,h5,h6')
|
||||||
|
let detectItem = ''
|
||||||
|
|
||||||
|
const findHeadPosition = top => {
|
||||||
|
if (top === 0) return false
|
||||||
|
|
||||||
|
let currentId = ''
|
||||||
|
let currentIndex = ''
|
||||||
|
|
||||||
|
for (let i = 0; i < $articleList.length; i++) {
|
||||||
|
const ele = $articleList[i]
|
||||||
|
if (top > btf.getEleTop(ele) - 80) {
|
||||||
|
const id = ele.id
|
||||||
|
currentId = id ? '#' + encodeURI(id) : ''
|
||||||
|
currentIndex = i
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (detectItem === currentIndex) return
|
||||||
|
|
||||||
|
if (isAnchor) btf.updateAnchor(currentId)
|
||||||
|
|
||||||
|
detectItem = currentIndex
|
||||||
|
|
||||||
|
if (isToc) {
|
||||||
|
$cardToc.querySelectorAll('.active').forEach(i => i.classList.remove('active'))
|
||||||
|
|
||||||
|
if (currentId) {
|
||||||
|
const currentActive = $tocLink[currentIndex]
|
||||||
|
currentActive.classList.add('active')
|
||||||
|
|
||||||
|
setTimeout(() => autoScrollToc(currentActive), 0)
|
||||||
|
|
||||||
|
if (!isExpand) {
|
||||||
|
let parent = currentActive.parentNode
|
||||||
|
while (!parent.matches('.toc')) {
|
||||||
|
if (parent.matches('li')) parent.classList.add('active')
|
||||||
|
parent = parent.parentNode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// main of scroll
|
||||||
|
const tocScrollFn = btf.throttle(() => {
|
||||||
|
const currentTop = window.scrollY || document.documentElement.scrollTop
|
||||||
|
if (isToc && GLOBAL_CONFIG.percent.toc) {
|
||||||
|
$tocPercentage.textContent = btf.getScrollPercent(currentTop, $article)
|
||||||
|
}
|
||||||
|
findHeadPosition(currentTop)
|
||||||
|
}, 100)
|
||||||
|
|
||||||
|
btf.addEventListenerPjax(window, 'scroll', tocScrollFn, { passive: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleThemeChange = mode => {
|
||||||
|
const globalFn = window.globalFn || {}
|
||||||
|
const themeChange = globalFn.themeChange || {}
|
||||||
|
if (!themeChange) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.keys(themeChange).forEach(key => {
|
||||||
|
const themeChangeFn = themeChange[key]
|
||||||
|
if (['disqus', 'disqusjs'].includes(key)) {
|
||||||
|
setTimeout(() => themeChangeFn(mode), 300)
|
||||||
|
} else {
|
||||||
|
themeChangeFn(mode)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rightside
|
||||||
|
*/
|
||||||
|
const rightSideFn = {
|
||||||
|
readmode: () => { // read mode
|
||||||
|
const $body = document.body
|
||||||
|
const newEle = document.createElement('button')
|
||||||
|
|
||||||
|
const exitReadMode = () => {
|
||||||
|
$body.classList.remove('read-mode')
|
||||||
|
newEle.remove()
|
||||||
|
newEle.removeEventListener('click', exitReadMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
$body.classList.add('read-mode')
|
||||||
|
newEle.type = 'button'
|
||||||
|
newEle.className = 'fas fa-sign-out-alt exit-readmode'
|
||||||
|
newEle.addEventListener('click', exitReadMode)
|
||||||
|
$body.appendChild(newEle)
|
||||||
|
},
|
||||||
|
darkmode: () => { // switch between light and dark mode
|
||||||
|
const willChangeMode = document.documentElement.getAttribute('data-theme') === 'dark' ? 'light' : 'dark'
|
||||||
|
if (willChangeMode === 'dark') {
|
||||||
|
btf.activateDarkMode()
|
||||||
|
GLOBAL_CONFIG.Snackbar !== undefined && btf.snackbarShow(GLOBAL_CONFIG.Snackbar.day_to_night)
|
||||||
|
} else {
|
||||||
|
btf.activateLightMode()
|
||||||
|
GLOBAL_CONFIG.Snackbar !== undefined && btf.snackbarShow(GLOBAL_CONFIG.Snackbar.night_to_day)
|
||||||
|
}
|
||||||
|
btf.saveToLocal.set('theme', willChangeMode, 2)
|
||||||
|
handleThemeChange(willChangeMode)
|
||||||
|
},
|
||||||
|
'rightside-config': item => { // Show or hide rightside-hide-btn
|
||||||
|
const hideLayout = item.firstElementChild
|
||||||
|
if (hideLayout.classList.contains('show')) {
|
||||||
|
hideLayout.classList.add('status')
|
||||||
|
setTimeout(() => {
|
||||||
|
hideLayout.classList.remove('status')
|
||||||
|
}, 300)
|
||||||
|
}
|
||||||
|
|
||||||
|
hideLayout.classList.toggle('show')
|
||||||
|
},
|
||||||
|
'go-up': () => { // Back to top
|
||||||
|
btf.scrollToDest(0, 500)
|
||||||
|
},
|
||||||
|
'hide-aside-btn': () => { // Hide aside
|
||||||
|
const $htmlDom = document.documentElement.classList
|
||||||
|
const saveStatus = $htmlDom.contains('hide-aside') ? 'show' : 'hide'
|
||||||
|
btf.saveToLocal.set('aside-status', saveStatus, 2)
|
||||||
|
$htmlDom.toggle('hide-aside')
|
||||||
|
},
|
||||||
|
'mobile-toc-button': (p, item) => { // Show mobile toc
|
||||||
|
const tocEle = document.getElementById('card-toc')
|
||||||
|
tocEle.style.transition = 'transform 0.3s ease-in-out'
|
||||||
|
|
||||||
|
const tocEleHeight = tocEle.clientHeight
|
||||||
|
const btData = item.getBoundingClientRect()
|
||||||
|
|
||||||
|
const tocEleBottom = window.innerHeight - btData.bottom - 30
|
||||||
|
if (tocEleHeight > tocEleBottom) {
|
||||||
|
tocEle.style.transformOrigin = `right ${tocEleHeight - tocEleBottom - btData.height / 2}px`
|
||||||
|
}
|
||||||
|
|
||||||
|
tocEle.classList.toggle('open')
|
||||||
|
tocEle.addEventListener('transitionend', () => {
|
||||||
|
tocEle.style.cssText = ''
|
||||||
|
}, { once: true })
|
||||||
|
},
|
||||||
|
'chat-btn': () => { // Show chat
|
||||||
|
window.chatBtnFn()
|
||||||
|
},
|
||||||
|
translateLink: () => { // switch between traditional and simplified chinese
|
||||||
|
window.translateFn.translatePage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('rightside').addEventListener('click', e => {
|
||||||
|
const $target = e.target.closest('[id]')
|
||||||
|
if ($target && rightSideFn[$target.id]) {
|
||||||
|
rightSideFn[$target.id](e.currentTarget, $target)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* menu
|
||||||
|
* 側邊欄sub-menu 展開/收縮
|
||||||
|
*/
|
||||||
|
const clickFnOfSubMenu = () => {
|
||||||
|
const handleClickOfSubMenu = e => {
|
||||||
|
const target = e.target.closest('.site-page.group')
|
||||||
|
if (!target) return
|
||||||
|
target.classList.toggle('hide')
|
||||||
|
}
|
||||||
|
|
||||||
|
const menusItems = document.querySelector('#sidebar-menus .menus_items')
|
||||||
|
menusItems && menusItems.addEventListener('click', handleClickOfSubMenu)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 手机端目录点击
|
||||||
|
*/
|
||||||
|
const openMobileMenu = () => {
|
||||||
|
const toggleMenu = document.getElementById('toggle-menu')
|
||||||
|
if (!toggleMenu) return
|
||||||
|
btf.addEventListenerPjax(toggleMenu, 'click', () => { sidebarFn.open() })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 複製時加上版權信息
|
||||||
|
*/
|
||||||
|
const addCopyright = () => {
|
||||||
|
const { limitCount, languages } = GLOBAL_CONFIG.copyright
|
||||||
|
|
||||||
|
const handleCopy = (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
const copyFont = window.getSelection(0).toString()
|
||||||
|
let textFont = copyFont
|
||||||
|
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)
|
||||||
|
} else {
|
||||||
|
return window.clipboardData.setData('text', textFont)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.body.addEventListener('copy', handleCopy)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 網頁運行時間
|
||||||
|
*/
|
||||||
|
const addRuntime = () => {
|
||||||
|
const $runtimeCount = document.getElementById('runtimeshow')
|
||||||
|
if ($runtimeCount) {
|
||||||
|
const publishDate = $runtimeCount.getAttribute('data-publishDate')
|
||||||
|
$runtimeCount.textContent = `${btf.diffDate(publishDate)} ${GLOBAL_CONFIG.runtime}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最後一次更新時間
|
||||||
|
*/
|
||||||
|
const addLastPushDate = () => {
|
||||||
|
const $lastPushDateItem = document.getElementById('last-push-date')
|
||||||
|
if ($lastPushDateItem) {
|
||||||
|
const lastPushDate = $lastPushDateItem.getAttribute('data-lastPushDate')
|
||||||
|
$lastPushDateItem.textContent = btf.diffDate(lastPushDate, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* table overflow
|
||||||
|
*/
|
||||||
|
const addTableWrap = () => {
|
||||||
|
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 = () => {
|
||||||
|
const hideButtons = document.querySelectorAll('#article-container .hide-button')
|
||||||
|
if (!hideButtons.length) return
|
||||||
|
hideButtons.forEach(item => item.addEventListener('click', e => {
|
||||||
|
const currentTarget = e.currentTarget
|
||||||
|
currentTarget.classList.add('open')
|
||||||
|
addJustifiedGallery(currentTarget.nextElementSibling.querySelectorAll('.gallery-container'))
|
||||||
|
}, { once: true }))
|
||||||
|
}
|
||||||
|
|
||||||
|
const tabsFn = () => {
|
||||||
|
const navTabsElements = document.querySelectorAll('#article-container .tabs')
|
||||||
|
if (!navTabsElements.length) return
|
||||||
|
|
||||||
|
const setActiveClass = (elements, activeIndex) => {
|
||||||
|
elements.forEach((el, index) => {
|
||||||
|
el.classList.toggle('active', index === activeIndex)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleNavClick = e => {
|
||||||
|
const target = e.target.closest('button')
|
||||||
|
if (!target || target.classList.contains('active')) return
|
||||||
|
|
||||||
|
const navItems = [...e.currentTarget.children]
|
||||||
|
const tabContents = [...e.currentTarget.nextElementSibling.children]
|
||||||
|
const indexOfButton = navItems.indexOf(target)
|
||||||
|
setActiveClass(navItems, indexOfButton)
|
||||||
|
e.currentTarget.classList.remove('no-default')
|
||||||
|
setActiveClass(tabContents, indexOfButton)
|
||||||
|
addJustifiedGallery(tabContents[indexOfButton].querySelectorAll('.gallery-container'), true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleToTopClick = tabElement => e => {
|
||||||
|
if (e.target.closest('button')) {
|
||||||
|
btf.scrollToDest(btf.getEleTop(tabElement), 300)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
navTabsElements.forEach(tabElement => {
|
||||||
|
btf.addEventListenerPjax(tabElement.firstElementChild, 'click', handleNavClick)
|
||||||
|
btf.addEventListenerPjax(tabElement.lastElementChild, 'click', handleToTopClick(tabElement))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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 postComment = document.getElementById('post-comment')
|
||||||
|
const handleSwitchBtn = () => {
|
||||||
|
postComment.classList.toggle('move')
|
||||||
|
if (!switchDone && typeof loadOtherComment === 'function') {
|
||||||
|
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 >= limitDay) {
|
||||||
|
const ele = document.createElement('div')
|
||||||
|
ele.className = 'post-outdate-notice'
|
||||||
|
ele.textContent = `${messagePrev} ${diffDay} ${messageNext}`
|
||||||
|
const $targetEle = document.getElementById('article-container')
|
||||||
|
if (position === 'top') {
|
||||||
|
$targetEle.insertBefore(ele, $targetEle.firstChild)
|
||||||
|
} else {
|
||||||
|
$targetEle.appendChild(ele)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const lazyloadImg = () => {
|
||||||
|
window.lazyLoadInstance = new LazyLoad({
|
||||||
|
elements_selector: 'img',
|
||||||
|
threshold: 0,
|
||||||
|
data_src: 'lazy-src'
|
||||||
|
})
|
||||||
|
|
||||||
|
btf.addGlobalFn('pjaxComplete', () => {
|
||||||
|
window.lazyLoadInstance.update()
|
||||||
|
}, 'lazyload')
|
||||||
|
}
|
||||||
|
|
||||||
|
const relativeDate = selector => {
|
||||||
|
selector.forEach(item => {
|
||||||
|
item.textContent = btf.diffDate(item.getAttribute('datetime'), true)
|
||||||
|
item.style.display = 'inline'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const justifiedIndexPostUI = () => {
|
||||||
|
const recentPostsElement = document.getElementById('recent-posts')
|
||||||
|
if (!(recentPostsElement && recentPostsElement.classList.contains('masonry'))) return
|
||||||
|
|
||||||
|
const init = () => {
|
||||||
|
const masonryItem = new InfiniteGrid.MasonryInfiniteGrid('.recent-post-items', {
|
||||||
|
gap: { horizontal: 10, vertical: 20 },
|
||||||
|
useTransform: true,
|
||||||
|
useResizeObserver: true
|
||||||
|
})
|
||||||
|
masonryItem.renderItems()
|
||||||
|
btf.addGlobalFn('pjaxCompleteOnce', () => { masonryItem.destroy() }, 'removeJustifiedIndexPostUI')
|
||||||
|
}
|
||||||
|
|
||||||
|
typeof InfiniteGrid === 'function' ? init() : btf.getScript(`${GLOBAL_CONFIG.infinitegrid.js}`).then(init)
|
||||||
|
}
|
||||||
|
|
||||||
|
const unRefreshFn = () => {
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
adjustMenu(false)
|
||||||
|
mobileSidebarOpen && btf.isHidden(document.getElementById('toggle-menu')) && sidebarFn.close()
|
||||||
|
})
|
||||||
|
|
||||||
|
const menuMask = document.getElementById('menu-mask')
|
||||||
|
menuMask && menuMask.addEventListener('click', () => { sidebarFn.close() })
|
||||||
|
|
||||||
|
clickFnOfSubMenu()
|
||||||
|
GLOBAL_CONFIG.islazyload && lazyloadImg()
|
||||||
|
GLOBAL_CONFIG.copyright !== undefined && addCopyright()
|
||||||
|
|
||||||
|
if (GLOBAL_CONFIG.autoDarkmode) {
|
||||||
|
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
|
||||||
|
if (btf.saveToLocal.get('theme') !== undefined) return
|
||||||
|
e.matches ? handleThemeChange('dark') : handleThemeChange('light')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const forPostFn = () => {
|
||||||
|
addHighlightTool()
|
||||||
|
addPhotoFigcaption()
|
||||||
|
addJustifiedGallery(document.querySelectorAll('#article-container .gallery-container'))
|
||||||
|
runLightbox()
|
||||||
|
scrollFnToDo()
|
||||||
|
addTableWrap()
|
||||||
|
clickFnOfTagHide()
|
||||||
|
tabsFn()
|
||||||
|
}
|
||||||
|
|
||||||
|
const refreshFn = () => {
|
||||||
|
initAdjust()
|
||||||
|
justifiedIndexPostUI()
|
||||||
|
|
||||||
|
if (GLOBAL_CONFIG_SITE.isPost) {
|
||||||
|
GLOBAL_CONFIG.noticeOutdate !== undefined && addPostOutdateNotice()
|
||||||
|
GLOBAL_CONFIG.relativeDate.post && relativeDate(document.querySelectorAll('#post-meta time'))
|
||||||
|
} else {
|
||||||
|
GLOBAL_CONFIG.relativeDate.homepage && relativeDate(document.querySelectorAll('#recent-posts time'))
|
||||||
|
GLOBAL_CONFIG.runtime && addRuntime()
|
||||||
|
addLastPushDate()
|
||||||
|
toggleCardCategory()
|
||||||
|
}
|
||||||
|
|
||||||
|
GLOBAL_CONFIG_SITE.isHome && scrollDownInIndex()
|
||||||
|
scrollFn()
|
||||||
|
|
||||||
|
forPostFn()
|
||||||
|
switchComments()
|
||||||
|
openMobileMenu()
|
||||||
|
}
|
||||||
|
|
||||||
|
btf.addGlobalFn('pjaxComplete', refreshFn, 'refreshFn')
|
||||||
|
refreshFn()
|
||||||
|
unRefreshFn()
|
||||||
|
|
||||||
|
// 處理 hexo-blog-encrypt 事件
|
||||||
|
window.addEventListener('hexo-blog-decrypt', e => {
|
||||||
|
forPostFn()
|
||||||
|
window.translateFn.translateInitialization()
|
||||||
|
Object.values(window.globalFn.encrypt).forEach(fn => {
|
||||||
|
fn()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
173
js/search/algolia.js
Normal file
173
js/search/algolia.js
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
window.addEventListener('load', () => {
|
||||||
|
const { algolia } = GLOBAL_CONFIG
|
||||||
|
const { appId, apiKey, indexName, hitsPerPage = 5, languages } = algolia
|
||||||
|
|
||||||
|
if (!appId || !apiKey || !indexName) {
|
||||||
|
return console.error('Algolia setting is invalid!')
|
||||||
|
}
|
||||||
|
|
||||||
|
const $searchMask = document.getElementById('search-mask')
|
||||||
|
const $searchDialog = document.querySelector('#algolia-search .search-dialog')
|
||||||
|
|
||||||
|
const animateElements = show => {
|
||||||
|
const action = show ? 'animateIn' : 'animateOut'
|
||||||
|
const maskAnimation = show ? 'to_show 0.5s' : 'to_hide 0.5s'
|
||||||
|
const dialogAnimation = show ? 'titleScale 0.5s' : 'search_close .5s'
|
||||||
|
btf[action]($searchMask, maskAnimation)
|
||||||
|
btf[action]($searchDialog, dialogAnimation)
|
||||||
|
}
|
||||||
|
|
||||||
|
const fixSafariHeight = () => {
|
||||||
|
if (window.innerWidth < 768) {
|
||||||
|
$searchDialog.style.setProperty('--search-height', `${window.innerHeight}px`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const openSearch = () => {
|
||||||
|
btf.overflowPaddingR.add()
|
||||||
|
animateElements(true)
|
||||||
|
setTimeout(() => { document.querySelector('#algolia-search .ais-SearchBox-input').focus() }, 100)
|
||||||
|
|
||||||
|
const handleEscape = event => {
|
||||||
|
if (event.code === 'Escape') {
|
||||||
|
closeSearch()
|
||||||
|
document.removeEventListener('keydown', handleEscape)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('keydown', handleEscape)
|
||||||
|
fixSafariHeight()
|
||||||
|
window.addEventListener('resize', fixSafariHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeSearch = () => {
|
||||||
|
btf.overflowPaddingR.remove()
|
||||||
|
animateElements(false)
|
||||||
|
window.removeEventListener('resize', fixSafariHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchClickFn = () => {
|
||||||
|
btf.addEventListenerPjax(document.querySelector('#search-button > .search'), 'click', openSearch)
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchFnOnce = () => {
|
||||||
|
$searchMask.addEventListener('click', closeSearch)
|
||||||
|
document.querySelector('#algolia-search .search-close-button').addEventListener('click', closeSearch)
|
||||||
|
}
|
||||||
|
|
||||||
|
const cutContent = (content) => {
|
||||||
|
if (!content) return ''
|
||||||
|
const firstOccur = content.indexOf('<mark>')
|
||||||
|
let start = firstOccur - 30
|
||||||
|
let end = firstOccur + 120
|
||||||
|
let pre = ''
|
||||||
|
let post = ''
|
||||||
|
|
||||||
|
if (start <= 0) {
|
||||||
|
start = 0
|
||||||
|
end = 140
|
||||||
|
} else {
|
||||||
|
pre = '...'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (end > content.length) {
|
||||||
|
end = content.length
|
||||||
|
} else {
|
||||||
|
post = '...'
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${pre}${content.substring(start, end)}${post}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const disableDiv = [
|
||||||
|
document.getElementById('algolia-hits'),
|
||||||
|
document.getElementById('algolia-pagination'),
|
||||||
|
document.querySelector('#algolia-info .algolia-stats')
|
||||||
|
]
|
||||||
|
|
||||||
|
const search = instantsearch({
|
||||||
|
indexName,
|
||||||
|
searchClient: algoliasearch(appId, apiKey),
|
||||||
|
searchFunction (helper) {
|
||||||
|
disableDiv.forEach(item => {
|
||||||
|
item.style.display = helper.state.query ? '' : 'none'
|
||||||
|
})
|
||||||
|
if (helper.state.query) helper.search()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const widgets = [
|
||||||
|
instantsearch.widgets.configure({ hitsPerPage }),
|
||||||
|
instantsearch.widgets.searchBox({
|
||||||
|
container: '#algolia-search-input',
|
||||||
|
showReset: false,
|
||||||
|
showSubmit: false,
|
||||||
|
placeholder: languages.input_placeholder,
|
||||||
|
showLoadingIndicator: true
|
||||||
|
}),
|
||||||
|
instantsearch.widgets.hits({
|
||||||
|
container: '#algolia-hits',
|
||||||
|
templates: {
|
||||||
|
item (data) {
|
||||||
|
const link = data.permalink || (GLOBAL_CONFIG.root + data.path)
|
||||||
|
const result = data._highlightResult
|
||||||
|
const content = result.contentStripTruncate
|
||||||
|
? cutContent(result.contentStripTruncate.value)
|
||||||
|
: result.contentStrip
|
||||||
|
? cutContent(result.contentStrip.value)
|
||||||
|
: result.content
|
||||||
|
? cutContent(result.content.value)
|
||||||
|
: ''
|
||||||
|
return `
|
||||||
|
<a href="${link}" class="algolia-hit-item-link">
|
||||||
|
<span class="algolia-hits-item-title">${result.title.value || 'no-title'}</span>
|
||||||
|
${content ? `<div class="algolia-hit-item-content">${content}</div>` : ''}
|
||||||
|
</a>`
|
||||||
|
},
|
||||||
|
empty (data) {
|
||||||
|
return `<div id="algolia-hits-empty">${languages.hits_empty.replace(/\$\{query}/, data.query)}</div>`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
instantsearch.widgets.stats({
|
||||||
|
container: '#algolia-info > .algolia-stats',
|
||||||
|
templates: {
|
||||||
|
text (data) {
|
||||||
|
const stats = languages.hits_stats
|
||||||
|
.replace(/\$\{hits}/, data.nbHits)
|
||||||
|
.replace(/\$\{time}/, data.processingTimeMS)
|
||||||
|
return `<hr>${stats}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
instantsearch.widgets.poweredBy({
|
||||||
|
container: '#algolia-info > .algolia-poweredBy'
|
||||||
|
}),
|
||||||
|
instantsearch.widgets.pagination({
|
||||||
|
container: '#algolia-pagination',
|
||||||
|
totalPages: 5,
|
||||||
|
templates: {
|
||||||
|
first: '<i class="fas fa-angle-double-left"></i>',
|
||||||
|
last: '<i class="fas fa-angle-double-right"></i>',
|
||||||
|
previous: '<i class="fas fa-angle-left"></i>',
|
||||||
|
next: '<i class="fas fa-angle-right"></i>'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]
|
||||||
|
|
||||||
|
search.addWidgets(widgets)
|
||||||
|
search.start()
|
||||||
|
searchClickFn()
|
||||||
|
searchFnOnce()
|
||||||
|
|
||||||
|
window.addEventListener('pjax:complete', () => {
|
||||||
|
if (!btf.isHidden($searchMask)) closeSearch()
|
||||||
|
searchClickFn()
|
||||||
|
})
|
||||||
|
|
||||||
|
if (window.pjax) {
|
||||||
|
search.on('render', () => {
|
||||||
|
window.pjax.refresh(document.getElementById('algolia-hits'))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
360
js/search/local-search.js
Normal file
360
js/search/local-search.js
Normal file
@ -0,0 +1,360 @@
|
|||||||
|
/**
|
||||||
|
* Refer to hexo-generator-searchdb
|
||||||
|
* https://github.com/next-theme/hexo-generator-searchdb/blob/main/dist/search.js
|
||||||
|
* Modified by hexo-theme-butterfly
|
||||||
|
*/
|
||||||
|
|
||||||
|
class LocalSearch {
|
||||||
|
constructor ({
|
||||||
|
path = '',
|
||||||
|
unescape = false,
|
||||||
|
top_n_per_article = 1
|
||||||
|
}) {
|
||||||
|
this.path = path
|
||||||
|
this.unescape = unescape
|
||||||
|
this.top_n_per_article = top_n_per_article
|
||||||
|
this.isfetched = false
|
||||||
|
this.datas = null
|
||||||
|
}
|
||||||
|
|
||||||
|
getIndexByWord (words, text, caseSensitive = false) {
|
||||||
|
const index = []
|
||||||
|
const included = new Set()
|
||||||
|
|
||||||
|
if (!caseSensitive) {
|
||||||
|
text = text.toLowerCase()
|
||||||
|
}
|
||||||
|
words.forEach(word => {
|
||||||
|
if (this.unescape) {
|
||||||
|
const div = document.createElement('div')
|
||||||
|
div.innerText = word
|
||||||
|
word = div.innerHTML
|
||||||
|
}
|
||||||
|
const wordLen = word.length
|
||||||
|
if (wordLen === 0) return
|
||||||
|
let startPosition = 0
|
||||||
|
let position = -1
|
||||||
|
if (!caseSensitive) {
|
||||||
|
word = word.toLowerCase()
|
||||||
|
}
|
||||||
|
while ((position = text.indexOf(word, startPosition)) > -1) {
|
||||||
|
index.push({ position, word })
|
||||||
|
included.add(word)
|
||||||
|
startPosition = position + wordLen
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// Sort index by position of keyword
|
||||||
|
index.sort((left, right) => {
|
||||||
|
if (left.position !== right.position) {
|
||||||
|
return left.position - right.position
|
||||||
|
}
|
||||||
|
return right.word.length - left.word.length
|
||||||
|
})
|
||||||
|
return [index, included]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge hits into slices
|
||||||
|
mergeIntoSlice (start, end, index) {
|
||||||
|
let item = index[0]
|
||||||
|
let { position, word } = item
|
||||||
|
const hits = []
|
||||||
|
const count = new Set()
|
||||||
|
while (position + word.length <= end && index.length !== 0) {
|
||||||
|
count.add(word)
|
||||||
|
hits.push({
|
||||||
|
position,
|
||||||
|
length: word.length
|
||||||
|
})
|
||||||
|
const wordEnd = position + word.length
|
||||||
|
|
||||||
|
// Move to next position of hit
|
||||||
|
index.shift()
|
||||||
|
while (index.length !== 0) {
|
||||||
|
item = index[0]
|
||||||
|
position = item.position
|
||||||
|
word = item.word
|
||||||
|
if (wordEnd > position) {
|
||||||
|
index.shift()
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
hits,
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
count: count.size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Highlight title and content
|
||||||
|
highlightKeyword (val, slice) {
|
||||||
|
let result = ''
|
||||||
|
let index = slice.start
|
||||||
|
for (const { position, length } of slice.hits) {
|
||||||
|
result += val.substring(index, position)
|
||||||
|
index = position + length
|
||||||
|
result += `<mark class="search-keyword">${val.substr(position, length)}</mark>`
|
||||||
|
}
|
||||||
|
result += val.substring(index, slice.end)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
getResultItems (keywords) {
|
||||||
|
const resultItems = []
|
||||||
|
this.datas.forEach(({ title, content, url }) => {
|
||||||
|
// The number of different keywords included in the article.
|
||||||
|
const [indexOfTitle, keysOfTitle] = this.getIndexByWord(keywords, title)
|
||||||
|
const [indexOfContent, keysOfContent] = this.getIndexByWord(keywords, content)
|
||||||
|
const includedCount = new Set([...keysOfTitle, ...keysOfContent]).size
|
||||||
|
|
||||||
|
// Show search results
|
||||||
|
const hitCount = indexOfTitle.length + indexOfContent.length
|
||||||
|
if (hitCount === 0) return
|
||||||
|
|
||||||
|
const slicesOfTitle = []
|
||||||
|
if (indexOfTitle.length !== 0) {
|
||||||
|
slicesOfTitle.push(this.mergeIntoSlice(0, title.length, indexOfTitle))
|
||||||
|
}
|
||||||
|
|
||||||
|
let slicesOfContent = []
|
||||||
|
while (indexOfContent.length !== 0) {
|
||||||
|
const item = indexOfContent[0]
|
||||||
|
const { position } = item
|
||||||
|
// Cut out 120 characters. The maxlength of .search-input is 80.
|
||||||
|
const start = Math.max(0, position - 20)
|
||||||
|
const end = Math.min(content.length, position + 100)
|
||||||
|
slicesOfContent.push(this.mergeIntoSlice(start, end, indexOfContent))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort slices in content by included keywords' count and hits' count
|
||||||
|
slicesOfContent.sort((left, right) => {
|
||||||
|
if (left.count !== right.count) {
|
||||||
|
return right.count - left.count
|
||||||
|
} else if (left.hits.length !== right.hits.length) {
|
||||||
|
return right.hits.length - left.hits.length
|
||||||
|
}
|
||||||
|
return left.start - right.start
|
||||||
|
})
|
||||||
|
|
||||||
|
// Select top N slices in content
|
||||||
|
const upperBound = parseInt(this.top_n_per_article, 10)
|
||||||
|
if (upperBound >= 0) {
|
||||||
|
slicesOfContent = slicesOfContent.slice(0, upperBound)
|
||||||
|
}
|
||||||
|
|
||||||
|
let resultItem = ''
|
||||||
|
|
||||||
|
url = new URL(url, location.origin)
|
||||||
|
url.searchParams.append('highlight', keywords.join(' '))
|
||||||
|
|
||||||
|
if (slicesOfTitle.length !== 0) {
|
||||||
|
resultItem += `<div class="local-search-hit-item"><a href="${url.href}"><span class="search-result-title">${this.highlightKeyword(title, slicesOfTitle[0])}</span>`
|
||||||
|
} else {
|
||||||
|
resultItem += `<div class="local-search-hit-item"><a href="${url.href}"><span class="search-result-title">${title}</span>`
|
||||||
|
}
|
||||||
|
|
||||||
|
slicesOfContent.forEach(slice => {
|
||||||
|
resultItem += `<p class="search-result">${this.highlightKeyword(content, slice)}...</p></a>`
|
||||||
|
})
|
||||||
|
|
||||||
|
resultItem += '</div>'
|
||||||
|
resultItems.push({
|
||||||
|
item: resultItem,
|
||||||
|
id: resultItems.length,
|
||||||
|
hitCount,
|
||||||
|
includedCount
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return resultItems
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchData () {
|
||||||
|
const isXml = !this.path.endsWith('json')
|
||||||
|
fetch(this.path)
|
||||||
|
.then(response => response.text())
|
||||||
|
.then(res => {
|
||||||
|
// Get the contents from search data
|
||||||
|
this.isfetched = true
|
||||||
|
this.datas = isXml
|
||||||
|
? [...new DOMParser().parseFromString(res, 'text/xml').querySelectorAll('entry')].map(element => ({
|
||||||
|
title: element.querySelector('title').textContent,
|
||||||
|
content: element.querySelector('content').textContent,
|
||||||
|
url: element.querySelector('url').textContent
|
||||||
|
}))
|
||||||
|
: JSON.parse(res)
|
||||||
|
// Only match articles with non-empty titles
|
||||||
|
this.datas = this.datas.filter(data => data.title).map(data => {
|
||||||
|
data.title = data.title.trim()
|
||||||
|
data.content = data.content ? data.content.trim().replace(/<[^>]+>/g, '') : ''
|
||||||
|
data.url = decodeURIComponent(data.url).replace(/\/{2,}/g, '/')
|
||||||
|
return data
|
||||||
|
})
|
||||||
|
// Remove loading animation
|
||||||
|
window.dispatchEvent(new Event('search:loaded'))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Highlight by wrapping node in mark elements with the given class name
|
||||||
|
highlightText (node, slice, className) {
|
||||||
|
const val = node.nodeValue
|
||||||
|
let index = slice.start
|
||||||
|
const children = []
|
||||||
|
for (const { position, length } of slice.hits) {
|
||||||
|
const text = document.createTextNode(val.substring(index, position))
|
||||||
|
index = position + length
|
||||||
|
const mark = document.createElement('mark')
|
||||||
|
mark.className = className
|
||||||
|
mark.appendChild(document.createTextNode(val.substr(position, length)))
|
||||||
|
children.push(text, mark)
|
||||||
|
}
|
||||||
|
node.nodeValue = val.substring(index, slice.end)
|
||||||
|
children.forEach(element => {
|
||||||
|
node.parentNode.insertBefore(element, node)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Highlight the search words provided in the url in the text
|
||||||
|
highlightSearchWords (body) {
|
||||||
|
const params = new URL(location.href).searchParams.get('highlight')
|
||||||
|
const keywords = params ? params.split(' ') : []
|
||||||
|
if (!keywords.length || !body) return
|
||||||
|
const walk = document.createTreeWalker(body, NodeFilter.SHOW_TEXT, null)
|
||||||
|
const allNodes = []
|
||||||
|
while (walk.nextNode()) {
|
||||||
|
if (!walk.currentNode.parentNode.matches('button, select, textarea, .mermaid')) allNodes.push(walk.currentNode)
|
||||||
|
}
|
||||||
|
allNodes.forEach(node => {
|
||||||
|
const [indexOfNode] = this.getIndexByWord(keywords, node.nodeValue)
|
||||||
|
if (!indexOfNode.length) return
|
||||||
|
const slice = this.mergeIntoSlice(0, node.nodeValue.length, indexOfNode)
|
||||||
|
this.highlightText(node, slice, 'search-keyword')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('load', () => {
|
||||||
|
// Search
|
||||||
|
const { path, top_n_per_article, unescape, languages } = GLOBAL_CONFIG.localSearch
|
||||||
|
const localSearch = new LocalSearch({
|
||||||
|
path,
|
||||||
|
top_n_per_article,
|
||||||
|
unescape
|
||||||
|
})
|
||||||
|
|
||||||
|
const input = document.querySelector('#local-search-input input')
|
||||||
|
const statsItem = document.getElementById('local-search-stats-wrap')
|
||||||
|
const $loadingStatus = document.getElementById('loading-status')
|
||||||
|
const isXml = !path.endsWith('json')
|
||||||
|
|
||||||
|
const inputEventFunction = () => {
|
||||||
|
if (!localSearch.isfetched) return
|
||||||
|
let searchText = input.value.trim().toLowerCase()
|
||||||
|
isXml && (searchText = searchText.replace(/</g, '<').replace(/>/g, '>'))
|
||||||
|
if (searchText !== '') $loadingStatus.innerHTML = '<i class="fas fa-spinner fa-pulse"></i>'
|
||||||
|
const keywords = searchText.split(/[-\s]+/)
|
||||||
|
const container = document.getElementById('local-search-results')
|
||||||
|
let resultItems = []
|
||||||
|
if (searchText.length > 0) {
|
||||||
|
// Perform local searching
|
||||||
|
resultItems = localSearch.getResultItems(keywords)
|
||||||
|
}
|
||||||
|
if (keywords.length === 1 && keywords[0] === '') {
|
||||||
|
container.textContent = ''
|
||||||
|
statsItem.textContent = ''
|
||||||
|
} else if (resultItems.length === 0) {
|
||||||
|
container.textContent = ''
|
||||||
|
const statsDiv = document.createElement('div')
|
||||||
|
statsDiv.className = 'search-result-stats'
|
||||||
|
statsDiv.textContent = languages.hits_empty.replace(/\$\{query}/, searchText)
|
||||||
|
statsItem.innerHTML = statsDiv.outerHTML
|
||||||
|
} else {
|
||||||
|
resultItems.sort((left, right) => {
|
||||||
|
if (left.includedCount !== right.includedCount) {
|
||||||
|
return right.includedCount - left.includedCount
|
||||||
|
} else if (left.hitCount !== right.hitCount) {
|
||||||
|
return right.hitCount - left.hitCount
|
||||||
|
}
|
||||||
|
return right.id - left.id
|
||||||
|
})
|
||||||
|
|
||||||
|
const stats = languages.hits_stats.replace(/\$\{hits}/, resultItems.length)
|
||||||
|
|
||||||
|
container.innerHTML = `<div class="search-result-list">${resultItems.map(result => result.item).join('')}</div>`
|
||||||
|
statsItem.innerHTML = `<hr><div class="search-result-stats">${stats}</div>`
|
||||||
|
window.pjax && window.pjax.refresh(container)
|
||||||
|
}
|
||||||
|
|
||||||
|
$loadingStatus.textContent = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
let loadFlag = false
|
||||||
|
const $searchMask = document.getElementById('search-mask')
|
||||||
|
const $searchDialog = document.querySelector('#local-search .search-dialog')
|
||||||
|
|
||||||
|
// fix safari
|
||||||
|
const fixSafariHeight = () => {
|
||||||
|
if (window.innerWidth < 768) {
|
||||||
|
$searchDialog.style.setProperty('--search-height', window.innerHeight + 'px')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const openSearch = () => {
|
||||||
|
btf.overflowPaddingR.add()
|
||||||
|
btf.animateIn($searchMask, 'to_show 0.5s')
|
||||||
|
btf.animateIn($searchDialog, 'titleScale 0.5s')
|
||||||
|
setTimeout(() => { input.focus() }, 300)
|
||||||
|
if (!loadFlag) {
|
||||||
|
!localSearch.isfetched && localSearch.fetchData()
|
||||||
|
input.addEventListener('input', inputEventFunction)
|
||||||
|
loadFlag = true
|
||||||
|
}
|
||||||
|
// shortcut: ESC
|
||||||
|
document.addEventListener('keydown', function f (event) {
|
||||||
|
if (event.code === 'Escape') {
|
||||||
|
closeSearch()
|
||||||
|
document.removeEventListener('keydown', f)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
fixSafariHeight()
|
||||||
|
window.addEventListener('resize', fixSafariHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeSearch = () => {
|
||||||
|
btf.overflowPaddingR.remove()
|
||||||
|
btf.animateOut($searchDialog, 'search_close .5s')
|
||||||
|
btf.animateOut($searchMask, 'to_hide 0.5s')
|
||||||
|
window.removeEventListener('resize', fixSafariHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchClickFn = () => {
|
||||||
|
btf.addEventListenerPjax(document.querySelector('#search-button > .search'), 'click', openSearch)
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchFnOnce = () => {
|
||||||
|
document.querySelector('#local-search .search-close-button').addEventListener('click', closeSearch)
|
||||||
|
$searchMask.addEventListener('click', closeSearch)
|
||||||
|
if (GLOBAL_CONFIG.localSearch.preload) {
|
||||||
|
localSearch.fetchData()
|
||||||
|
}
|
||||||
|
localSearch.highlightSearchWords(document.getElementById('article-container'))
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('search:loaded', () => {
|
||||||
|
const $loadDataItem = document.getElementById('loading-database')
|
||||||
|
$loadDataItem.nextElementSibling.style.display = 'block'
|
||||||
|
$loadDataItem.remove()
|
||||||
|
})
|
||||||
|
|
||||||
|
searchClickFn()
|
||||||
|
searchFnOnce()
|
||||||
|
|
||||||
|
// pjax
|
||||||
|
window.addEventListener('pjax:complete', () => {
|
||||||
|
!btf.isHidden($searchMask) && closeSearch()
|
||||||
|
localSearch.highlightSearchWords(document.getElementById('article-container'))
|
||||||
|
searchClickFn()
|
||||||
|
})
|
||||||
|
})
|
||||||
117
js/tw_cn.js
Normal file
117
js/tw_cn.js
Normal file
File diff suppressed because one or more lines are too long
297
js/utils.js
Normal file
297
js/utils.js
Normal file
@ -0,0 +1,297 @@
|
|||||||
|
(() => {
|
||||||
|
const btfFn = {
|
||||||
|
debounce: (func, wait = 0, immediate = false) => {
|
||||||
|
let timeout
|
||||||
|
return (...args) => {
|
||||||
|
const later = () => {
|
||||||
|
timeout = null
|
||||||
|
if (!immediate) func(...args)
|
||||||
|
}
|
||||||
|
const callNow = immediate && !timeout
|
||||||
|
clearTimeout(timeout)
|
||||||
|
timeout = setTimeout(later, wait)
|
||||||
|
if (callNow) func(...args)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
throttle: function (func, wait, options = {}) {
|
||||||
|
let timeout, context, args
|
||||||
|
let previous = 0
|
||||||
|
|
||||||
|
const later = () => {
|
||||||
|
previous = options.leading === false ? 0 : new Date().getTime()
|
||||||
|
timeout = null
|
||||||
|
func.apply(context, args)
|
||||||
|
if (!timeout) context = args = null
|
||||||
|
}
|
||||||
|
|
||||||
|
const throttled = (...params) => {
|
||||||
|
const now = new Date().getTime()
|
||||||
|
if (!previous && options.leading === false) previous = now
|
||||||
|
const remaining = wait - (now - previous)
|
||||||
|
context = this
|
||||||
|
args = params
|
||||||
|
if (remaining <= 0 || remaining > wait) {
|
||||||
|
if (timeout) {
|
||||||
|
clearTimeout(timeout)
|
||||||
|
timeout = null
|
||||||
|
}
|
||||||
|
previous = now
|
||||||
|
func.apply(context, args)
|
||||||
|
if (!timeout) context = args = null
|
||||||
|
} else if (!timeout && options.trailing !== false) {
|
||||||
|
timeout = setTimeout(later, remaining)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return throttled
|
||||||
|
},
|
||||||
|
|
||||||
|
overflowPaddingR: {
|
||||||
|
add: () => {
|
||||||
|
const paddingRight = window.innerWidth - document.body.clientWidth
|
||||||
|
|
||||||
|
if (paddingRight > 0) {
|
||||||
|
document.body.style.paddingRight = `${paddingRight}px`
|
||||||
|
document.body.style.overflow = 'hidden'
|
||||||
|
const menuElement = document.querySelector('#page-header.nav-fixed #menus')
|
||||||
|
if (menuElement) {
|
||||||
|
menuElement.style.paddingRight = `${paddingRight}px`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
remove: () => {
|
||||||
|
document.body.style.paddingRight = ''
|
||||||
|
document.body.style.overflow = ''
|
||||||
|
const menuElement = document.querySelector('#page-header.nav-fixed #menus')
|
||||||
|
if (menuElement) {
|
||||||
|
menuElement.style.paddingRight = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
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,
|
||||||
|
backgroundColor: bg,
|
||||||
|
showAction,
|
||||||
|
duration,
|
||||||
|
pos: position,
|
||||||
|
customClass: 'snackbar-css'
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
diffDate: (inputDate, more = false) => {
|
||||||
|
const dateNow = new Date()
|
||||||
|
const datePost = new Date(inputDate)
|
||||||
|
const diffMs = dateNow - datePost
|
||||||
|
const diffSec = diffMs / 1000
|
||||||
|
const diffMin = diffSec / 60
|
||||||
|
const diffHour = diffMin / 60
|
||||||
|
const diffDay = diffHour / 24
|
||||||
|
const diffMonth = diffDay / 30
|
||||||
|
const { dateSuffix } = GLOBAL_CONFIG
|
||||||
|
|
||||||
|
if (!more) return Math.floor(diffDay)
|
||||||
|
|
||||||
|
if (diffMonth > 12) return datePost.toISOString().slice(0, 10)
|
||||||
|
if (diffMonth >= 1) return `${Math.floor(diffMonth)} ${dateSuffix.month}`
|
||||||
|
if (diffDay >= 1) return `${Math.floor(diffDay)} ${dateSuffix.day}`
|
||||||
|
if (diffHour >= 1) return `${Math.floor(diffHour)} ${dateSuffix.hour}`
|
||||||
|
if (diffMin >= 1) return `${Math.floor(diffMin)} ${dateSuffix.min}`
|
||||||
|
return dateSuffix.just
|
||||||
|
},
|
||||||
|
|
||||||
|
loadComment: (dom, callback) => {
|
||||||
|
if ('IntersectionObserver' in window) {
|
||||||
|
const observerItem = new IntersectionObserver((entries) => {
|
||||||
|
if (entries[0].isIntersecting) {
|
||||||
|
callback()
|
||||||
|
observerItem.disconnect()
|
||||||
|
}
|
||||||
|
}, { threshold: [0] })
|
||||||
|
observerItem.observe(dom)
|
||||||
|
} else {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
scrollToDest: (pos, time = 500) => {
|
||||||
|
const currentPos = window.scrollY
|
||||||
|
const isNavFixed = document.getElementById('page-header').classList.contains('fixed')
|
||||||
|
if (currentPos > pos || isNavFixed) pos = pos - 70
|
||||||
|
|
||||||
|
if ('scrollBehavior' in document.documentElement.style) {
|
||||||
|
window.scrollTo({
|
||||||
|
top: pos,
|
||||||
|
behavior: 'smooth'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const startTime = performance.now()
|
||||||
|
const animate = currentTime => {
|
||||||
|
const timeElapsed = currentTime - startTime
|
||||||
|
const progress = Math.min(timeElapsed / time, 1)
|
||||||
|
window.scrollTo(0, currentPos + (pos - currentPos) * progress)
|
||||||
|
if (progress < 1) {
|
||||||
|
requestAnimationFrame(animate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
requestAnimationFrame(animate)
|
||||||
|
},
|
||||||
|
|
||||||
|
animateIn: (ele, animation) => {
|
||||||
|
ele.style.display = 'block'
|
||||||
|
ele.style.animation = animation
|
||||||
|
},
|
||||||
|
|
||||||
|
animateOut: (ele, animation) => {
|
||||||
|
const handleAnimationEnd = () => {
|
||||||
|
ele.style.display = ''
|
||||||
|
ele.style.animation = ''
|
||||||
|
ele.removeEventListener('animationend', handleAnimationEnd)
|
||||||
|
}
|
||||||
|
ele.addEventListener('animationend', handleAnimationEnd)
|
||||||
|
ele.style.animation = animation
|
||||||
|
},
|
||||||
|
|
||||||
|
wrap: (selector, eleType, options) => {
|
||||||
|
const createEle = document.createElement(eleType)
|
||||||
|
for (const [key, value] of Object.entries(options)) {
|
||||||
|
createEle.setAttribute(key, value)
|
||||||
|
}
|
||||||
|
selector.parentNode.insertBefore(createEle, selector)
|
||||||
|
createEle.appendChild(selector)
|
||||||
|
},
|
||||||
|
|
||||||
|
isHidden: ele => ele.offsetHeight === 0 && ele.offsetWidth === 0,
|
||||||
|
|
||||||
|
getEleTop: ele => {
|
||||||
|
let actualTop = ele.offsetTop
|
||||||
|
let current = ele.offsetParent
|
||||||
|
|
||||||
|
while (current !== null) {
|
||||||
|
actualTop += current.offsetTop
|
||||||
|
current = current.offsetParent
|
||||||
|
}
|
||||||
|
|
||||||
|
return actualTop
|
||||||
|
},
|
||||||
|
|
||||||
|
loadLightbox: ele => {
|
||||||
|
const service = GLOBAL_CONFIG.lightbox
|
||||||
|
|
||||||
|
if (service === 'medium_zoom') {
|
||||||
|
mediumZoom(ele, { background: 'var(--zoom-bg)' })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (service === 'fancybox') {
|
||||||
|
Array.from(ele).forEach(i => {
|
||||||
|
if (i.parentNode.tagName !== 'A') {
|
||||||
|
const dataSrc = i.dataset.lazySrc || i.src
|
||||||
|
const dataCaption = i.title || i.alt || ''
|
||||||
|
btf.wrap(i, 'a', { href: dataSrc, 'data-fancybox': 'gallery', 'data-caption': dataCaption, 'data-thumb': dataSrc })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!window.fancyboxRun) {
|
||||||
|
Fancybox.bind('[data-fancybox]', {
|
||||||
|
Hash: false,
|
||||||
|
Thumbs: {
|
||||||
|
showOnStart: false
|
||||||
|
},
|
||||||
|
Images: {
|
||||||
|
Panzoom: {
|
||||||
|
maxScale: 4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Carousel: {
|
||||||
|
transition: 'slide'
|
||||||
|
},
|
||||||
|
Toolbar: {
|
||||||
|
display: {
|
||||||
|
left: ['infobar'],
|
||||||
|
middle: [
|
||||||
|
'zoomIn',
|
||||||
|
'zoomOut',
|
||||||
|
'toggle1to1',
|
||||||
|
'rotateCCW',
|
||||||
|
'rotateCW',
|
||||||
|
'flipX',
|
||||||
|
'flipY'
|
||||||
|
],
|
||||||
|
right: ['slideshow', 'thumbs', 'close']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
window.fancyboxRun = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
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, anchor)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getScrollPercent: (() => {
|
||||||
|
let docHeight, winHeight, headerHeight, contentMath
|
||||||
|
|
||||||
|
return (currentTop, ele) => {
|
||||||
|
if (!docHeight || ele.clientHeight !== docHeight) {
|
||||||
|
docHeight = ele.clientHeight
|
||||||
|
winHeight = window.innerHeight
|
||||||
|
headerHeight = ele.offsetTop
|
||||||
|
contentMath = Math.max(docHeight - winHeight, document.documentElement.scrollHeight - winHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
const scrollPercent = (currentTop - headerHeight) / contentMath
|
||||||
|
return Math.max(0, Math.min(100, Math.round(scrollPercent * 100)))
|
||||||
|
}
|
||||||
|
})(),
|
||||||
|
|
||||||
|
addEventListenerPjax: (ele, event, fn, option = false) => {
|
||||||
|
ele.addEventListener(event, fn, option)
|
||||||
|
btf.addGlobalFn('pjaxSendOnce', () => {
|
||||||
|
ele.removeEventListener(event, fn, option)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
removeGlobalFnEvent: (key, parent = window) => {
|
||||||
|
const globalFn = parent.globalFn || {}
|
||||||
|
const keyObj = globalFn[key]
|
||||||
|
if (!keyObj) return
|
||||||
|
|
||||||
|
Object.keys(keyObj).forEach(i => keyObj[i]())
|
||||||
|
|
||||||
|
delete globalFn[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.btf = { ...window.btf, ...btfFn }
|
||||||
|
})()
|
||||||
Loading…
x
Reference in New Issue
Block a user