diff --git a/layout/includes/sidebar.pug b/layout/includes/sidebar.pug index ef64d2b..178c420 100644 --- a/layout/includes/sidebar.pug +++ b/layout/includes/sidebar.pug @@ -3,16 +3,16 @@ if theme.menu #menu-mask #sidebar-menus .avatar-img.text-center - img(src=url_for(theme.avatar.img) onerror=`onerror=null;src='${theme.error_img.flink}'` alt="avatar") + img(src=url_for(theme.avatar.img) onerror=`this.onerror=null;this.src='${url_for(theme.error_img.flink)}'` alt="avatar") .site-data.text-center - a(href=url_for(config.archive_dir) + '/') + a(href=`${url_for(config.archive_dir)}/`) .headline= _p('aside.articles') .length-num= site.posts.length - a(href=url_for(config.tag_dir) + '/' ) + a(href=`${url_for(config.tag_dir)}/`) .headline= _p('aside.tags') .length-num= site.tags.length - a(href=url_for(config.category_dir) + '/') + a(href=`${url_for(config.category_dir)}/`) .headline= _p('aside.categories') .length-num= site.categories.length - !=partial('includes/header/menu_item', {}, {cache: true}) + != partial('includes/header/menu_item', {}, { cache: true }) \ No newline at end of file diff --git a/layout/includes/third-party/newest-comments/artalk.pug b/layout/includes/third-party/newest-comments/artalk.pug index 89ca3ad..4e703b2 100644 --- a/layout/includes/third-party/newest-comments/artalk.pug +++ b/layout/includes/third-party/newest-comments/artalk.pug @@ -34,7 +34,7 @@ script. const searchParams = new URLSearchParams({ 'site_name': '!{site}', - 'limit': '!{theme.aside.card_newest_comments.limit}', + 'limit': '!{newestCommentsLimit * 2}', // Fetch more comments to filter pending comments }) const getComment = async (ele) => { @@ -42,16 +42,19 @@ script. const res = await fetch(`!{server}/api/v2/stats/latest_comments?${searchParams}`) const result = await res.json() const { avatarCdn, avatarDefault } = await getAvatarValue() - const artalk = result.data.map(e => { - const avatar = avatarCdn && e.email_encrypted ? `${avatarCdn}${e.email_encrypted}?${avatarDefault}` : '' - return { - 'avatar': avatar, - 'content': changeContent(e.content_marked), - 'nick': e.nick, - 'url': e.page_url, - 'date': e.date, - } - }) + const artalk = result.data + .filter(e => !e.is_pending) // Filter pending comments + .slice(0, !{newestCommentsLimit}) // Limit the number of comments + .map(e => { + const avatar = avatarCdn && e.email_encrypted ? `${avatarCdn}${e.email_encrypted}?${avatarDefault}` : '' + return { + 'avatar': avatar, + 'content': changeContent(e.content_marked), + 'nick': e.nick, + 'url': e.page_url, + 'date': e.date, + } + }) btf.saveToLocal.set(keyName, JSON.stringify(artalk), !{theme.aside.card_newest_comments.storage}/(60*24)) generateHtml(artalk, ele) } catch (e) { diff --git a/layout/includes/third-party/newest-comments/disqus-comment.pug b/layout/includes/third-party/newest-comments/disqus-comment.pug index 96eaa02..e26bb41 100644 --- a/layout/includes/third-party/newest-comments/disqus-comment.pug +++ b/layout/includes/third-party/newest-comments/disqus-comment.pug @@ -6,7 +6,7 @@ script. const { changeContent, generateHtml, run } = window.newestComments const getComment = ele => { - fetch('https://disqus.com/api/3.0/forums/listPosts.json?forum=!{forum}&related=thread&limit=!{theme.aside.card_newest_comments.limit}&api_key=!{apiKey}') + fetch('https://disqus.com/api/3.0/forums/listPosts.json?forum=!{forum}&related=thread&limit=!{newestCommentsLimit}&api_key=!{apiKey}') .then(response => response.json()) .then(data => { const disqusArray = data.response.map(item => { diff --git a/layout/includes/third-party/newest-comments/github-issues.pug b/layout/includes/third-party/newest-comments/github-issues.pug index d36f1b2..7dc418c 100644 --- a/layout/includes/third-party/newest-comments/github-issues.pug +++ b/layout/includes/third-party/newest-comments/github-issues.pug @@ -32,7 +32,7 @@ script. } const getComment = ele => { - fetch('https://api.github.com/repos/!{userRepo}/issues/comments?sort=updated&direction=desc&per_page=!{theme.aside.card_newest_comments.limit}&page=1',{ + fetch('https://api.github.com/repos/!{userRepo}/issues/comments?sort=updated&direction=desc&per_page=!{newestCommentsLimit}&page=1',{ "headers": { Accept: 'application/vnd.github.v3.html+json' } diff --git a/layout/includes/third-party/newest-comments/index.pug b/layout/includes/third-party/newest-comments/index.pug index 8ceaccf..a2f2c0d 100644 --- a/layout/includes/third-party/newest-comments/index.pug +++ b/layout/includes/third-party/newest-comments/index.pug @@ -1,7 +1,11 @@ - let { use } = theme.comments if use - - let forum,apiKey,userRepo + - + let forum,apiKey,userRepo + let { limit:newestCommentsLimit } = theme.aside.card_newest_comments + if (newestCommentsLimit > 10 || newestCommentsLimit < 1) newestCommentsLimit = 6 + case use[0] when 'Valine' include ./valine.pug diff --git a/layout/includes/third-party/newest-comments/remark42.pug b/layout/includes/third-party/newest-comments/remark42.pug index be3b2fc..4321b3b 100644 --- a/layout/includes/third-party/newest-comments/remark42.pug +++ b/layout/includes/third-party/newest-comments/remark42.pug @@ -7,7 +7,7 @@ script. const { changeContent, generateHtml, run } = window.newestComments const getComment = ele => { - fetch('!{host}/api/v1/last/!{theme.aside.card_newest_comments.limit}?site=!{siteId}') + fetch('!{host}/api/v1/last/!{newestCommentsLimit}?site=!{siteId}') .then(response => response.json()) .then(data => { const remark42 = data.map(e => { diff --git a/layout/includes/third-party/newest-comments/twikoo-comment.pug b/layout/includes/third-party/newest-comments/twikoo-comment.pug index 941f42b..772c8ec 100644 --- a/layout/includes/third-party/newest-comments/twikoo-comment.pug +++ b/layout/includes/third-party/newest-comments/twikoo-comment.pug @@ -10,7 +10,7 @@ script. twikoo.getRecentComments({ envId: '!{theme.twikoo.envId}', region: '!{theme.twikoo.region}', - pageSize: !{theme.aside.card_newest_comments.limit}, + pageSize: !{newestCommentsLimit}, includeReply: true }).then(res => { const twikooArray = res.map(e => { diff --git a/layout/includes/third-party/newest-comments/valine.pug b/layout/includes/third-party/newest-comments/valine.pug index 6b87683..08af70c 100644 --- a/layout/includes/third-party/newest-comments/valine.pug +++ b/layout/includes/third-party/newest-comments/valine.pug @@ -27,7 +27,7 @@ script. }, } - fetch(`${serverURL}/1.1/classes/Comment?limit=!{theme.aside.card_newest_comments.limit}&order=-createdAt`,settings) + fetch(`${serverURL}/1.1/classes/Comment?limit=!{newestCommentsLimit}&order=-createdAt`,settings) .then(response => response.json()) .then(data => { const valineArray = data.results.map(e => { diff --git a/layout/includes/third-party/newest-comments/waline.pug b/layout/includes/third-party/newest-comments/waline.pug index 4031d34..72743ea 100644 --- a/layout/includes/third-party/newest-comments/waline.pug +++ b/layout/includes/third-party/newest-comments/waline.pug @@ -9,7 +9,7 @@ script. const getComment = async (ele) => { try { - const res = await fetch('!{serverURL}/api/comment?type=recent&count=!{theme.aside.card_newest_comments.limit}', { method: 'GET' }) + const res = await fetch('!{serverURL}/api/comment?type=recent&count=!{newestCommentsLimit}') const result = await res.json() const walineArray = result.data.map(e => { return { diff --git a/layout/includes/widget/card_webinfo.pug b/layout/includes/widget/card_webinfo.pug index 09ea9c4..7ea4d98 100644 --- a/layout/includes/widget/card_webinfo.pug +++ b/layout/includes/widget/card_webinfo.pug @@ -6,40 +6,39 @@ if theme.aside.card_webinfo.enable .webinfo if theme.aside.card_webinfo.post_count .webinfo-item - .item-name= _p('aside.card_webinfo.article_name') + " :" + .item-name= `${_p('aside.card_webinfo.article_name')} :` .item-count= site.posts.length if theme.aside.card_webinfo.runtime_date .webinfo-item - .item-name= _p('aside.card_webinfo.runtime.name') + " :" + .item-name= `${_p('aside.card_webinfo.runtime.name')} :` .item-count#runtimeshow(data-publishDate=date_xml(theme.aside.card_webinfo.runtime_date)) i.fa-solid.fa-spinner.fa-spin if theme.wordcount.enable && theme.wordcount.total_wordcount .webinfo-item - .item-name=_p('aside.card_webinfo.site_wordcount') + " :" - .item-count=totalcount(site) + .item-name= `${_p('aside.card_webinfo.site_wordcount')} :` + .item-count= totalcount(site) if theme.umami_analytics.enable && theme.umami_analytics.UV_PV.site_uv .webinfo-item - .item-name= _p('aside.card_webinfo.site_uv_name') + " :" + .item-name= `${_p('aside.card_webinfo.site_uv_name')} :` .item-count#umami-site-uv i.fa-solid.fa-spinner.fa-spin else if theme.busuanzi.site_uv .webinfo-item - .item-name= _p('aside.card_webinfo.site_uv_name') + " :" + .item-name= `${_p('aside.card_webinfo.site_uv_name')} :` .item-count#busuanzi_value_site_uv i.fa-solid.fa-spinner.fa-spin if theme.umami_analytics.enable && theme.umami_analytics.UV_PV.site_pv .webinfo-item - .item-name= _p('aside.card_webinfo.site_pv_name') + " :" + .item-name= `${_p('aside.card_webinfo.site_pv_name')} :` .item-count#umami-site-pv i.fa-solid.fa-spinner.fa-spin else if theme.busuanzi.site_pv .webinfo-item - .item-name= _p('aside.card_webinfo.site_pv_name') + " :" + .item-name= `${_p('aside.card_webinfo.site_pv_name')} :` .item-count#busuanzi_value_site_pv i.fa-solid.fa-spinner.fa-spin if theme.aside.card_webinfo.last_push_date .webinfo-item - .item-name= _p('aside.card_webinfo.last_push_date.name') + " :" + .item-name= `${_p('aside.card_webinfo.last_push_date.name')} :` .item-count#last-push-date(data-lastPushDate=date_xml(Date.now())) - i.fa-solid.fa-spinner.fa-spin - + i.fa-solid.fa-spinner.fa-spin \ No newline at end of file diff --git a/layout/page.pug b/layout/page.pug index 5fdbbbd..2374681 100644 --- a/layout/page.pug +++ b/layout/page.pug @@ -2,7 +2,7 @@ extends includes/layout.pug block content - const noCardLayout = ['shuoshuo', '404'].includes(page.type) ? 'nc' : '' - - var commentsJsLoad = false + - var commentsJsLoad = false mixin commentLoad if page.comments !== false && theme.comments.use diff --git a/layout/post.pug b/layout/post.pug index 947b379..9a44282 100644 --- a/layout/post.pug +++ b/layout/post.pug @@ -6,7 +6,7 @@ block content include includes/header/post-info.pug article#article-container.container.post-content - if theme.noticeOutdate.enable && page.noticeOutdate !== false + if theme.noticeOutdate.enable && page.noticeOutdate !== false include includes/post/outdate-notice.pug else !=page.content diff --git a/package.json b/package.json index 5eff91a..389317c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hexo-theme-butterfly", - "version": "5.3.0-b2", + "version": "5.3.0-b3", "description": "A Simple and Card UI Design theme for Hexo", "main": "package.json", "scripts": { diff --git a/plugins.yml b/plugins.yml index bcc4f9a..0f10ffb 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.12.0 + version: 5.16.0 aplayer_css: name: aplayer file: dist/APlayer.min.css @@ -45,7 +45,7 @@ canvas_ribbon: chartjs: name: chart.js file: dist/chart.umd.js - version: 4.4.6 + version: 4.4.7 clickShowText: name: butterfly-extsrc file: dist/click-show-text.min.js @@ -66,12 +66,12 @@ docsearch_css: name: '@docsearch/css' other_name: docsearch-css file: dist/style.css - version: 3.6.3 + version: 3.8.0 docsearch_js: name: '@docsearch/js' other_name: docsearch-js file: dist/umd/index.js - version: 3.6.3 + version: 3.8.0 egjs_infinitegrid: name: '@egjs/infinitegrid' other_name: egjs-infinitegrid @@ -95,7 +95,7 @@ fontawesome: name: '@fortawesome/fontawesome-free' file: css/all.min.css other_name: font-awesome - version: 6.6.0 + version: 6.7.1 gitalk: name: gitalk file: dist/gitalk.min.js @@ -111,17 +111,17 @@ instantpage: instantsearch: name: instantsearch.js file: dist/instantsearch.production.min.js - version: 4.75.3 + version: 4.75.6 katex: name: katex file: dist/katex.min.css other_name: KaTeX - version: 0.16.11 + version: 0.16.15 katex_copytex: name: katex file: dist/contrib/copy-tex.min.js other_name: KaTeX - version: 0.16.11 + version: 0.16.15 lazyload: name: vanilla-lazyload file: dist/lazyload.iife.min.js @@ -137,7 +137,7 @@ medium_zoom: mermaid: name: mermaid file: dist/mermaid.min.js - version: 11.4.0 + version: 11.4.1 meting_js: name: butterfly-extsrc file: metingjs/dist/Meting.min.js @@ -190,7 +190,7 @@ snackbar_css: twikoo: name: twikoo file: dist/twikoo.all.min.js - version: 1.6.39 + version: 1.6.40 typed: name: typed.js file: dist/typed.umd.js @@ -203,9 +203,9 @@ waline_css: name: '@waline/client' file: dist/waline.css other_name: waline - version: 3.3.2 + version: 3.4.1 waline_js: name: '@waline/client' file: dist/waline.js other_name: waline - version: 3.3.2 + version: 3.4.1 diff --git a/scripts/filters/random_cover.js b/scripts/filters/random_cover.js index 2cce148..f9cc13c 100644 --- a/scripts/filters/random_cover.js +++ b/scripts/filters/random_cover.js @@ -1,40 +1,60 @@ /** - * Butterfly - * ramdom cover + * Random cover for posts */ 'use strict' -hexo.extend.filter.register('before_post_render', 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 (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}` - } - +hexo.extend.generator.register('post', locals => { + const recentCovers = [] const randomCoverFn = () => { const { cover: { default_cover: defaultCover } } = hexo.theme.config if (!defaultCover) return false if (!Array.isArray(defaultCover)) return defaultCover - const num = Math.floor(Math.random() * defaultCover.length) + const defaultCoverLen = defaultCover.length + const limit = 3 + + let num + do { + num = Math.floor(Math.random() * defaultCoverLen) + } while (recentCovers.includes(num)) + + recentCovers.push(num) + if (recentCovers.length > limit) recentCovers.shift() + return defaultCover[num] } - if (coverVal === false) return data + const handleImg = data => { + const imgTestReg = /\.(png|jpe?g|gif|svg|webp|avif)(\?.*)?$/i + let { cover: coverVal, top_img: topImg } = data - // If cover is not set, use random cover - if (!coverVal) { - const randomCover = randomCoverFn() - data.cover = randomCover - coverVal = randomCover // update coverVal + // Add path to top_img and cover if post_asset_folder is enabled + if (hexo.config.post_asset_folder) { + 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 (coverVal === false) return data + + // If cover is not set, use random cover + if (!coverVal) { + const randomCover = randomCoverFn() + data.cover = randomCover + coverVal = randomCover // update coverVal + } + + if (coverVal && (coverVal.indexOf('//') !== -1 || imgTestReg.test(coverVal))) { + data.cover_type = 'img' + } + + return data } - if (coverVal && (coverVal.indexOf('//') !== -1 || imgTestReg.test(coverVal))) { - data.cover_type = 'img' - } - - return data + return locals.posts.sort('date').map(post => { + return { + data: handleImg(post), + layout: 'post', + path: post.path + } + }) }) diff --git a/scripts/helpers/aside_archives.js b/scripts/helpers/aside_archives.js index 1cf7521..df24317 100644 --- a/scripts/helpers/aside_archives.js +++ b/scripts/helpers/aside_archives.js @@ -2,38 +2,68 @@ hexo.extend.helper.register('aside_archives', function (options = {}) { const { config, page, site, url_for, _p } = this - const archiveDir = config.archive_dir - const { timezone } = config - const lang = toMomentLocale(page.lang || page.language || config.language) - const type = options.type || 'monthly' - const format = options.format || (type === 'monthly' ? 'MMMM YYYY' : 'YYYY') - const showCount = Object.prototype.hasOwnProperty.call(options, 'show_count') ? options.show_count : true - const order = options.order || -1 - const limit = options.limit + const { + archive_dir: archiveDir, + timezone, + language + } = config + + // Destructure and set default options with object destructuring + const { + type = 'monthly', + format = type === 'monthly' ? 'MMMM YYYY' : 'YYYY', + show_count: showCount = true, + order = -1, + limit, + transform + } = 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, monthA, yearB, monthB) => yearA === yearB + : (yearA, yearB) => yearA === yearB - const posts = site.posts.sort('date', order) - if (!posts.length) return '' + // Early return if no posts + if (!site.posts.length) return '' - const data = [] - posts.forEach(post => { - let date = post.date.clone() - if (timezone) date = date.tz(timezone) + // 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 year = date.year() - const month = date.month() + 1 + const year = date.year() + const month = date.month() + 1 - if (!data.length || !compareFunc(data[data.length - 1].year, data[data.length - 1].month, year, month)) { if (lang) date = date.locale(lang) - data.push({ name: date.format(format), year, month, count: 1 }) - } else { - data[data.length - 1].count++ - } - }) - const link = item => { + // Find or create archive entry + const lastEntry = acc[acc.length - 1] + if (!lastEntry || !compareFunc( + lastEntry.year, + lastEntry.month, + year, + month + )) { + acc.push({ + name: date.format(format), + year, + month, + count: 1 + }) + } else { + lastEntry.count++ + } + + return acc + }, []) + + // Create link generator function + const createArchiveLink = item => { let url = `${archiveDir}/${item.year}/` if (type === 'monthly') { url += item.month < 10 ? `0${item.month}/` : `${item.month}/` @@ -41,37 +71,48 @@ hexo.extend.helper.register('aside_archives', function (options = {}) { return url_for(url) } - const len = data.length - const limitLength = limit === 0 ? len : Math.min(len, limit) + // Limit results efficiently + const limitedData = limit > 0 + ? data.slice(0, Math.min(data.length, limit)) + : data - let result = ` + // Use template literal for better readability + const archiveHeader = `
${descr}
- -${descr}
+ +
- ${escapeHTML(content)}
+ ${escapeHTML(content)}