mirror of
https://github.com/jerryc127/hexo-theme-butterfly.git
synced 2026-04-08 12:07:06 +08:00
chore: 升級版本至 5.5.3 並合併開發分支功能更新
主要更新內容: - feat: 網站背景支援隨機數組配置,可從多個背景中隨機選擇 - feat: 增加文章分頁封面配置 pagination_cover 選項 - improvement: 優化代碼高亮工具複製按鈕的位置計算和邊界檢查 - improvement: 優化 Pjax 載入,script 標籤新增 defer 屬性 - improvement: 優化相關文章算法,提升性能並改進排序邏輯 - improvement: 優化側邊欄歸檔和分類模塊的性能 - improvement: 改善 TOC 滾動檢測和位置計算 - improvement: 優化 Umami Analytics 數據處理邏輯 - fix: 修復代碼塊工具欄圖標對齊和樣式問題 - fix: 修復夜間模式下圖片亮度調整 - fix: 修復閱讀模式按鈕樣式和功能 - chore: 更新多個依賴套件版本(algolia, fancybox, katex, mermaid, waline 等) 此次合併解決了所有衝突並整合了開發分支的所有新功能和優化。
This commit is contained in:
@@ -91,7 +91,8 @@ category_per_img:
|
||||
footer_img: false
|
||||
|
||||
# Website Background
|
||||
# Can set it to color or image url
|
||||
# Can set it to color, image URL or an array containing colors and/or image URLs
|
||||
# If an array is provided, a random background will be selected from the array on each load
|
||||
background:
|
||||
|
||||
cover:
|
||||
|
||||
@@ -52,7 +52,7 @@ div
|
||||
!= partial("includes/third-party/umami_analytics", {}, { cache: true })
|
||||
|
||||
if theme.busuanzi.site_uv || theme.busuanzi.site_pv || theme.busuanzi.page_pv
|
||||
script(async data-pjax src=url_for(theme.asset.busuanzi) || '//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js')
|
||||
script(async data-pjax src=theme.asset.busuanzi ? url_for(theme.asset.busuanzi) : '//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js')
|
||||
|
||||
!= partial('includes/third-party/search/index', {}, { cache: true })
|
||||
|
||||
|
||||
@@ -13,7 +13,29 @@ html(lang=config.language data-theme=theme.display_mode class=htmlClassHideAside
|
||||
!=partial('includes/loading/index', {}, {cache: true})
|
||||
|
||||
if theme.background
|
||||
#web_bg(style=getBgPath(theme.background))
|
||||
if !Array.isArray(theme.background)
|
||||
#web_bg.bg-animation(style=getBgPath(theme.background))
|
||||
else
|
||||
#web_bg.bg-animation
|
||||
- const bgStyleArr = theme.background.map(getBgPath)
|
||||
script.
|
||||
(() => {
|
||||
const arr = !{JSON.stringify(bgStyleArr)}
|
||||
const webBgDiv = document.getElementById('web_bg')
|
||||
|
||||
const setRandomBg = () => {
|
||||
webBgDiv.style = arr[Math.floor(Math.random() * arr.length)]
|
||||
requestAnimationFrame(() => webBgDiv.classList.add('bg-animation'))
|
||||
}
|
||||
|
||||
document.addEventListener('pjax:send', () => {
|
||||
webBgDiv.style = ''
|
||||
webBgDiv.classList.remove('bg-animation')
|
||||
})
|
||||
|
||||
document.addEventListener('pjax:complete', setRandomBg)
|
||||
document.addEventListener('DOMContentLoaded', setRandomBg)
|
||||
})()
|
||||
|
||||
!=partial('includes/sidebar', {}, {cache: true})
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ if page.total !== 1
|
||||
|
||||
a.pagination-related(class=className href=url_for(direction.path) title=direction.title)
|
||||
if direction.cover_type === 'img'
|
||||
img.cover(src=url_for(direction.cover) onerror=`onerror=null;src='${url_for(theme.error_img.post_page)}'` alt=`cover of ${key === 'prev' ? 'previous' : 'next'} post`)
|
||||
img.cover(src=url_for(direction.pagination_cover || direction.cover) onerror=`onerror=null;src='${url_for(theme.error_img.post_page)}'` alt=`cover of ${key === 'prev' ? 'previous' : 'next'} post`)
|
||||
else
|
||||
.cover(style=`background: ${direction.cover || 'var(--default-bg-color)'}`)
|
||||
|
||||
|
||||
9
layout/includes/third-party/pjax.pug
vendored
9
layout/includes/third-party/pjax.pug
vendored
@@ -14,9 +14,9 @@ if choose
|
||||
else
|
||||
- pjaxSelectors.unshift('meta[name="description"]')
|
||||
|
||||
script(src=url_for(theme.asset.pjax))
|
||||
script(src=url_for(theme.asset.pjax) defer)
|
||||
script.
|
||||
(() => {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const pjaxSelectors = !{JSON.stringify(pjaxSelectors)}
|
||||
|
||||
window.pjax = new Pjax({
|
||||
@@ -65,10 +65,9 @@ script.
|
||||
|
||||
document.addEventListener('pjax:error', e => {
|
||||
if (e.request.status === 404) {
|
||||
const usePjax = !{theme.pjax && theme.pjax.enable}
|
||||
!{theme.error_404 && theme.error_404.enable}
|
||||
? (usePjax ? pjax.loadUrl('!{url_for("/404.html")}') : window.location.href = '!{url_for("/404.html")}')
|
||||
? pjax.loadUrl('!{url_for("/404.html")}')
|
||||
: window.location.href = e.request.responseURL
|
||||
}
|
||||
})
|
||||
})()
|
||||
})
|
||||
6
layout/includes/third-party/prismjs.pug
vendored
6
layout/includes/third-party/prismjs.pug
vendored
@@ -17,7 +17,7 @@ if (syntax_highlighter === 'prismjs' || enable) && !preprocess
|
||||
btf.addGlobalFn('encrypt', highlightAll, 'prismjs')
|
||||
})()
|
||||
|
||||
script(src=url_for(prismjs_js))
|
||||
script(src=url_for(prismjs_autoloader))
|
||||
script(src=url_for(prismjs_js) defer)
|
||||
script(src=url_for(prismjs_autoloader) defer)
|
||||
if (line_number)
|
||||
script(src=url_for(prismjs_lineNumber_js))
|
||||
script(src=url_for(prismjs_lineNumber_js) defer)
|
||||
14
layout/includes/third-party/umami_analytics.pug
vendored
14
layout/includes/third-party/umami_analytics.pug
vendored
@@ -31,7 +31,7 @@ script.
|
||||
const getData = async (isPost) => {
|
||||
try {
|
||||
const now = Date.now()
|
||||
const keyUrl = isPost ? `&url=${window.location.pathname}` : ''
|
||||
const keyUrl = isPost ? `&url=${window.location.pathname}&path=${window.location.pathname}` : ''
|
||||
const headerList = { 'Accept': 'application/json' }
|
||||
|
||||
if (!{isServerURL}) {
|
||||
@@ -62,8 +62,8 @@ script.
|
||||
const pagePV = document.getElementById('umamiPV')
|
||||
if (pagePV) {
|
||||
const data = await getData(true)
|
||||
if (data && data.pageviews && typeof data.pageviews.value !== 'undefined') {
|
||||
pagePV.textContent = data.pageviews.value
|
||||
if (data && data.pageviews) {
|
||||
pagePV.textContent = typeof data.pageviews.value !== 'undefined' ? data.pageviews.value : data.pageviews
|
||||
} else {
|
||||
console.warn('Umami Analytics: Invalid page view data received')
|
||||
}
|
||||
@@ -75,8 +75,8 @@ script.
|
||||
|
||||
if (config.site_uv) {
|
||||
const siteUV = document.getElementById('umami-site-uv')
|
||||
if (siteUV && data && data.visitors && typeof data.visitors.value !== 'undefined') {
|
||||
siteUV.textContent = data.visitors.value
|
||||
if (siteUV && data && data.visitors) {
|
||||
siteUV.textContent = typeof data.visitors.value !== 'undefined' ? data.visitors.value : data.visitors
|
||||
} else if (siteUV) {
|
||||
console.warn('Umami Analytics: Invalid site UV data received')
|
||||
}
|
||||
@@ -84,8 +84,8 @@ script.
|
||||
|
||||
if (config.site_pv) {
|
||||
const sitePV = document.getElementById('umami-site-pv')
|
||||
if (sitePV && data && data.pageviews && typeof data.pageviews.value !== 'undefined') {
|
||||
sitePV.textContent = data.pageviews.value
|
||||
if (sitePV && data && data.pageviews) {
|
||||
sitePV.textContent = typeof data.pageviews.value !== 'undefined' ? data.pageviews.value : data.pageviews
|
||||
} else if (sitePV) {
|
||||
console.warn('Umami Analytics: Invalid site PV data received')
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "hexo-theme-butterfly",
|
||||
"version": "5.5.2",
|
||||
"version": "5.5.3",
|
||||
"description": "A Simple and Card UI Design theme for Hexo",
|
||||
"main": "package.json",
|
||||
"scripts": {
|
||||
|
||||
20
plugins.yml
20
plugins.yml
@@ -9,7 +9,7 @@ activate_power_mode:
|
||||
algolia_search:
|
||||
name: algoliasearch
|
||||
file: dist/lite/builds/browser.umd.js
|
||||
version: 5.43.0
|
||||
version: 5.46.0
|
||||
aplayer_css:
|
||||
name: aplayer
|
||||
file: dist/APlayer.min.css
|
||||
@@ -66,12 +66,12 @@ docsearch_css:
|
||||
name: '@docsearch/css'
|
||||
other_name: docsearch-css
|
||||
file: dist/style.css
|
||||
version: 4.3.1
|
||||
version: 4.3.2
|
||||
docsearch_js:
|
||||
name: '@docsearch/js'
|
||||
other_name: docsearch-js
|
||||
file: dist/umd/index.js
|
||||
version: 4.3.1
|
||||
version: 4.3.2
|
||||
egjs_infinitegrid:
|
||||
name: '@egjs/infinitegrid'
|
||||
other_name: egjs-infinitegrid
|
||||
@@ -80,12 +80,12 @@ egjs_infinitegrid:
|
||||
fancybox:
|
||||
name: '@fancyapps/ui'
|
||||
file: dist/fancybox/fancybox.umd.js
|
||||
version: 6.1.4
|
||||
version: 6.1.7
|
||||
other_name: fancyapps-ui
|
||||
fancybox_css:
|
||||
name: '@fancyapps/ui'
|
||||
file: dist/fancybox/fancybox.css
|
||||
version: 6.1.4
|
||||
version: 6.1.7
|
||||
other_name: fancyapps-ui
|
||||
fireworks:
|
||||
name: butterfly-extsrc
|
||||
@@ -112,12 +112,12 @@ katex:
|
||||
name: katex
|
||||
file: dist/katex.min.css
|
||||
other_name: KaTeX
|
||||
version: 0.16.25
|
||||
version: 0.16.27
|
||||
katex_copytex:
|
||||
name: katex
|
||||
file: dist/contrib/copy-tex.min.js
|
||||
other_name: KaTeX
|
||||
version: 0.16.25
|
||||
version: 0.16.27
|
||||
lazyload:
|
||||
name: vanilla-lazyload
|
||||
file: dist/lazyload.iife.min.js
|
||||
@@ -133,7 +133,7 @@ medium_zoom:
|
||||
mermaid:
|
||||
name: mermaid
|
||||
file: dist/mermaid.min.js
|
||||
version: 11.12.1
|
||||
version: 11.12.2
|
||||
meting_js:
|
||||
name: butterfly-extsrc
|
||||
file: metingjs/dist/Meting.min.js
|
||||
@@ -199,9 +199,9 @@ waline_css:
|
||||
name: '@waline/client'
|
||||
file: dist/waline.css
|
||||
other_name: waline
|
||||
version: 3.7.1
|
||||
version: 3.8.0
|
||||
waline_js:
|
||||
name: '@waline/client'
|
||||
file: dist/waline.js
|
||||
other_name: waline
|
||||
version: 3.7.1
|
||||
version: 3.8.0
|
||||
|
||||
@@ -46,45 +46,43 @@ hexo.extend.filter.register('before_generate', () => {
|
||||
}
|
||||
|
||||
const createCDNLink = (data, type, cond = '') => {
|
||||
Object.keys(data).forEach(key => {
|
||||
let { name, version, file, other_name } = data[key]
|
||||
const cdnjs_name = other_name || name
|
||||
const cdnjs_file = file.replace(/^[lib|dist]*\/|browser\//g, '')
|
||||
const min_cdnjs_file = minFile(cdnjs_file)
|
||||
return Object.keys(data).reduce((result, key) => {
|
||||
let { name, version, file, other_name: otherName } = data[key]
|
||||
const cdnjsName = otherName || name
|
||||
const cdnjsFile = file.replace(/^[lib|dist]*\/|browser\//g, '')
|
||||
const minCdnjsFile = minFile(cdnjsFile)
|
||||
if (cond === 'internal') file = `source/${file}`
|
||||
const min_file = minFile(file)
|
||||
const minFilePath = minFile(file)
|
||||
const verType = CDN.version ? (type === 'local' ? `?v=${version}` : `@${version}`) : ''
|
||||
|
||||
const value = {
|
||||
version,
|
||||
name,
|
||||
file,
|
||||
cdnjs_file,
|
||||
min_file,
|
||||
min_cdnjs_file,
|
||||
cdnjs_name
|
||||
cdnjs_file: cdnjsFile,
|
||||
min_file: minFilePath,
|
||||
min_cdnjs_file: minCdnjsFile,
|
||||
cdnjs_name: cdnjsName
|
||||
}
|
||||
|
||||
const cdnSource = {
|
||||
local: cond === 'internal' ? `${cdnjs_file + verType}` : `/pluginsSrc/${name}/${file + verType}`,
|
||||
jsdelivr: `https://cdn.jsdelivr.net/npm/${name}${verType}/${min_file}`,
|
||||
local: cond === 'internal' ? `${cdnjsFile + verType}` : `/pluginsSrc/${name}/${file + verType}`,
|
||||
jsdelivr: `https://cdn.jsdelivr.net/npm/${name}${verType}/${minFilePath}`,
|
||||
unpkg: `https://unpkg.com/${name}${verType}/${file}`,
|
||||
cdnjs: `https://cdnjs.cloudflare.com/ajax/libs/${cdnjs_name}/${version}/${min_cdnjs_file}`,
|
||||
cdnjs: `https://cdnjs.cloudflare.com/ajax/libs/${cdnjsName}/${version}/${minCdnjsFile}`,
|
||||
custom: (CDN.custom_format || '').replace(/\$\{(.+?)\}/g, (match, $1) => value[$1])
|
||||
}
|
||||
|
||||
data[key] = cdnSource[type]
|
||||
})
|
||||
|
||||
if (cond === 'internal') data.main_css = 'css/index.css' + (CDN.version ? `?v=${version}` : '')
|
||||
return data
|
||||
result[key] = cdnSource[type]
|
||||
return result
|
||||
}, cond === 'internal' ? { main_css: 'css/index.css' + (CDN.version ? `?v=${version}` : '') } : {})
|
||||
}
|
||||
|
||||
// delete null value
|
||||
const deleteNullValue = obj => {
|
||||
if (!obj) return
|
||||
if (!obj) return {}
|
||||
for (const i in obj) {
|
||||
obj[i] === null && delete obj[i]
|
||||
if (obj[i] === null) delete obj[i]
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ function checkHexoEnvironment (hexo) {
|
||||
if (major < requiredMajor || (major === requiredMajor && minor < requiredMinor)) {
|
||||
log.error('Please update Hexo to V5.3.0 or higher!')
|
||||
log.error('請把 Hexo 升級到 V5.3.0 或更高的版本!')
|
||||
process.exit(-1)
|
||||
throw new Error('Hexo version too old')
|
||||
}
|
||||
|
||||
// Check for deprecated configuration file
|
||||
@@ -26,7 +26,7 @@ function checkHexoEnvironment (hexo) {
|
||||
if (data && data.butterfly) {
|
||||
log.error("'butterfly.yml' is deprecated. Please use '_config.butterfly.yml'")
|
||||
log.error("'butterfly.yml' 已經棄用,請使用 '_config.butterfly.yml'")
|
||||
process.exit(-1)
|
||||
throw new Error('Deprecated configuration file')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,55 +5,64 @@
|
||||
'use strict'
|
||||
|
||||
hexo.extend.generator.register('post', locals => {
|
||||
const previousIndexes = []
|
||||
const imgTestReg = /\.(png|jpe?g|gif|svg|webp|avif)(\?.*)?$/i
|
||||
const { post_asset_folder: postAssetFolder } = hexo.config
|
||||
const { cover: { default_cover: defaultCover } } = hexo.theme.config
|
||||
|
||||
const getRandomCover = defaultCover => {
|
||||
if (!defaultCover) return false
|
||||
if (!Array.isArray(defaultCover)) return defaultCover
|
||||
function * createCoverGenerator () {
|
||||
if (!defaultCover) {
|
||||
while (true) yield false
|
||||
}
|
||||
if (!Array.isArray(defaultCover)) {
|
||||
while (true) yield defaultCover
|
||||
}
|
||||
|
||||
const coverCount = defaultCover.length
|
||||
|
||||
if (coverCount === 1) {
|
||||
return defaultCover[0]
|
||||
while (true) yield defaultCover[0]
|
||||
}
|
||||
|
||||
const maxPreviousIndexes = coverCount === 2 ? 1 : (coverCount === 3 ? 2 : 3)
|
||||
const maxHistory = Math.min(3, coverCount - 1)
|
||||
const history = []
|
||||
|
||||
let index
|
||||
do {
|
||||
index = Math.floor(Math.random() * coverCount)
|
||||
} while (previousIndexes.includes(index) && previousIndexes.length < coverCount)
|
||||
while (true) {
|
||||
let index
|
||||
do {
|
||||
index = Math.floor(Math.random() * coverCount)
|
||||
} while (history.includes(index))
|
||||
|
||||
previousIndexes.push(index)
|
||||
if (previousIndexes.length > maxPreviousIndexes) {
|
||||
previousIndexes.shift()
|
||||
history.push(index)
|
||||
if (history.length > maxHistory) history.shift()
|
||||
|
||||
yield defaultCover[index]
|
||||
}
|
||||
|
||||
return defaultCover[index]
|
||||
}
|
||||
|
||||
const coverGenerator = createCoverGenerator()
|
||||
|
||||
const handleImg = data => {
|
||||
const imgTestReg = /\.(png|jpe?g|gif|svg|webp|avif)(\?.*)?$/i
|
||||
let { cover: coverVal, top_img: topImg } = data
|
||||
let { cover: coverVal, top_img: topImg, pagination_cover: paginationCover } = data
|
||||
|
||||
// Add path to top_img and cover if post_asset_folder is enabled
|
||||
if (hexo.config.post_asset_folder) {
|
||||
if (postAssetFolder) {
|
||||
if (topImg && topImg.indexOf('/') === -1 && imgTestReg.test(topImg)) {
|
||||
data.top_img = `${data.path}${topImg}`
|
||||
}
|
||||
if (coverVal && coverVal.indexOf('/') === -1 && imgTestReg.test(coverVal)) {
|
||||
data.cover = `${data.path}${coverVal}`
|
||||
}
|
||||
if (paginationCover && paginationCover.indexOf('/') === -1 && imgTestReg.test(paginationCover)) {
|
||||
data.pagination_cover = `${data.path}${paginationCover}`
|
||||
}
|
||||
}
|
||||
|
||||
if (coverVal === false) return data
|
||||
|
||||
// If cover is not set, use random cover
|
||||
if (!coverVal) {
|
||||
const { cover: { default_cover: defaultCover } } = hexo.theme.config
|
||||
const randomCover = getRandomCover(defaultCover)
|
||||
const randomCover = coverGenerator.next().value
|
||||
data.cover = randomCover
|
||||
coverVal = randomCover // update coverVal
|
||||
coverVal = randomCover
|
||||
}
|
||||
|
||||
if (coverVal && (coverVal.indexOf('//') !== -1 || imgTestReg.test(coverVal))) {
|
||||
@@ -63,7 +72,6 @@ hexo.extend.generator.register('post', locals => {
|
||||
return data
|
||||
}
|
||||
|
||||
// https://github.com/hexojs/hexo/blob/master/lib%2Fplugins%2Fgenerator%2Fpost.ts
|
||||
const posts = locals.posts.sort('date').toArray()
|
||||
const { length } = posts
|
||||
|
||||
|
||||
@@ -17,56 +17,43 @@ hexo.extend.helper.register('aside_archives', function (options = {}) {
|
||||
// Optimize locale handling
|
||||
const lang = toMomentLocale(page.lang || page.language || language)
|
||||
|
||||
// Memoize comparison function to improve performance
|
||||
const compareFunc =
|
||||
type === 'monthly'
|
||||
? (yearA, monthA, yearB, monthB) => yearA === yearB && monthA === monthB
|
||||
: (yearA, yearB) => yearA === yearB
|
||||
|
||||
// Early return if no posts
|
||||
if (!site.posts.length) return ''
|
||||
|
||||
// Use reduce for more efficient data processing
|
||||
const data = site.posts.sort('date', order).reduce((acc, post) => {
|
||||
let date = post.date.clone()
|
||||
if (timezone) date = date.tz(timezone)
|
||||
|
||||
const archives = new Map()
|
||||
site.posts.forEach(post => {
|
||||
const date = post.date
|
||||
const year = date.year()
|
||||
const month = date.month() + 1
|
||||
const key = type === 'yearly' ? year : `${year}-${month}`
|
||||
|
||||
if (lang) date = date.locale(lang)
|
||||
|
||||
// Find or create archive entry
|
||||
const lastEntry = acc[acc.length - 1]
|
||||
|
||||
if (type === 'yearly') {
|
||||
const existingYearIndex = acc.findIndex(entry => entry.year === year)
|
||||
if (existingYearIndex !== -1) {
|
||||
acc[existingYearIndex].count++
|
||||
} else {
|
||||
// 否則創建新條目
|
||||
acc.push({
|
||||
name: date.format(format),
|
||||
year,
|
||||
month,
|
||||
count: 1
|
||||
})
|
||||
}
|
||||
if (archives.has(key)) {
|
||||
archives.get(key).count++
|
||||
} else {
|
||||
if (!lastEntry || !compareFunc(lastEntry.year, lastEntry.month, year, month)) {
|
||||
acc.push({
|
||||
name: date.format(format),
|
||||
year,
|
||||
month,
|
||||
count: 1
|
||||
})
|
||||
} else {
|
||||
lastEntry.count++
|
||||
}
|
||||
archives.set(key, {
|
||||
year,
|
||||
month,
|
||||
count: 1,
|
||||
date // Store date object for later formatting
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return acc
|
||||
}, [])
|
||||
const data = Array.from(archives.values()).sort((a, b) => {
|
||||
if (order === -1) {
|
||||
return b.year - a.year || b.month - a.month
|
||||
}
|
||||
return a.year - b.year || a.month - b.month
|
||||
})
|
||||
|
||||
// Format names after aggregation
|
||||
data.forEach(item => {
|
||||
let date = item.date.clone()
|
||||
if (timezone) date = date.tz(timezone)
|
||||
if (lang) date = date.locale(lang)
|
||||
item.name = date.format(format)
|
||||
delete item.date // Clean up
|
||||
})
|
||||
|
||||
// Create link generator function
|
||||
const createArchiveLink = item => {
|
||||
|
||||
@@ -19,15 +19,33 @@ hexo.extend.helper.register('aside_categories', function (categories, options =
|
||||
const expandClass = isExpand && options.expand === true ? 'expand' : ''
|
||||
const buttonLabel = this._p('aside.more_button')
|
||||
|
||||
const prepareQuery = parent => {
|
||||
const query = parent ? { parent } : { parent: { $exists: false } }
|
||||
return categories.find(query).sort(orderby, order).filter(cat => cat.length)
|
||||
const categoryMap = new Map()
|
||||
categories.forEach(cat => {
|
||||
if (cat.length) {
|
||||
const parentId = cat.parent || 'root'
|
||||
if (!categoryMap.has(parentId)) {
|
||||
categoryMap.set(parentId, [])
|
||||
}
|
||||
categoryMap.get(parentId).push(cat)
|
||||
}
|
||||
})
|
||||
|
||||
const sortFn = (a, b) => {
|
||||
const valA = a[orderby]
|
||||
const valB = b[orderby]
|
||||
if (valA < valB) return -order
|
||||
if (valA > valB) return order
|
||||
return 0
|
||||
}
|
||||
|
||||
const hierarchicalList = (remaining, level = 0, parent) => {
|
||||
for (const list of categoryMap.values()) {
|
||||
list.sort(sortFn)
|
||||
}
|
||||
|
||||
const hierarchicalList = (remaining, level = 0, parentId = 'root') => {
|
||||
let result = ''
|
||||
if (remaining > 0) {
|
||||
prepareQuery(parent).forEach(cat => {
|
||||
if (remaining > 0 && categoryMap.has(parentId)) {
|
||||
categoryMap.get(parentId).forEach(cat => {
|
||||
if (remaining > 0) {
|
||||
remaining -= 1
|
||||
let child = ''
|
||||
@@ -37,7 +55,8 @@ hexo.extend.helper.register('aside_categories', function (categories, options =
|
||||
remaining = childList.remaining
|
||||
}
|
||||
|
||||
const parentClass = isExpand && !parent && child ? 'parent' : ''
|
||||
const isTopLevel = parentId === 'root'
|
||||
const parentClass = isExpand && isTopLevel && child ? 'parent' : ''
|
||||
result += `<li class="card-category-list-item ${parentClass}">`
|
||||
result += `<a class="card-category-list-link" href="${this.url_for(cat.path)}">`
|
||||
result += `<span class="card-category-list-name">${cat.name}</span>`
|
||||
@@ -46,7 +65,7 @@ hexo.extend.helper.register('aside_categories', function (categories, options =
|
||||
result += `<span class="card-category-list-count">${cat.length}</span>`
|
||||
}
|
||||
|
||||
if (isExpand && !parent && child) {
|
||||
if (isExpand && isTopLevel && child) {
|
||||
result += `<i class="fas fa-caret-left ${expandClass}"></i>`
|
||||
}
|
||||
|
||||
|
||||
@@ -8,28 +8,29 @@ hexo.extend.helper.register('getArchiveLength', function () {
|
||||
// Archives Page
|
||||
if (!year) return posts.length
|
||||
|
||||
// Function to generate a unique key based on the granularity
|
||||
const getKey = (post, type) => {
|
||||
const date = post.date.clone()
|
||||
const y = date.year()
|
||||
const m = date.month() + 1
|
||||
const d = date.date()
|
||||
if (type === 'year') return `${y}`
|
||||
if (type === 'month') return `${y}-${m}`
|
||||
if (type === 'day') return `${y}-${m}-${d}`
|
||||
}
|
||||
|
||||
// Create a map to count posts per period
|
||||
const mapData = this.fragment_cache('createArchiveObj', () => {
|
||||
const map = new Map()
|
||||
posts.forEach(post => {
|
||||
const keyYear = getKey(post, 'year')
|
||||
const keyMonth = getKey(post, 'month')
|
||||
const keyDay = getKey(post, 'day')
|
||||
const date = post.date
|
||||
const y = date.year()
|
||||
const m = date.month() + 1
|
||||
const d = date.date()
|
||||
|
||||
if (yearly) map.set(keyYear, (map.get(keyYear) || 0) + 1)
|
||||
if (monthly) map.set(keyMonth, (map.get(keyMonth) || 0) + 1)
|
||||
if (daily) map.set(keyDay, (map.get(keyDay) || 0) + 1)
|
||||
if (yearly) {
|
||||
const keyYear = `${y}`
|
||||
map.set(keyYear, (map.get(keyYear) || 0) + 1)
|
||||
}
|
||||
|
||||
if (monthly) {
|
||||
const keyMonth = `${y}-${m}`
|
||||
map.set(keyMonth, (map.get(keyMonth) || 0) + 1)
|
||||
}
|
||||
|
||||
if (daily) {
|
||||
const keyDay = `${y}-${m}-${d}`
|
||||
map.set(keyDay, (map.get(keyDay) || 0) + 1)
|
||||
}
|
||||
})
|
||||
return map
|
||||
})
|
||||
|
||||
@@ -5,6 +5,12 @@ const { prettyUrls } = require('hexo-util')
|
||||
const crypto = require('crypto')
|
||||
const moment = require('moment-timezone')
|
||||
|
||||
const absoluteUrlPattern = /^(?:[a-z][a-z\d+.-]*:)?\/\//i
|
||||
const relativeUrlPattern = /^(\.\/|\.\.\/|\/|[^/]+\/).*$/
|
||||
const colorPattern = /^(#|rgb|rgba|hsl|hsla)/i
|
||||
const simpleFilePattern = /\.(png|jpg|jpeg|gif|bmp|webp|svg|tiff)$/i
|
||||
const archiveRegex = /\/archives\//
|
||||
|
||||
hexo.extend.helper.register('truncate', truncateContent)
|
||||
|
||||
hexo.extend.helper.register('postDesc', data => {
|
||||
@@ -20,32 +26,27 @@ hexo.extend.helper.register('cloudTags', function (options = {}) {
|
||||
}
|
||||
|
||||
const sizes = [...new Set(source.map(tag => tag.length).sort((a, b) => a - b))]
|
||||
const sizeMap = new Map(sizes.map((size, index) => [size, index]))
|
||||
const length = sizes.length - 1
|
||||
|
||||
const getRandomColor = () => {
|
||||
const randomColor = () => Math.floor(Math.random() * 201)
|
||||
const r = randomColor()
|
||||
const g = randomColor()
|
||||
const b = randomColor()
|
||||
const r = Math.floor(Math.random() * 201)
|
||||
const g = Math.floor(Math.random() * 201)
|
||||
const b = Math.floor(Math.random() * 201)
|
||||
return `rgb(${Math.max(r, 50)}, ${Math.max(g, 50)}, ${Math.max(b, 50)})`
|
||||
}
|
||||
|
||||
const generateStyle = (size, unit, page) => {
|
||||
if (page === 'tags') {
|
||||
return `font-size: ${parseFloat(size.toFixed(2)) + unit}; background-color: ${getRandomColor()};`
|
||||
} else {
|
||||
return `font-size: ${parseFloat(size.toFixed(2)) + unit}; color: ${getRandomColor()};`
|
||||
}
|
||||
const colorStyle = page === 'tags' ? `background-color: ${getRandomColor()};` : `color: ${getRandomColor()};`
|
||||
return `font-size: ${parseFloat(size.toFixed(2))}${unit}; ${colorStyle}`
|
||||
}
|
||||
|
||||
const length = sizes.length - 1
|
||||
const result = source.sort(orderby, order).map(tag => {
|
||||
const ratio = length ? sizes.indexOf(tag.length) / length : 0
|
||||
return source.sort(orderby, order).map(tag => {
|
||||
const ratio = length ? sizeMap.get(tag.length) / length : 0
|
||||
const size = minfontsize + ((maxfontsize - minfontsize) * ratio)
|
||||
const style = generateStyle(size, unit, page)
|
||||
return `<a href="${env.url_for(tag.path)}" style="${style}">${tag.name}</a>`
|
||||
}).join('')
|
||||
|
||||
return result
|
||||
})
|
||||
|
||||
hexo.extend.helper.register('urlNoIndex', function (url = null, trailingIndex = false, trailingHtml = false) {
|
||||
@@ -77,7 +78,7 @@ hexo.extend.helper.register('findArchivesTitle', function (page, menu, date) {
|
||||
if (result) return result
|
||||
}
|
||||
|
||||
if (/\/archives\//.test(m[key])) {
|
||||
if (archiveRegex.test(m[key])) {
|
||||
return key
|
||||
}
|
||||
}
|
||||
@@ -89,13 +90,9 @@ hexo.extend.helper.register('findArchivesTitle', function (page, menu, date) {
|
||||
hexo.extend.helper.register('getBgPath', function (path) {
|
||||
if (!path) return ''
|
||||
|
||||
const absoluteUrlPattern = /^(?:[a-z][a-z\d+.-]*:)?\/\//i
|
||||
const relativeUrlPattern = /^(\.\/|\.\.\/|\/|[^/]+\/).*$/
|
||||
const colorPattern = /^(#|rgb|rgba|hsl|hsla)/i
|
||||
|
||||
if (colorPattern.test(path)) {
|
||||
return `background-color: ${path};`
|
||||
} else if (absoluteUrlPattern.test(path) || relativeUrlPattern.test(path)) {
|
||||
} else if (absoluteUrlPattern.test(path) || relativeUrlPattern.test(path) || simpleFilePattern.test(path)) {
|
||||
return `background-image: url(${this.url_for(path)});`
|
||||
} else {
|
||||
return `background: ${path};`
|
||||
@@ -104,39 +101,34 @@ hexo.extend.helper.register('getBgPath', function (path) {
|
||||
|
||||
hexo.extend.helper.register('shuoshuoFN', (data, page) => {
|
||||
const { limit } = page
|
||||
let finalResult = ''
|
||||
|
||||
// Shallow copy to avoid mutating original data
|
||||
let processedData = data.map(item => ({ ...item }))
|
||||
|
||||
// Check if limit.value is a valid date
|
||||
const isValidDate = date => !isNaN(Date.parse(date))
|
||||
|
||||
// order by date
|
||||
const orderByDate = data => data.sort((a, b) => Date.parse(b.date) - Date.parse(a.date))
|
||||
processedData.sort((a, b) => Date.parse(b.date) - Date.parse(a.date))
|
||||
|
||||
// Apply number limit or time limit conditionally
|
||||
const limitData = data => {
|
||||
if (limit && limit.type === 'num' && limit.value > 0) {
|
||||
return data.slice(0, limit.value)
|
||||
} else if (limit && limit.type === 'date' && isValidDate(limit.value)) {
|
||||
const limitDate = Date.parse(limit.value)
|
||||
return data.filter(item => Date.parse(item.date) >= limitDate)
|
||||
}
|
||||
|
||||
return data
|
||||
if (limit && limit.type === 'num' && limit.value > 0) {
|
||||
processedData = processedData.slice(0, limit.value)
|
||||
} else if (limit && limit.type === 'date' && isValidDate(limit.value)) {
|
||||
const limitDate = Date.parse(limit.value)
|
||||
processedData = processedData.filter(item => Date.parse(item.date) >= limitDate)
|
||||
}
|
||||
|
||||
orderByDate(data)
|
||||
finalResult = limitData(data)
|
||||
|
||||
// This is a hack method, because hexo treats time as UTC time
|
||||
// so you need to manually convert the time zone
|
||||
finalResult.forEach(item => {
|
||||
processedData.forEach(item => {
|
||||
const utcDate = moment.utc(item.date).format('YYYY-MM-DD HH:mm:ss')
|
||||
item.date = moment.tz(utcDate, hexo.config.timezone).format('YYYY-MM-DD HH:mm:ss')
|
||||
// markdown
|
||||
item.content = hexo.render.renderSync({ text: item.content, engine: 'markdown' })
|
||||
})
|
||||
|
||||
return finalResult
|
||||
return processedData
|
||||
})
|
||||
|
||||
hexo.extend.helper.register('getPageType', (page, isHome) => {
|
||||
|
||||
@@ -9,14 +9,22 @@
|
||||
|
||||
const { postDesc } = require('../common/postDesc')
|
||||
|
||||
hexo.extend.helper.register('related_posts', function (currentPost, allPosts) {
|
||||
let relatedPosts = []
|
||||
hexo.extend.helper.register('related_posts', function (currentPost) {
|
||||
const relatedPosts = new Map()
|
||||
const tagsData = currentPost.tags
|
||||
tagsData.length && tagsData.forEach(function (tag) {
|
||||
allPosts.forEach(function (post) {
|
||||
if (currentPost.path !== post.path && isTagRelated(tag.name, post.tags)) {
|
||||
|
||||
if (!tagsData || !tagsData.length) return ''
|
||||
|
||||
tagsData.forEach(tag => {
|
||||
const posts = tag.posts
|
||||
posts.forEach(post => {
|
||||
if (currentPost.path === post.path) return
|
||||
|
||||
if (relatedPosts.has(post.path)) {
|
||||
relatedPosts.get(post.path).weight += 1
|
||||
} else {
|
||||
const getPostDesc = post.postDesc || postDesc(post, hexo)
|
||||
const relatedPost = {
|
||||
relatedPosts.set(post.path, {
|
||||
title: post.title,
|
||||
path: post.path,
|
||||
cover: post.cover,
|
||||
@@ -24,22 +32,17 @@ hexo.extend.helper.register('related_posts', function (currentPost, allPosts) {
|
||||
weight: 1,
|
||||
updated: post.updated,
|
||||
created: post.date,
|
||||
postDesc: getPostDesc
|
||||
}
|
||||
const index = findItem(relatedPosts, 'path', post.path)
|
||||
if (index !== -1) {
|
||||
relatedPosts[index].weight += 1
|
||||
} else {
|
||||
relatedPosts.push(relatedPost)
|
||||
}
|
||||
postDesc: getPostDesc,
|
||||
random: Math.random()
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
if (relatedPosts.length === 0) {
|
||||
if (relatedPosts.size === 0) {
|
||||
return ''
|
||||
}
|
||||
let result = ''
|
||||
|
||||
const hexoConfig = hexo.config
|
||||
const config = hexo.theme.config
|
||||
|
||||
@@ -47,51 +50,42 @@ hexo.extend.helper.register('related_posts', function (currentPost, allPosts) {
|
||||
const dateType = config.related_post.date_type || 'created'
|
||||
const headlineLang = this._p('post.recommend')
|
||||
|
||||
relatedPosts = relatedPosts.sort(compare('weight'))
|
||||
|
||||
if (relatedPosts.length > 0) {
|
||||
result += '<div class="relatedPosts">'
|
||||
result += `<div class="headline"><i class="fas fa-thumbs-up fa-fw"></i><span>${headlineLang}</span></div>`
|
||||
result += '<div class="relatedPosts-list">'
|
||||
|
||||
for (let i = 0; i < Math.min(relatedPosts.length, limitNum); i++) {
|
||||
let { cover, title, path, cover_type, created, updated, postDesc } = relatedPosts[i]
|
||||
const { escape_html, url_for, date } = this
|
||||
cover = cover || 'var(--default-bg-color)'
|
||||
title = escape_html(title)
|
||||
const className = postDesc ? 'pagination-related' : 'pagination-related no-desc'
|
||||
result += `<a class="${className}" href="${url_for(path)}" title="${title}">`
|
||||
if (cover_type === 'img') {
|
||||
result += `<img class="cover" src="${url_for(cover)}" alt="cover">`
|
||||
} else {
|
||||
result += `<div class="cover" style="background: ${cover}"></div>`
|
||||
}
|
||||
if (dateType === 'created') {
|
||||
result += `<div class="info text-center"><div class="info-1"><div class="info-item-1"><i class="far fa-calendar-alt fa-fw"></i> ${date(created, hexoConfig.date_format)}</div>`
|
||||
} else {
|
||||
result += `<div class="info text-center"><div class="info-1"><div class="info-item-1"><i class="fas fa-history fa-fw"></i> ${date(updated, hexoConfig.date_format)}</div>`
|
||||
}
|
||||
result += `<div class="info-item-2">${title}</div></div>`
|
||||
|
||||
if (postDesc) {
|
||||
result += `<div class="info-2"><div class="info-item-1">${postDesc}</div></div>`
|
||||
}
|
||||
result += '</div></a>'
|
||||
const relatedPostsList = Array.from(relatedPosts.values()).sort((a, b) => {
|
||||
if (b.weight !== a.weight) {
|
||||
return b.weight - a.weight
|
||||
}
|
||||
return b.random - a.random
|
||||
})
|
||||
|
||||
result += '</div></div>'
|
||||
return result
|
||||
let result = '<div class="relatedPosts">'
|
||||
result += `<div class="headline"><i class="fas fa-thumbs-up fa-fw"></i><span>${headlineLang}</span></div>`
|
||||
result += '<div class="relatedPosts-list">'
|
||||
|
||||
for (let i = 0; i < Math.min(relatedPostsList.length, limitNum); i++) {
|
||||
let { cover, title, path, cover_type, created, updated, postDesc } = relatedPostsList[i]
|
||||
const { escape_html, url_for, date } = this
|
||||
cover = cover || 'var(--default-bg-color)'
|
||||
title = escape_html(title)
|
||||
const className = postDesc ? 'pagination-related' : 'pagination-related no-desc'
|
||||
result += `<a class="${className}" href="${url_for(path)}" title="${title}">`
|
||||
if (cover_type === 'img') {
|
||||
result += `<img class="cover" src="${url_for(cover)}" alt="cover">`
|
||||
} else {
|
||||
result += `<div class="cover" style="background: ${cover}"></div>`
|
||||
}
|
||||
if (dateType === 'created') {
|
||||
result += `<div class="info text-center"><div class="info-1"><div class="info-item-1"><i class="far fa-calendar-alt fa-fw"></i> ${date(created, hexoConfig.date_format)}</div>`
|
||||
} else {
|
||||
result += `<div class="info text-center"><div class="info-1"><div class="info-item-1"><i class="fas fa-history fa-fw"></i> ${date(updated, hexoConfig.date_format)}</div>`
|
||||
}
|
||||
result += `<div class="info-item-2">${title}</div></div>`
|
||||
|
||||
if (postDesc) {
|
||||
result += `<div class="info-2"><div class="info-item-1">${postDesc}</div></div>`
|
||||
}
|
||||
result += '</div></a>'
|
||||
}
|
||||
|
||||
result += '</div></div>'
|
||||
return result
|
||||
})
|
||||
|
||||
function isTagRelated (tagName, tags) {
|
||||
return tags.some(tag => tag.name === tagName)
|
||||
}
|
||||
|
||||
function findItem (arrayToSearch, attr, val) {
|
||||
return arrayToSearch.findIndex(item => item[attr] === val)
|
||||
}
|
||||
|
||||
function compare (attr) {
|
||||
return (a, b) => b[attr] - a[attr]
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ if hexo-config('enter_transitions')
|
||||
animation: titleScale 1s
|
||||
|
||||
canvas:not(#ribbon-canvas),
|
||||
#web_bg
|
||||
#web_bg.bg-animation
|
||||
animation: to_show 4s
|
||||
|
||||
#ribbon-canvas
|
||||
|
||||
@@ -113,6 +113,10 @@ $code-block
|
||||
margin: 2px
|
||||
|
||||
i
|
||||
display: inline-flex
|
||||
justify-content: center
|
||||
align-items: center
|
||||
padding: 5px
|
||||
cursor: pointer
|
||||
transition: all .3s
|
||||
|
||||
@@ -128,7 +132,7 @@ $code-block
|
||||
|
||||
if !$highlight_macstyle
|
||||
& > .macStyle
|
||||
padding: 0
|
||||
margin: 0
|
||||
|
||||
.code-lang
|
||||
flex: 1 1 auto
|
||||
@@ -182,6 +186,7 @@ if $highlight_macstyle
|
||||
.highlight-tools
|
||||
.macStyle
|
||||
display: flex
|
||||
padding: 3px
|
||||
|
||||
& > *
|
||||
margin-right: 8px
|
||||
|
||||
@@ -130,6 +130,7 @@ if hexo-config('darkmode.enable') || hexo-config('display_mode') == 'dark'
|
||||
|
||||
// hide-tags
|
||||
.hide-button,
|
||||
.toggle-button,
|
||||
#post-outdate-notice,
|
||||
.error-img,
|
||||
.container iframe,
|
||||
@@ -140,9 +141,9 @@ if hexo-config('darkmode.enable') || hexo-config('display_mode') == 'dark'
|
||||
|
||||
img:not(.cover)
|
||||
if hexo-config('lazyload.enable') && hexo-config('lazyload.blur') && !hexo-config('lazyload.placeholder')
|
||||
filter: blur(0) brightness(.8)
|
||||
filter: blur(0) brightness(.88) contrast(.95)
|
||||
else
|
||||
filter: brightness(.8)
|
||||
filter: brightness(.88) contrast(.95)
|
||||
|
||||
#aside-content .aside-list > .aside-list-item:not(:last-child)
|
||||
border-bottom: 1px dashed alpha(#FFFFFF, .1)
|
||||
|
||||
@@ -42,6 +42,7 @@ if hexo-config('readmode')
|
||||
font-size: 16px
|
||||
transition: background .3s
|
||||
addBorderRadius(8)
|
||||
@extend .btn-effects
|
||||
|
||||
+maxWidth768()
|
||||
top: initial
|
||||
|
||||
@@ -39,11 +39,26 @@
|
||||
border: 1px solid $tag-hide-toggle-bg
|
||||
addBorderRadius(5, true)
|
||||
|
||||
& > .toggle-content
|
||||
margin: 30px 24px
|
||||
|
||||
& > .toggle-button
|
||||
padding: 6px 15px
|
||||
background: $tag-hide-toggle-bg
|
||||
color: #1F2D3D
|
||||
list-style: none
|
||||
cursor: pointer
|
||||
|
||||
& > .toggle-content
|
||||
margin: 30px 24px
|
||||
&::-webkit-details-marker
|
||||
display: none
|
||||
|
||||
&::before
|
||||
@extend .fontawesomeIcon
|
||||
margin-right: 8px
|
||||
content: '\f0d7'
|
||||
transition: transform .3s ease
|
||||
transform: rotate(-90deg)
|
||||
transform-origin: center center
|
||||
|
||||
&[open] summary::before
|
||||
transform: rotate(0)
|
||||
@@ -84,10 +84,17 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const buttonRect = ele.getBoundingClientRect()
|
||||
const scrollTop = window.pageYOffset || document.documentElement.scrollTop
|
||||
const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft
|
||||
const finalTop = buttonRect.top + scrollTop - 40
|
||||
const finalLeft = buttonRect.left + scrollLeft + buttonRect.width / 2
|
||||
|
||||
const topValue = ele.closest('figure.highlight').classList.contains('code-fullpage') ? finalTop + 60 : finalTop
|
||||
// X-axis boundary check
|
||||
const halfWidth = newEle.offsetWidth / 2
|
||||
const centerLeft = buttonRect.left + scrollLeft + buttonRect.width / 2
|
||||
const finalLeft = Math.max(halfWidth + 10, Math.min(window.innerWidth - halfWidth - 10, centerLeft))
|
||||
|
||||
// Show tooltip below button if too close to top
|
||||
const normalTop = buttonRect.top + scrollTop - 40
|
||||
const shouldShowBelow = buttonRect.top < 60 || normalTop < 10
|
||||
|
||||
const topValue = shouldShowBelow ? buttonRect.top + scrollTop + buttonRect.height + 10 : normalTop
|
||||
|
||||
newEle.style.cssText = `
|
||||
top: ${topValue + 10}px;
|
||||
@@ -111,6 +118,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}, 800)
|
||||
}
|
||||
}
|
||||
|
||||
const copy = async (text, ctx) => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text)
|
||||
@@ -155,6 +163,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
// 獲取隱藏狀態下元素的真實高度
|
||||
const getActualHeight = item => {
|
||||
if (item.offsetHeight > 0) return item.offsetHeight
|
||||
const hiddenElements = new Map()
|
||||
|
||||
const fix = () => {
|
||||
@@ -541,17 +550,29 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const $articleList = $article.querySelectorAll('h1,h2,h3,h4,h5,h6')
|
||||
let detectItem = ''
|
||||
|
||||
// Optimization: Cache header positions
|
||||
let headerList = []
|
||||
const updateHeaderPositions = () => {
|
||||
headerList = Array.from($articleList).map(ele => ({
|
||||
ele,
|
||||
top: btf.getEleTop(ele),
|
||||
id: ele.id
|
||||
}))
|
||||
}
|
||||
|
||||
updateHeaderPositions()
|
||||
btf.addEventListenerPjax(window, 'resize', btf.throttle(updateHeaderPositions, 200))
|
||||
|
||||
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) : ''
|
||||
for (let i = 0; i < headerList.length; i++) {
|
||||
const item = headerList[i]
|
||||
if (top > item.top - 80) {
|
||||
currentId = item.id ? '#' + encodeURI(item.id) : ''
|
||||
currentIndex = i
|
||||
} else {
|
||||
break
|
||||
@@ -629,7 +650,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
$body.classList.add('read-mode')
|
||||
newEle.type = 'button'
|
||||
newEle.className = 'fas fa-sign-out-alt exit-readmode'
|
||||
newEle.className = 'exit-readmode'
|
||||
newEle.innerHTML = '<i class="fas fa-sign-out-alt"></i>'
|
||||
newEle.addEventListener('click', exitReadMode)
|
||||
$body.appendChild(newEle)
|
||||
},
|
||||
|
||||
@@ -14,37 +14,35 @@
|
||||
}
|
||||
},
|
||||
|
||||
throttle: function (func, wait, options = {}) {
|
||||
let timeout, context, args
|
||||
throttle: (func, wait, options = {}) => {
|
||||
let timeout, 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
|
||||
func(...args)
|
||||
if (!timeout) args = null
|
||||
}
|
||||
|
||||
const throttled = (...params) => {
|
||||
return (...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
|
||||
func(...args)
|
||||
if (!timeout) args = null
|
||||
} else if (!timeout && options.trailing !== false) {
|
||||
timeout = setTimeout(later, remaining)
|
||||
}
|
||||
}
|
||||
|
||||
return throttled
|
||||
},
|
||||
|
||||
overflowPaddingR: {
|
||||
@@ -169,17 +167,7 @@
|
||||
|
||||
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
|
||||
},
|
||||
getEleTop: ele => ele.getBoundingClientRect().top + window.scrollY,
|
||||
|
||||
loadLightbox: ele => {
|
||||
const service = GLOBAL_CONFIG.lightbox
|
||||
@@ -190,7 +178,7 @@
|
||||
}
|
||||
|
||||
if (service === 'fancybox') {
|
||||
Array.from(ele).forEach(i => {
|
||||
ele.forEach(i => {
|
||||
if (i.parentNode.tagName !== 'A') {
|
||||
const dataSrc = i.dataset.lazySrc || i.src
|
||||
const dataCaption = i.title || i.alt || ''
|
||||
|
||||
Reference in New Issue
Block a user