diff --git a/languages/zh-CN.yml b/languages/zh-CN.yml index 454f589..782e013 100644 --- a/languages/zh-CN.yml +++ b/languages/zh-CN.yml @@ -49,7 +49,7 @@ search: pagination: prev: 上一篇 next: 下一篇 - page_info: '第 ${current} 页 / 共 ${total} 页' + page_info: '第 ${current} 頁 / 共 ${total} 頁' comment: 评论 diff --git a/layout/includes/third-party/pjax.pug b/layout/includes/third-party/pjax.pug index 55158b2..9c97c19 100644 --- a/layout/includes/third-party/pjax.pug +++ b/layout/includes/third-party/pjax.pug @@ -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 } }) - })() \ No newline at end of file + }) \ No newline at end of file diff --git a/layout/includes/third-party/prismjs.pug b/layout/includes/third-party/prismjs.pug index b8bc784..0c866ab 100644 --- a/layout/includes/third-party/prismjs.pug +++ b/layout/includes/third-party/prismjs.pug @@ -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)) \ No newline at end of file + script(src=url_for(prismjs_lineNumber_js) defer) \ No newline at end of file diff --git a/package.json b/package.json index ef4df59..702094f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hexo-theme-butterfly", - "version": "5.5.3-b2", + "version": "5.5.3", "description": "A Simple and Card UI Design theme for Hexo", "main": "package.json", "scripts": { diff --git a/plugins.yml b/plugins.yml index 1246ce5..3ae26b0 100644 --- a/plugins.yml +++ b/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 diff --git a/scripts/events/cdn.js b/scripts/events/cdn.js index 7d3e31e..cc1ac27 100644 --- a/scripts/events/cdn.js +++ b/scripts/events/cdn.js @@ -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 } diff --git a/scripts/events/init.js b/scripts/events/init.js index e8dfadf..c022766 100644 --- a/scripts/events/init.js +++ b/scripts/events/init.js @@ -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') } } } diff --git a/scripts/filters/random_cover.js b/scripts/filters/random_cover.js index 7bccbee..b18e209 100644 --- a/scripts/filters/random_cover.js +++ b/scripts/filters/random_cover.js @@ -5,39 +5,46 @@ '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 // 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}` } @@ -50,10 +57,9 @@ hexo.extend.generator.register('post', locals => { // 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 +69,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 diff --git a/scripts/helpers/aside_archives.js b/scripts/helpers/aside_archives.js index 1d99d09..e742f63 100644 --- a/scripts/helpers/aside_archives.js +++ b/scripts/helpers/aside_archives.js @@ -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 => { diff --git a/scripts/helpers/aside_categories.js b/scripts/helpers/aside_categories.js index faeba94..050d722 100644 --- a/scripts/helpers/aside_categories.js +++ b/scripts/helpers/aside_categories.js @@ -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 += `