]+?href=["']?([^"']+)["']?[^>]*>([^<]+)<\/a>/gi, '[!{_p("aside.card_newest_comments.link")}]') // replace url
content = content.replace(/.*?<\/pre>/gi, '[!{_p("aside.card_newest_comments.code")}]') // replace code
- content = content.replace(/.*?<\/code>/gi, '[!{_p("aside.card_newest_comments.code")}]') // replace code
+ content = content.replace(/.*?<\/code>/gi, '[!{_p("aside.card_newest_comments.code")}]') // replace code
content = content.replace(/<[^>]+>/g, "") // remove html tag
if (content.length > 150) {
@@ -23,8 +23,9 @@ script.
result += ''
if (!{theme.aside.card_newest_comments.avatar} && array[i].avatar) {
- const imgAttr = '!{theme.lazyload.enable ? "data-lazy-src" : "src"}'
- result += `
![${array[i].nick}]()
`
+ const imgAttr = '!{theme.lazyload.enable && !theme.lazyload.native ? "data-lazy-src" : "src"}'
+ const lazyloadNative = '!{theme.lazyload.enable && theme.lazyload.native ? "loading=\"lazy\"" : ""}'
+ result += `
![${array[i].nick}]()
`
}
result += `
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/third-party/pangu.pug b/layout/includes/third-party/pangu.pug
deleted file mode 100644
index 53bb064..0000000
--- a/layout/includes/third-party/pangu.pug
+++ /dev/null
@@ -1,23 +0,0 @@
-script.
- (() => {
- const panguFn = () => {
- if (typeof pangu === 'object') pangu.autoSpacingPage()
- else {
- btf.getScript('!{url_for(theme.asset.pangu)}')
- .then(() => {
- pangu.autoSpacingPage()
- })
- }
- }
-
- const panguInit = () => {
- if (!{theme.pangu.field === 'post'}){
- GLOBAL_CONFIG_SITE.isPost && panguFn()
- } else {
- panguFn()
- }
- }
-
- btf.addGlobalFn('pjaxComplete', panguInit, 'pangu')
- document.addEventListener('DOMContentLoaded', panguInit)
- })()
diff --git a/layout/includes/third-party/search/algolia.pug b/layout/includes/third-party/search/algolia.pug
index b1f3c3d..4ef6001 100644
--- a/layout/includes/third-party/search/algolia.pug
+++ b/layout/includes/third-party/search/algolia.pug
@@ -14,7 +14,7 @@
#algolia-info
.algolia-stats
.algolia-poweredBy
-
+
#search-mask
script(src=url_for(theme.asset.algolia_search))
diff --git a/layout/includes/third-party/umami_analytics.pug b/layout/includes/third-party/umami_analytics.pug
index 518afd5..3534c20 100644
--- a/layout/includes/third-party/umami_analytics.pug
+++ b/layout/includes/third-party/umami_analytics.pug
@@ -35,7 +35,7 @@ script.
const insertData = async () => {
try {
- if (GLOBAL_CONFIG_SITE.isPost && config.page_pv) {
+ if (GLOBAL_CONFIG_SITE.pageType === 'post' && config.page_pv) {
const pagePV = document.getElementById('umamiPV')
if (pagePV) {
const data = await getData(true)
diff --git a/layout/includes/widget/card_author.pug b/layout/includes/widget/card_author.pug
index 095f66f..9f7e687 100644
--- a/layout/includes/widget/card_author.pug
+++ b/layout/includes/widget/card_author.pug
@@ -13,14 +13,14 @@ if theme.aside.card_author.enable
.headline= _p('aside.tags')
.length-num= site.tags.length
a(href=url_for(config.category_dir) + '/')
- .headline= _p('aside.categories')
+ .headline= _p('aside.categories')
.length-num= site.categories.length
if theme.aside.card_author.button.enable
a#card-info-btn(href=theme.aside.card_author.button.link)
i(class=theme.aside.card_author.button.icon)
span=theme.aside.card_author.button.text
-
+
if(theme.social)
.card-info-social-icons
!=partial('includes/header/social', {}, {cache: true})
diff --git a/layout/includes/widget/card_post_toc.pug b/layout/includes/widget/card_post_toc.pug
index b811210..cd30d50 100644
--- a/layout/includes/widget/card_post_toc.pug
+++ b/layout/includes/widget/card_post_toc.pug
@@ -10,6 +10,5 @@
if (page.encrypt == true)
.toc-content.toc-div-class(class=tocExpandClass style="display:none")!=toc(page.origin, {list_number: tocNumber})
- else
+ else
.toc-content(class=tocExpandClass)!=toc(page.content, {list_number: tocNumber})
-
\ No newline at end of file
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/includes/widget/index.pug b/layout/includes/widget/index.pug
index 388ea1c..b298878 100644
--- a/layout/includes/widget/index.pug
+++ b/layout/includes/widget/index.pug
@@ -1,6 +1,6 @@
#aside-content.aside-content
//- post
- if is_post()
+ if globalPageType === 'post'
- const tocStyle = page.toc_style_simple
- const tocStyleVal = tocStyle === true || tocStyle === false ? tocStyle : theme.toc.style_simple
if showToc && tocStyleVal
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 08bdea3..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
@@ -17,7 +17,7 @@ block content
each item, index in page.tags.data
a(href=url_for(item.path)).post-meta__tags #[=item.name]
include includes/third-party/share/index.pug
-
+
if theme.reward.enable && theme.reward.QR_code
!=partial('includes/post/reward', {}, {cache: true})
@@ -33,4 +33,3 @@ block content
if page.comments !== false && theme.comments.use
- var commentsJsLoad = true
!=partial('includes/third-party/comments/index', {}, {cache: true})
-
\ No newline at end of file
diff --git a/package.json b/package.json
index aa27123..0e13d2e 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "hexo-theme-butterfly",
- "version": "5.2.2",
+ "version": "5.3.0",
"description": "A Simple and Card UI Design theme for Hexo",
"main": "package.json",
"scripts": {
diff --git a/plugins.yml b/plugins.yml
index 520adf4..d0474e2 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.19.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.2
docsearch_js:
name: '@docsearch/js'
other_name: docsearch-js
file: dist/umd/index.js
- version: 3.6.3
+ version: 3.8.2
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.2
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.7
katex:
name: katex
file: dist/katex.min.css
other_name: KaTeX
- version: 0.16.11
+ version: 0.16.19
katex_copytex:
name: katex
file: dist/contrib/copy-tex.min.js
other_name: KaTeX
- version: 0.16.11
+ version: 0.16.19
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
@@ -152,10 +152,6 @@ pace_js:
other_name: pace
file: pace.min.js
version: 1.2.4
-pangu:
- name: pangu
- file: dist/browser/pangu.min.js
- version: 4.0.7
pjax:
name: pjax
file: pjax.min.js
@@ -194,7 +190,7 @@ snackbar_css:
twikoo:
name: twikoo
file: dist/twikoo.all.min.js
- version: 1.6.39
+ version: 1.6.41
typed:
name: typed.js
file: dist/typed.umd.js
@@ -202,14 +198,14 @@ typed:
valine:
name: valine
file: dist/Valine.min.js
- version: 1.5.2
+ version: 1.5.3
waline_css:
name: '@waline/client'
file: dist/waline.css
other_name: waline
- version: 3.3.2
+ version: 3.4.3
waline_js:
name: '@waline/client'
file: dist/waline.js
other_name: waline
- version: 3.3.2
+ version: 3.4.3
diff --git a/scripts/events/merge_config.js b/scripts/events/merge_config.js
index fe04871..c14c57c 100644
--- a/scripts/events/merge_config.js
+++ b/scripts/events/merge_config.js
@@ -545,12 +545,9 @@ hexo.extend.filter.register('before_generate', () => {
bg_dark: '#1f1f1f'
},
instantpage: false,
- pangu: {
- enable: false,
- field: 'site'
- },
lazyload: {
enable: false,
+ native: false,
field: 'site',
placeholder: null,
blur: false
@@ -567,6 +564,7 @@ hexo.extend.filter.register('before_generate', () => {
enable: true,
option: null
},
+ structured_data: true,
css_prefix: true,
inject: {
head: null,
diff --git a/scripts/filters/post_lazyload.js b/scripts/filters/post_lazyload.js
index cecc6ce..7ad6339 100644
--- a/scripts/filters/post_lazyload.js
+++ b/scripts/filters/post_lazyload.js
@@ -9,6 +9,10 @@
const urlFor = require('hexo-util').url_for.bind(hexo)
const lazyload = htmlContent => {
+ if (hexo.theme.config.lazyload.native) {
+ return htmlContent.replace(/(
)/ig, '$1 loading=\'lazy\'$2')
+ }
+
const bg = hexo.theme.config.lazyload.placeholder ? urlFor(hexo.theme.config.lazyload.placeholder) : 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'
return htmlContent.replace(/( {
- const imgTestReg = /\.(png|jpe?g|gif|svg|webp)(\?.*)?$/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 = `
${_p('aside.card_archives')}
- ${len > limitLength ? `
` : ''}
+ ${data.length > limitedData.length
+ ? `
+
+ `
+ : ''}
- '
- return result
+ return archiveHeader + archiveList
})
-const toMomentLocale = function (lang) {
- if (!lang || lang === 'en' || lang === 'default') {
- return 'en'
- }
+// Improved locale conversion function
+const toMomentLocale = lang => {
+ if (!lang || ['en', 'default'].includes(lang)) return 'en'
return lang.toLowerCase().replace('_', '-')
}
diff --git a/scripts/helpers/page.js b/scripts/helpers/page.js
index cb97842..3259640 100644
--- a/scripts/helpers/page.js
+++ b/scripts/helpers/page.js
@@ -131,3 +131,17 @@ hexo.extend.helper.register('shuoshuoFN', (data, page) => {
return finalResult
})
+
+hexo.extend.helper.register('getPageType', (page, isHome) => {
+ const { layout, tag, category, type, archive } = page
+ if (layout) return layout
+ if (tag) return 'tag'
+ if (category) return 'category'
+ if (archive) return 'archive'
+ if (type) {
+ if (type === 'tags' || type === 'categories') return type
+ else return 'page'
+ }
+ if (isHome) return 'home'
+ return 'post'
+})
diff --git a/scripts/tag/gallery.js b/scripts/tag/gallery.js
index 5d6949b..f4bfab1 100644
--- a/scripts/tag/gallery.js
+++ b/scripts/tag/gallery.js
@@ -3,7 +3,7 @@
* galleryGroup and gallery
* {% galleryGroup [name] [descr] [url] [img] %}
*
- * {% gallery [button] %}
+ * {% gallery [button],[limit],[firstLimit] %}
* {% gallery url,[url],[button] %}
*/
@@ -11,54 +11,66 @@
const urlFor = require('hexo-util').url_for.bind(hexo)
-const gallery = (args, content) => {
- args = args.join(' ').split(',')
- let button = false
- let type = 'data'
- let dataStr = ''
+const DEFAULT_LIMIT = 10
+const DEFAULT_FIRST_LIMIT = 10
+const IMAGE_REGEX = /!\[(.*?)\]\(([^\s]*)\s*(?:["'](.*?)["']?)?\s*\)/g
- if (args[0] === 'url') {
- [type, dataStr, button] = args // url,[link],[lazyload]
- dataStr = urlFor(dataStr)
- } else {
- [button] = args // [lazyload]
- const regex = /!\[(.*?)\]\(([^\s]*)\s*(?:["'](.*?)["']?)?\s*\)/g
- let m
- const arr = []
- while ((m = regex.exec(content)) !== null) {
- if (m.index === regex.lastIndex) {
- regex.lastIndex++
- }
- arr.push({
- url: m[2],
- alt: m[1],
- title: m[3]
- })
- }
+// Helper functions
+const parseGalleryArgs = args => {
+ const [type, ...rest] = args.join(' ').split(',').map(arg => arg.trim())
+ return {
+ isUrl: type === 'url',
+ params: type === 'url' ? rest : [type, ...rest]
+ }
+}
- dataStr = JSON.stringify(arr)
+const parseImageContent = content => {
+ const images = []
+ let match
+
+ while ((match = IMAGE_REGEX.exec(content)) !== null) {
+ images.push({
+ url: match[2],
+ alt: match[1] || '',
+ title: match[3] || ''
+ })
}
- return ``
+ return images
+}
+
+const createGalleryHTML = (type, dataStr, button, limit, firstLimit) => {
+ return ``
+}
+
+const gallery = (args, content) => {
+ const { isUrl, params } = parseGalleryArgs(args)
+
+ if (isUrl) {
+ const [dataStr, button = false, limit = DEFAULT_LIMIT, firstLimit = DEFAULT_FIRST_LIMIT] = params
+ return createGalleryHTML('url', urlFor(dataStr), button, limit, firstLimit)
+ }
+
+ const [button = false, limit = DEFAULT_LIMIT, firstLimit = DEFAULT_FIRST_LIMIT] = params
+ const images = parseImageContent(content)
+ return createGalleryHTML('data', JSON.stringify(images), button, limit, firstLimit)
}
const galleryGroup = args => {
- const [name, descr, url, img] = args
- const imgUrl = urlFor(img)
- const urlLink = urlFor(url)
+ const [name = '', descr = '', url = '', img = ''] = args.map(arg => arg.trim())
return `
-
-
- ${name}
- ${descr}
-
-
-
- `
+
+
+ ${name}
+ ${descr}
+
+
+ `
}
+// Register tags
hexo.extend.tag.register('gallery', gallery, { ends: true })
hexo.extend.tag.register('galleryGroup', galleryGroup)
diff --git a/scripts/tag/hide.js b/scripts/tag/hide.js
index a788172..3a58bec 100644
--- a/scripts/tag/hide.js
+++ b/scripts/tag/hide.js
@@ -16,50 +16,37 @@
'use strict'
-const parseArgs = args => {
- return args.join(' ').split(',')
-}
+const parseArgs = args => args.join(' ').split(',')
const generateStyle = (bg, color) => {
let style = 'style="'
- if (bg) {
- style += `background-color: ${bg};`
- }
- if (color) {
- style += `color: ${color}`
- }
+ if (bg) style += `background-color: ${bg};`
+ if (color) style += `color: ${color}`
style += '"'
return style
}
const hideInline = args => {
const [content, display = 'Click', bg = false, color = false] = parseArgs(args)
- const group = generateStyle(bg, color)
-
- return `${content}`
+ const style = generateStyle(bg, color)
+ return `${content}`
}
const hideBlock = (args, content) => {
const [display = 'Click', bg = false, color = false] = parseArgs(args)
- const group = generateStyle(bg, color)
-
- return `${hexo.render.renderSync({ text: content, engine: 'markdown' })}
`
+ const style = generateStyle(bg, color)
+ const renderedContent = hexo.render.renderSync({ text: content, engine: 'markdown' })
+ return `${renderedContent}
`
}
const hideToggle = (args, content) => {
const [display, bg = false, color = false] = parseArgs(args)
- const group = generateStyle(bg, color)
- let border = ''
-
- if (bg) {
- border = `style="border: 1px solid ${bg}"`
- }
-
- return `${display}
${hexo.render.renderSync({ text: content, engine: 'markdown' })}
`
+ const style = generateStyle(bg, color)
+ const border = bg ? `style="border: 1px solid ${bg}"` : ''
+ const renderedContent = hexo.render.renderSync({ text: content, engine: 'markdown' })
+ return `${display}
${renderedContent}
`
}
hexo.extend.tag.register('hideInline', hideInline)
hexo.extend.tag.register('hideBlock', hideBlock, { ends: true })
-hexo.extend.tag.register('hideToggle', hideToggle, { ends: true })
+hexo.extend.tag.register('hideToggle', hideToggle, { ends: true })
\ No newline at end of file
diff --git a/scripts/tag/inlineImg.js b/scripts/tag/inlineImg.js
index 753add0..a0357eb 100644
--- a/scripts/tag/inlineImg.js
+++ b/scripts/tag/inlineImg.js
@@ -1,9 +1,9 @@
/**
- * inlineImg 圖片
- * @param {Array} args 圖片名稱和高度
- * @param {string} args[0] 圖片名稱
- * @param {number} args[1] 圖片高度
- * @returns {string} 圖片標籤
+ * inlineImg
+ * @param {Array} args - Image name and height
+ * @param {string} args[0] - Image name
+ * @param {number} args[1] - Image height
+ * @returns {string} - Image tag
*/
'use strict'
diff --git a/scripts/tag/mermaid.js b/scripts/tag/mermaid.js
index ba25c0f..bf6b1e5 100644
--- a/scripts/tag/mermaid.js
+++ b/scripts/tag/mermaid.js
@@ -10,7 +10,7 @@ const { escapeHTML } = require('hexo-util')
const mermaid = (args, content) => {
return `
- ${escapeHTML(content)}
+ ${escapeHTML(content)}
`
}
diff --git a/scripts/tag/timeline.js b/scripts/tag/timeline.js
index 1257940..945454c 100644
--- a/scripts/tag/timeline.js
+++ b/scripts/tag/timeline.js
@@ -1,41 +1,50 @@
/**
- * timeline
- * by Jerry
+ * Timeline tag for Hexo
+ * Syntax:
+ * {% timeline [headline],[color] %}
+ *
+ * [content]
+ *
+ *
+ * [content]
+ *
+ * {% endtimeline %}
*/
'use strict'
const timeLineFn = (args, content) => {
- const tlBlock = /\n([\w\W\s\S]*?)/g
+ // Use named capture groups for better readability
+ const tlBlock = /\n(?[\s\S]*?)/g
- let result = ''
- let color = ''
- let text = ''
- if (args.length) {
- [text, color] = args.join(' ').split(',')
- const mdContent = hexo.render.renderSync({ text, engine: 'markdown' })
- result += ``
- }
+ // Pre-compile markdown render function
+ const renderMd = text => hexo.render.renderSync({ text, engine: 'markdown' })
- const matches = []
- let match
+ // Parse arguments more efficiently
+ const [text, color = ''] = args.length ? args.join(' ').split(',') : []
- while ((match = tlBlock.exec(content)) !== null) {
- matches.push(match[1])
- matches.push(match[2])
- }
+ // Build initial headline if text exists
+ const headline = text
+ ? ``
+ : ''
- for (let i = 0; i < matches.length; i += 2) {
- const tlChildTitle = hexo.render.renderSync({ text: matches[i], engine: 'markdown' })
- const tlChildContent = hexo.render.renderSync({ text: matches[i + 1], engine: 'markdown' })
+ // Match all timeline blocks in one pass and transform
+ const items = Array.from(content.matchAll(tlBlock))
+ .map(({ groups: { title, content } }) =>
+ `
+
+
${renderMd(content)}
+
`
+ )
+ .join('')
- const tlTitleHtml = ``
- const tlContentHtml = `${tlChildContent}
`
-
- result += `${tlTitleHtml + tlContentHtml}
`
- }
-
- return `${result}
`
+ return `${headline}${items}
`
}
hexo.extend.tag.register('timeline', timeLineFn, { ends: true })
diff --git a/source/css/var.styl b/source/css/var.styl
index 6ab9a83..68796fb 100644
--- a/source/css/var.styl
+++ b/source/css/var.styl
@@ -16,7 +16,7 @@ $theme-toc-color = $themeColorEnable && hexo-config('theme_color.toc_color') ? c
$chinseFont = $language == 'zh-CN' ? 'Microsoft YaHei' : 'Microsoft JhengHei'
$dafault-font-family = -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue', Lato, Roboto, 'PingFang SC', $chinseFont, sans-serif
-$dafault-code-font = consolas, Menlo, 'PingFang SC', $chinseFont, sans-serif
+$dafault-code-font = consolas, Menlo, monospace, 'PingFang SC', $chinseFont, sans-serif
$font-family = hexo-config('font.font_family') ? unquote(hexo-config('font.font_family')) : $dafault-font-family
$code-font-family = hexo-config('font.code_font_family') ? unquote(hexo-config('font.code_font_family')) : $dafault-code-font
diff --git a/source/js/main.js b/source/js/main.js
index 42cfd97..20f509a 100644
--- a/source/js/main.js
+++ b/source/js/main.js
@@ -224,14 +224,23 @@ document.addEventListener('DOMContentLoaded', () => {
*/
const fetchUrl = async url => {
- const response = await fetch(url)
- return await response.json()
+ try {
+ const response = await fetch(url)
+ return await response.json()
+ } catch (error) {
+ console.error('Failed to fetch URL:', error)
+ return []
+ }
}
- const runJustifiedGallery = (item, data, isButton = false, tabs) => {
- const dataLength = data.length
+ const runJustifiedGallery = (container, data, config) => {
+ const { isButton, limit, firstLimit, tabs } = config
- const ig = new InfiniteGrid.JustifiedInfiniteGrid(item, {
+ const dataLength = data.length
+ const maxGroupKey = Math.ceil((dataLength - firstLimit) / limit + 1)
+
+ // Gallery configuration
+ const igConfig = {
gap: 5,
isConstantSize: true,
sizeRange: [150, 600],
@@ -239,132 +248,130 @@ document.addEventListener('DOMContentLoaded', () => {
// 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(`
-

-
`)
- }
- return nextItems
}
- const buttonText = GLOBAL_CONFIG.infinitegrid.buttonText
- const addButton = item => {
- const button = document.createElement('button')
- button.innerHTML = buttonText + ''
-
- 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)
+ const ig = new InfiniteGrid.JustifiedInfiniteGrid(container, igConfig)
let isLayoutHidden = false
- const completeFn = e => {
- if (tabs) {
- const parentNode = item.parentNode
+ // Utility functions
+ const sanitizeString = str => (str && str.replace(/"/g, '"')) || ''
+ const createImageItem = item => {
+ const alt = item.alt ? `alt="${sanitizeString(item.alt)}"` : ''
+ const title = item.title ? `title="${sanitizeString(item.title)}"` : ''
+ return `
+

+
`
+ }
+
+ const getItems = (nextGroupKey, count, isFirst = false) => {
+ const startIndex = isFirst ? (nextGroupKey - 1) * count : (nextGroupKey - 2) * count + firstLimit
+ return data.slice(startIndex, startIndex + count).map(createImageItem)
+ }
+
+ // Load more button
+ const addLoadMoreButton = container => {
+ const button = document.createElement('button')
+ button.innerHTML = `${GLOBAL_CONFIG.infinitegrid.buttonText}`
+
+ button.addEventListener('click', () => {
+ button.remove()
+ btf.setLoading.add(container)
+ appendItems(ig.getGroups().length + 1, limit)
+ }, { once: true })
+
+ container.insertAdjacentElement('afterend', button)
+ }
+
+ const appendItems = (nextGroupKey, count, isFirst) => {
+ ig.append(getItems(nextGroupKey, count, isFirst), nextGroupKey)
+ }
+
+ // Event handlers
+ const handleRenderComplete = e => {
+ if (tabs) {
+ const parentNode = container.parentNode
if (isLayoutHidden) {
parentNode.style.visibility = 'visible'
}
-
- if (item.offsetHeight === 0) {
+ if (container.offsetHeight === 0) {
parentNode.style.visibility = 'hidden'
isLayoutHidden = true
}
}
const { updated, isResize, mounted } = e
- if (!updated.length || !mounted.length || isResize) {
- return
- }
+ if (!updated.length || !mounted.length || isResize) return
- btf.loadLightbox(item.querySelectorAll('img:not(.medium-zoom-image)'))
+ btf.loadLightbox(container.querySelectorAll('img:not(.medium-zoom-image)'))
if (ig.getGroups().length === maxGroupKey) {
- btf.setLoading.remove(item)
- !tabs && ig.off('renderComplete', completeFn)
+ btf.setLoading.remove(container)
+ !tabs && ig.off('renderComplete', handleRenderComplete)
return
}
if (isButton) {
- btf.setLoading.remove(item)
- addButton(item)
+ btf.setLoading.remove(container)
+ addLoadMoreButton(container)
}
}
- const requestAppendFn = btf.debounce(e => {
+ const handleRequestAppend = btf.debounce(e => {
const nextGroupKey = (+e.groupKey || 0) + 1
- appendItem(nextGroupKey, 10)
- if (nextGroupKey === maxGroupKey) {
- ig.off('requestAppend', requestAppendFn)
- }
+ if (nextGroupKey === 1) appendItems(nextGroupKey, firstLimit, true)
+ else appendItems(nextGroupKey, limit)
+
+ if (nextGroupKey === maxGroupKey) ig.off('requestAppend', handleRequestAppend)
}, 300)
- btf.setLoading.add(item)
- ig.on('renderComplete', completeFn)
+ btf.setLoading.add(container)
+ ig.on('renderComplete', handleRenderComplete)
if (isButton) {
- appendItem(1, 10)
+ appendItems(1, firstLimit, true)
} else {
- ig.on('requestAppend', requestAppendFn)
+ ig.on('requestAppend', handleRequestAppend)
ig.renderItems()
}
- btf.addGlobalFn('pjaxSendOnce', () => { ig.destroy() })
+ 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 addJustifiedGallery = async (elements, tabs = false) => {
+ if (!elements.length) return
+
+ const initGallery = async () => {
+ for (const element of elements) {
+ if (btf.isHidden(element) || element.classList.contains('loaded')) continue
+
+ const config = {
+ isButton: element.getAttribute('data-button') === 'true',
+ limit: parseInt(element.getAttribute('data-limit'), 10),
+ firstLimit: parseInt(element.getAttribute('data-first'), 10),
+ tabs
+ }
+
+ const container = element.firstElementChild
+ const content = container.textContent
+ container.textContent = ''
+ element.classList.add('loaded')
- 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)
+ const data = element.getAttribute('data-type') === 'url' ? await fetchUrl(content) : JSON.parse(content)
+ runJustifiedGallery(container, data, config)
+ } catch (error) {
+ console.error('Gallery data parsing failed:', error)
}
}
}
if (typeof InfiniteGrid === 'function') {
- init()
+ await initGallery()
} else {
- await btf.getScript(`${GLOBAL_CONFIG.infinitegrid.js}`)
- init()
+ await btf.getScript(GLOBAL_CONFIG.infinitegrid.js)
+ await initGallery()
}
}
@@ -864,7 +871,7 @@ document.addEventListener('DOMContentLoaded', () => {
menuMask && menuMask.addEventListener('click', () => { sidebarFn.close() })
clickFnOfSubMenu()
- GLOBAL_CONFIG.islazyload && lazyloadImg()
+ GLOBAL_CONFIG.islazyloadPlugin && lazyloadImg()
GLOBAL_CONFIG.copyright !== undefined && addCopyright()
if (GLOBAL_CONFIG.autoDarkmode) {
@@ -890,7 +897,7 @@ document.addEventListener('DOMContentLoaded', () => {
initAdjust()
justifiedIndexPostUI()
- if (GLOBAL_CONFIG_SITE.isPost) {
+ if (GLOBAL_CONFIG_SITE.pageType === 'post') {
addPostOutdateNotice()
GLOBAL_CONFIG.relativeDate.post && relativeDate(document.querySelectorAll('#post-meta time'))
} else {
@@ -900,11 +907,11 @@ document.addEventListener('DOMContentLoaded', () => {
toggleCardCategory()
}
- GLOBAL_CONFIG_SITE.isHome && scrollDownInIndex()
+ GLOBAL_CONFIG_SITE.pageType === 'home' && scrollDownInIndex()
scrollFn()
forPostFn()
- !GLOBAL_CONFIG_SITE.isShuoshuo && btf.switchComments(document)
+ GLOBAL_CONFIG_SITE.pageType !== 'shuoshuo' && btf.switchComments(document)
openMobileMenu()
}