diff --git a/README.md b/README.md index d9ecfb9..ef3512c 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,7 @@ npm i hexo-theme-butterfly - [x] Chart.js - [x] Justified Gallery - [x] Lazyload images -- [x] Instantpage/Pangu/Snackbar notification toast/PWA...... +- [x] Instantpage/Snackbar notification toast/PWA...... ## ✨ Contributors diff --git a/README_CN.md b/README_CN.md index f1d7a5c..2b0e07d 100644 --- a/README_CN.md +++ b/README_CN.md @@ -101,7 +101,7 @@ theme: butterfly - [x] Chart.js 圖表顯示 - [x] 照片牆 - [x] 圖片懶加載 -- [x] Instantpage/Pangu/Snackbar彈窗/PWA...... +- [x] Instantpage/Snackbar彈窗/PWA...... ## ✨ 貢獻者 diff --git a/_config.yml b/_config.yml index cab31b5..0ec8370 100644 --- a/_config.yml +++ b/_config.yml @@ -983,13 +983,6 @@ snackbar: # https://instant.page/ instantpage: false -# Pangu - Insert a space between Chinese character and English character -# https://github.com/vinta/pangu.js -pangu: - enable: false - # Specify the field to use pangu (site or post) - field: site - # Lazyload # https://github.com/verlok/vanilla-lazyload lazyload: @@ -1023,6 +1016,10 @@ Open_Graph_meta: # fb_admins: # fb_app_id: +# Structured Data +# https://developers.google.com/search/docs/guides/intro-structured-data +structured_data: true + # Add the vendor prefixes to ensure compatibility css_prefix: true @@ -1092,7 +1089,6 @@ CDN: # medium_zoom: # mermaid: # meting_js: - # pangu: # prismjs_autoloader: # prismjs_js: # prismjs_lineNumber_js: diff --git a/layout/includes/additional-js.pug b/layout/includes/additional-js.pug index 7728937..b884ced 100644 --- a/layout/includes/additional-js.pug +++ b/layout/includes/additional-js.pug @@ -11,15 +11,12 @@ div if theme.instantpage script(src=url_for(theme.asset.instantpage), type='module') - if theme.lazyload.enable + if theme.lazyload.enable && !theme.lazyload.native script(src=url_for(theme.asset.lazyload)) if theme.snackbar.enable script(src=url_for(theme.asset.snackbar)) - if theme.pangu.enable - != partial("includes/third-party/pangu.pug", {}, { cache: true }) - .js-pjax if needLoadCountJs != partial("includes/third-party/card-post-count/index", {}, { cache: true }) @@ -36,7 +33,7 @@ div != partial("includes/third-party/prismjs", {}, { cache: true }) if theme.aside.enable && theme.aside.card_newest_comments.enable - if theme.pjax.enable || (!is_post() && page.aside !== false) + if theme.pjax.enable || (globalPageType !== 'post' && page.aside !== false) != partial("includes/third-party/newest-comments/index", {}, { cache: true }) != fragment_cache('injectBottom', function(){return injectHtml(theme.inject.bottom)}) diff --git a/layout/includes/head.pug b/layout/includes/head.pug index 56c29f0..3e77573 100644 --- a/layout/includes/head.pug +++ b/layout/includes/head.pug @@ -1,12 +1,18 @@ - var pageTitle -- is_archive() ? page.title = findArchivesTitle(page, theme.menu, date) : '' -- if (is_tag()) pageTitle = _p('page.tag') + ': ' + page.tag -- else if (is_category()) pageTitle = _p('page.category') + ': ' + page.category -- else if (is_current('/404.html', [strict])) pageTitle = _p('error404') -- else pageTitle = page.title || config.title || '' +- globalPageType === 'archive' ? page.title = findArchivesTitle(page, theme.menu, date) : '' +case globalPageType + when 'tag' + - pageTitle = _p('page.tag') + ': ' + page.tag + when 'category' + - pageTitle = _p('page.category') + ': ' + page.category + when '404' + - pageTitle = _p('error404') + default + - pageTitle = page.title || config.title || '' + - var isSubtitle = config.subtitle ? ' - ' + config.subtitle : '' -- var tabTitle = is_home() || !pageTitle ? config.title + isSubtitle : pageTitle + ' | ' + config.title +- var tabTitle = globalPageType === 'home' || !pageTitle ? config.title + isSubtitle : pageTitle + ' | ' + config.title - var pageAuthor = config.email ? config.author + ',' + config.email : config.author - var pageCopyright = config.copyright || config.author - var themeColorLight = theme.theme_color && theme.theme_color.enable && theme.theme_color.meta_theme_color_light || '#ffffff' @@ -25,6 +31,9 @@ meta(name="theme-color" content=themeColor) //- Open_Graph include ./head/Open_Graph.pug +//- Structured Data +include ./head/structured_data.pug + !=favicon_tag(theme.favicon || config.favicon) link(rel="canonical" href=urlNoIndex(null,config.pretty_urls.trailing_index,config.pretty_urls.trailing_html)) diff --git a/layout/includes/head/Open_Graph.pug b/layout/includes/head/Open_Graph.pug index abde7e6..b3628d0 100644 --- a/layout/includes/head/Open_Graph.pug +++ b/layout/includes/head/Open_Graph.pug @@ -2,7 +2,7 @@ if theme.Open_Graph_meta.enable - const coverVal = page.cover_type === 'img' ? page.cover : theme.avatar.img let ogOption = Object.assign({ - type: is_post() ? 'article' : 'website', + type: globalPageType === 'post' ? 'article' : 'website', image: coverVal ? full_url_for(coverVal) : '', fb_admins: theme.facebook_comments.user_id || '', fb_app_id: theme.facebook_comments.app_id || '', diff --git a/layout/includes/head/analytics.pug b/layout/includes/head/analytics.pug index 5c389ba..d75145d 100644 --- a/layout/includes/head/analytics.pug +++ b/layout/includes/head/analytics.pug @@ -4,7 +4,7 @@ if theme.baidu_analytics (function() { var hm = document.createElement("script"); hm.src = "https://hm.baidu.com/hm.js?!{theme.baidu_analytics}"; - var s = document.getElementsByTagName("script")[0]; + var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(hm, s); })(); btf.addGlobalFn('pjaxComplete', () => { diff --git a/layout/includes/head/config.pug b/layout/includes/head/config.pug index b23742e..efa3654 100644 --- a/layout/includes/head/config.pug +++ b/layout/includes/head/config.pug @@ -116,7 +116,7 @@ script. buttonText: '!{_p("load_more")}' }, isPhotoFigcaption: !{theme.photofigcaption}, - islazyload: !{theme.lazyload.enable}, + islazyloadPlugin: !{theme.lazyload.enable && !theme.lazyload.native}, isAnchor: !{theme.anchor.auto_update || false}, percent: { toc: !{theme.toc.scroll_percent}, diff --git a/layout/includes/head/config_site.pug b/layout/includes/head/config_site.pug index ccd45dc..5c1c67d 100644 --- a/layout/includes/head/config_site.pug +++ b/layout/includes/head/config_site.pug @@ -9,8 +9,8 @@ var showToc = false if (theme.aside.enable && page.aside !== false) { let tocEnable = false - if (is_post() && theme.toc.post) tocEnable = true - else if (is_page() && theme.toc.page) tocEnable = true + if (globalPageType === 'post' && theme.toc.post) tocEnable = true + else if (globalPageType === 'page' && theme.toc.page) tocEnable = true const pageToc = typeof page.toc === 'boolean' ? page.toc : tocEnable showToc = pageToc && (toc(page.content) !== '' || page.encrypt === true) } @@ -19,9 +19,7 @@ script#config-diff. var GLOBAL_CONFIG_SITE = { title: '!{titleVal}', - isPost: !{is_post()}, - isHome: !{is_home()}, isHighlightShrink: !{isHighlightShrink}, isToc: !{showToc}, - isShuoshuo: !{page.type == 'shuoshuo'} + pageType: '!{page.type == 'shuoshuo' ? 'shuoshuo' : globalPageType}' } diff --git a/layout/includes/head/preconnect.pug b/layout/includes/head/preconnect.pug index f8af380..cefe9fc 100644 --- a/layout/includes/head/preconnect.pug +++ b/layout/includes/head/preconnect.pug @@ -10,7 +10,7 @@ if internal_provider === third_party_provider && internal_provider !== 'local' link(rel="preconnect" href=providers[internal_provider]) -else +else if internal_provider !== 'local' link(rel="preconnect" href=providers[internal_provider]) if third_party_provider !== 'local' diff --git a/layout/includes/head/structured_data.pug b/layout/includes/head/structured_data.pug new file mode 100644 index 0000000..cacb02c --- /dev/null +++ b/layout/includes/head/structured_data.pug @@ -0,0 +1,34 @@ +if theme.structured_data && page.layout === 'post' + - + // use json-ld to add structured data + + const title = page.title + const url = page.permalink + const imageVal = page.cover_type === 'img' ? page.cover : theme.avatar.img + const image = imageVal ? full_url_for(imageVal) : '' + const datePublished = page.date.toISOString() + const dateModified = (page.updated || page.date).toISOString() + const author = page.copyright_author || config.author + const authorHrefVal = page.copyright_author_href || theme.post_copyright.author_href || site.url; + const authorHref = full_url_for(authorHrefVal); + + const jsonLd = { + "@context": "https://schema.org", + "@type": "BlogPosting", + "headline": title, + "url": url, + "image": image, + "datePublished": datePublished, + "dateModified": dateModified, + "author": [{ + "@type": "Person", + "name": author, + "url": authorHref + }] + }; + + jsonLdScript = JSON.stringify(jsonLd, null, 2); + - + + script(type="application/ld+json"). + !{jsonLdScript} diff --git a/layout/includes/header/index.pug b/layout/includes/header/index.pug index caa7998..481db70 100644 --- a/layout/includes/header/index.pug +++ b/layout/includes/header/index.pug @@ -6,33 +6,32 @@ var bg_img = '' if !theme.disable_top_img && page.top_img !== false - if is_post() - - top_img = page.top_img || page.cover || theme.default_top_img - else if is_page() - - top_img = page.top_img || theme.default_top_img - else if is_tag() - - top_img = theme.tag_per_img && theme.tag_per_img[page.tag] - - top_img = top_img || returnTopImg(theme.tag_img) - else if is_category() - - top_img = theme.category_per_img && theme.category_per_img[page.category] - - top_img = top_img || returnTopImg(theme.category_img) - else if is_home() - - top_img = returnTopImg(theme.index_img) - else if is_archive() - - top_img = returnTopImg(theme.archive_img) - else - - top_img = page.top_img || theme.default_top_img + case globalPageType + when 'post' + - top_img = page.top_img || page.cover || theme.default_top_img + when 'page' + - top_img = page.top_img || theme.default_top_img + when 'tag' + - top_img = theme.tag_per_img && theme.tag_per_img[page.tag] || returnTopImg(theme.tag_img) + when 'category' + - top_img = theme.category_per_img && theme.category_per_img[page.category] || returnTopImg(theme.category_img) + when 'home' + - top_img = returnTopImg(theme.index_img) + when 'archive' + - top_img = returnTopImg(theme.archive_img) + default + - top_img = page.top_img || theme.default_top_img if top_img !== false - bg_img = getBgPath(top_img) - - headerClassName = is_home() ? 'full_page' : is_post() ? 'post-bg' : 'not-home-page' + - headerClassName = globalPageType === 'home' ? 'full_page' : globalPageType === 'post' ? 'post-bg' : 'not-home-page' header#page-header(class=`${headerClassName + isFixedClass}` style=bg_img) include ./nav.pug if top_img !== false - if is_post() + if globalPageType === 'post' include ./post-info.pug - else if is_home() + else if globalPageType === 'home' #site-info h1#site-title=config.title if theme.subtitle.enable @@ -48,6 +47,6 @@ header#page-header(class=`${headerClassName + isFixedClass}` style=bg_img) #page-site-info h1#site-title=page.title || page.tag || page.category else - //- improvement seo - if !is_post() + //- improve seo + if globalPageType !== 'post' h1.title-seo=page.title || page.tag || page.category || config.title \ No newline at end of file diff --git a/layout/includes/header/nav.pug b/layout/includes/header/nav.pug index 97d5b1e..814a476 100644 --- a/layout/includes/header/nav.pug +++ b/layout/includes/header/nav.pug @@ -5,7 +5,7 @@ nav#nav img.site-icon(src=url_for(theme.nav.logo) alt='Logo') if theme.nav.display_title span.site-name=config.title - if is_post() + if globalPageType === 'post' a.nav-page-title(href=url_for('/')) span.site-name=(page.title || config.title) #menus diff --git a/layout/includes/layout.pug b/layout/includes/layout.pug index 81e7e25..cf3e973 100644 --- a/layout/includes/layout.pug +++ b/layout/includes/layout.pug @@ -1,7 +1,8 @@ +- var globalPageType = getPageType(page, is_home) - var htmlClassHideAside = theme.aside.enable && theme.aside.hide ? 'hide-aside' : '' -- page.aside = is_archive() ? theme.aside.display.archive: is_category() ? theme.aside.display.category : is_tag() ? theme.aside.display.tag : page.aside +- page.aside = globalPageType === 'archive' ? theme.aside.display.archive: globalPageType === 'category' ? theme.aside.display.category : globalPageType === 'tag' ? theme.aside.display.tag : page.aside - var hideAside = !theme.aside.enable || page.aside === false ? 'hide-aside' : '' -- var pageType = is_post() ? 'post' : 'page' +- var pageType = globalPageType === 'post' ? 'post' : 'page' - pageType = page.type ? pageType + ' type-' + page.type : pageType doctype html @@ -13,7 +14,7 @@ html(lang=config.language data-theme=theme.display_mode class=htmlClassHideAside if theme.background #web_bg(style=getBgPath(theme.background)) - + !=partial('includes/sidebar', {}, {cache: true}) #body-wrap(class=pageType) diff --git a/layout/includes/mixins/indexPostUI.pug b/layout/includes/mixins/indexPostUI.pug index 4c53704..4f2c3ee 100644 --- a/layout/includes/mixins/indexPostUI.pug +++ b/layout/includes/mixins/indexPostUI.pug @@ -10,7 +10,7 @@ mixin indexPostUI() - const leftOrRight = indexLayout === 3 ? (index % 2 === 0 ? 'left' : 'right') : (indexLayout === 2 ? 'right' : '') - const post_cover = article.cover - const no_cover = article.cover === false || !theme.cover.index_enable ? 'no-cover' : '' - + if post_cover && theme.cover.index_enable .post_cover(class=leftOrRight) a(href=url_for(link) title=title) @@ -20,7 +20,7 @@ mixin indexPostUI() div.post-bg(style=`background: ${post_cover}`) .recent-post-info(class=no_cover) a.article-title(href=url_for(link) title=title) - if is_home() && (article.top || article.sticky > 0) + if globalPageType === 'home' && (article.top || article.sticky > 0) i.fas.fa-thumbtack.sticky = title .article-meta-wrap @@ -58,7 +58,7 @@ mixin indexPostUI() a(href=url_for(item.path)).article-meta__tags #[=item.name] if index < article.tags.data.length - 1 span.article-meta-link #[='•'] - + mixin countBlockInIndex - needLoadCountJs = true span.article-meta @@ -67,7 +67,7 @@ mixin indexPostUI() if block block span.article-meta-label= ' ' + _p('card_post_count') - + if theme.comments.card_post_count && theme.comments.use case theme.comments.use[0] when 'Disqus' diff --git a/layout/includes/page/flink.pug b/layout/includes/page/flink.pug index 30cdad5..f94fffc 100644 --- a/layout/includes/page/flink.pug +++ b/layout/includes/page/flink.pug @@ -30,7 +30,7 @@ - + ` @@ -63,14 +63,14 @@ - let listResult = "" each j in i.link_list - - + - listResult += ` ` diff --git a/layout/includes/pagination.pug b/layout/includes/pagination.pug index ad2602b..96a3d71 100644 --- a/layout/includes/pagination.pug +++ b/layout/includes/pagination.pug @@ -6,7 +6,7 @@ escape: false } -if is_post() +if globalPageType === 'post' - let paginationOrder = theme.post_pagination === 1 ? { prev: page.prev, next: page.next } : { prev: page.next, next: page.prev } nav#pagination.pagination-post @@ -15,13 +15,13 @@ if is_post() - const getPostDesc = direction.postDesc || postDesc(direction) - let className = key === 'prev' ? (paginationOrder.next ? '' : 'full-width') : (paginationOrder.prev ? '' : 'full-width') - className = getPostDesc ? className : className + ' no-desc' - + 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`) else .cover(style=`background: ${direction.cover || 'var(--default-bg-color)'}`) - + .info(class=key === 'prev' ? '' : 'text-right') .info-1 .info-item-1=_p(`pagination.${key}`) @@ -32,6 +32,6 @@ if is_post() else nav#pagination .pagination - if is_home() + if globalPageType === 'home' - options.format = 'page/%d/#content-inner' !=paginator(options) \ No newline at end of file diff --git a/layout/includes/rightside.pug b/layout/includes/rightside.pug index 01c8021..abd1851 100644 --- a/layout/includes/rightside.pug +++ b/layout/includes/rightside.pug @@ -3,7 +3,7 @@ mixin rightsideItem(array) each item in array case item when 'readmode' - if is_post() && readmode + if globalPageType === 'post' && readmode button#readmode(type="button" title=_p('rightside.readmode_title')) i.fas.fa-book-open when 'translate' @@ -23,7 +23,7 @@ mixin rightsideItem(array) i.fas.fa-list-ul when 'chat' if chat.rightside_button && chat.use - button#chat-btn(type="button" title=_p("rightside.chat")) + button#chat-btn(type="button" title=_p("rightside.chat") style="display:none") i.fas.fa-message when 'comment' if commentsJsLoad @@ -35,7 +35,7 @@ mixin rightsideItem(array) - const hideArray = enable ? hide && hide.split(',') : ['readmode','translate','darkmode','hideAside'] - const showArray = enable ? show && show.split(',') : ['toc','chat','comment'] - + #rightside-config-hide if hideArray +rightsideItem(hideArray) @@ -45,7 +45,7 @@ mixin rightsideItem(array) button#rightside-config(type="button" title=_p("rightside.setting")) i.fas.fa-cog.fa-spin else - if is_post() + if globalPageType === 'post' if (readmode || translate.enable || (darkmode.enable && darkmode.button)) button#rightside-config(type="button" title=_p("rightside.setting")) i.fas.fa-cog.fa-spin diff --git a/layout/includes/sidebar.pug b/layout/includes/sidebar.pug index 42131cd..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) + '/') - .headline= _p('aside.categories') + 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/abcjs/index.pug b/layout/includes/third-party/abcjs/index.pug index 12d1478..d187d55 100644 --- a/layout/includes/third-party/abcjs/index.pug +++ b/layout/includes/third-party/abcjs/index.pug @@ -1,6 +1,3 @@ if theme.abcjs.enable - if theme.abcjs.per_page - if is_post() || is_page() - include ./abcjs.pug - else if page.abcjs + if theme.abcjs.per_page && (['post','page'].includes(globalPageType)) || page.abcjs include ./abcjs.pug diff --git a/layout/includes/third-party/chat/chatra.pug b/layout/includes/third-party/chat/chatra.pug index 41f0423..0fad991 100644 --- a/layout/includes/third-party/chat/chatra.pug +++ b/layout/includes/third-party/chat/chatra.pug @@ -1,42 +1,38 @@ //- https://chatra.io/help/api/ script. (() => { - const isChatBtn = !{theme.chat.rightside_button} - const isChatHideShow = !{theme.chat.button_hide_show} - - if (isChatBtn) { - const close = () => { - Chatra('minimizeWidget') - Chatra('hide') - } - - const open = () => { - Chatra('openChat', true) - Chatra('show') - } - - window.ChatraSetup = { startHidden: true } - - window.chatBtnFn = () => { - document.getElementById('chatra').classList.contains('chatra--expanded') ? close() : open() - } - } else if (isChatHideShow) { - window.chatBtn = { - hide: () => Chatra('hide'), - show: () => Chatra('show') - } + window.ChatraID = '#{theme.chatra.id}' + window.Chatra = window.Chatra || function() { + (window.Chatra.q = window.Chatra.q || []).push(arguments) } - (function(d, w, c) { - w.ChatraID = '#{theme.chatra.id}' - var s = d.createElement('script') - w[c] = w[c] || function() { - (w[c].q = w[c].q || []).push(arguments) + btf.getScript('https://call.chatra.io/chatra.js').then(() => { + const isChatBtn = !{theme.chat.rightside_button} + const isChatHideShow = !{theme.chat.button_hide_show} + + if (isChatBtn) { + const close = () => { + Chatra('minimizeWidget') + Chatra('hide') + } + + const open = () => { + Chatra('openChat', true) + Chatra('show') + } + + window.ChatraSetup = { startHidden: true } + + window.chatBtnFn = () => document.getElementById('chatra').classList.contains('chatra--expanded') ? close() : open() + + document.getElementById('chat-btn').style.display = 'block' + } else if (isChatHideShow) { + window.chatBtn = { + hide: () => Chatra('hide'), + show: () => Chatra('show') + } } - s.async = true - s.src = 'https://call.chatra.io/chatra.js' - if (d.head) d.head.appendChild(s) - })(document, window, 'Chatra') + }) })() diff --git a/layout/includes/third-party/chat/crisp.pug b/layout/includes/third-party/chat/crisp.pug index e25a17f..08a1916 100644 --- a/layout/includes/third-party/chat/crisp.pug +++ b/layout/includes/third-party/chat/crisp.pug @@ -1,37 +1,32 @@ script. (() => { - window.$crisp = []; - window.CRISP_WEBSITE_ID = "!{theme.crisp.website_id}"; - (function () { - d = document; - s = d.createElement("script"); - s.src = "https://client.crisp.chat/l.js"; - s.async = 1; - d.getElementsByTagName("head")[0].appendChild(s); - })(); - $crisp.push(["safe", true]) + window.$crisp = ['safe', true] + window.CRISP_WEBSITE_ID = "!{theme.crisp.website_id}" - const isChatBtn = !{theme.chat.rightside_button} - const isChatHideShow = !{theme.chat.button_hide_show} + btf.getScript('https://client.crisp.chat/l.js').then(() => { + const isChatBtn = !{theme.chat.rightside_button} + const isChatHideShow = !{theme.chat.button_hide_show} - if (isChatBtn) { - const open = () => { - $crisp.push(["do", "chat:show"]) - $crisp.push(["do", "chat:open"]) + if (isChatBtn) { + const open = () => { + $crisp.push(["do", "chat:show"]) + $crisp.push(["do", "chat:open"]) + } + + const close = () => $crisp.push(["do", "chat:hide"]) + + close() + + $crisp.push(["on", "chat:closed", close]) + + window.chatBtnFn = () => $crisp.is("chat:visible") ? close() : open() + + document.getElementById('chat-btn').style.display = 'block' + } else if (isChatHideShow) { + window.chatBtn = { + hide: () => $crisp.push(["do", "chat:hide"]), + show: () => $crisp.push(["do", "chat:show"]) + } } - - const close = () => $crisp.push(["do", "chat:hide"]) - - close() - - $crisp.push(["on", "chat:closed", close]) - - window.chatBtnFn = () => $crisp.is("chat:visible") ? close() : open() - - } else if (isChatHideShow) { - window.chatBtn = { - hide: () => $crisp.push(["do", "chat:hide"]), - show: () => $crisp.push(["do", "chat:show"]) - } - } + }) })() \ No newline at end of file diff --git a/layout/includes/third-party/chat/tidio.pug b/layout/includes/third-party/chat/tidio.pug index 78361af..f9d40ea 100644 --- a/layout/includes/third-party/chat/tidio.pug +++ b/layout/includes/third-party/chat/tidio.pug @@ -1,41 +1,45 @@ -script(src=`//code.tidio.co/${theme.tidio.public_key}.js` async) script. (() => { - const isChatBtn = !{theme.chat.rightside_button} - const isChatHideShow = !{theme.chat.button_hide_show} + btf.getScript('//code.tidio.co/!{theme.tidio.public_key}.js').then(() => { + const isChatBtn = !{theme.chat.rightside_button} + const isChatHideShow = !{theme.chat.button_hide_show} - if (isChatBtn) { - let isShow = false - const close = () => { - window.tidioChatApi.hide() - isShow = false - } - - const open = () => { - window.tidioChatApi.open() - window.tidioChatApi.show() - isShow = true - } + if (isChatBtn) { + let isShow = false + const close = () => { + window.tidioChatApi.hide() + isShow = false + } + + const open = () => { + window.tidioChatApi.open() + window.tidioChatApi.show() + isShow = true + } - const onTidioChatApiReady = () => { - window.tidioChatApi.hide() - window.tidioChatApi.on("close", close) - } - if (window.tidioChatApi) { - window.tidioChatApi.on("ready", onTidioChatApiReady) - } else { - document.addEventListener("tidioChat-ready", onTidioChatApiReady) - } + const onTidioChatApiReady = () => { + window.tidioChatApi.hide() + window.tidioChatApi.on("close", close) + } + if (window.tidioChatApi) { + window.tidioChatApi.on("ready", onTidioChatApiReady) + } else { + document.addEventListener("tidioChat-ready", onTidioChatApiReady) + } - window.chatBtnFn = () => { - if (!window.tidioChatApi) return - isShow ? close() : open() + window.chatBtnFn = () => { + if (!window.tidioChatApi) return + isShow ? close() : open() + } + + document.getElementById('chat-btn').style.display = 'block' + + } else if (isChatHideShow) { + window.chatBtn = { + hide: () => window.tidioChatApi && window.tidioChatApi.hide(), + show: () => window.tidioChatApi && window.tidioChatApi.show() + } } - } else if (isChatHideShow) { - window.chatBtn = { - hide: () => window.tidioChatApi && window.tidioChatApi.hide(), - show: () => window.tidioChatApi && window.tidioChatApi.show() - } - } + }) })() diff --git a/layout/includes/third-party/comments/artalk.pug b/layout/includes/third-party/comments/artalk.pug index 3640334..0113fc3 100644 --- a/layout/includes/third-party/comments/artalk.pug +++ b/layout/includes/third-party/comments/artalk.pug @@ -5,7 +5,7 @@ script. (() => { let artalkItem = null const option = !{JSON.stringify(option)} - const isShuoshuo = GLOBAL_CONFIG_SITE.isShuoshuo + const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo' const destroyArtalk = () => { if (artalkItem) { diff --git a/layout/includes/third-party/comments/disqus.pug b/layout/includes/third-party/comments/disqus.pug index c521af3..eeca5c4 100644 --- a/layout/includes/third-party/comments/disqus.pug +++ b/layout/includes/third-party/comments/disqus.pug @@ -4,7 +4,7 @@ script. (() => { - const isShuoshuo = GLOBAL_CONFIG_SITE.isShuoshuo + const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo' const disqusReset = conf => { window.DISQUS && window.DISQUS.reset({ @@ -72,7 +72,7 @@ script. if (!{lazyload}) btf.loadComment(document.getElementById('disqus_thread'), loadDisqus) else { loadDisqus() - !{ count ? 'GLOBAL_CONFIG_SITE.isPost && getCount()' : '' } + !{ count ? `GLOBAL_CONFIG_SITE.pageType === 'post' && getCount()` : '' } } } else { window.loadOtherComment = loadDisqus diff --git a/layout/includes/third-party/comments/disqusjs.pug b/layout/includes/third-party/comments/disqusjs.pug index 3141611..ae7ff06 100644 --- a/layout/includes/third-party/comments/disqusjs.pug +++ b/layout/includes/third-party/comments/disqusjs.pug @@ -3,7 +3,7 @@ script. (() => { - const isShuoshuo = GLOBAL_CONFIG_SITE.isShuoshuo + const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'== 'shuoshuo' const dqOption = !{JSON.stringify(dqOption)} const destroyDisqusjs = () => { @@ -79,7 +79,7 @@ script. if (!{theme.comments.lazyload}) btf.loadComment(document.getElementById('disqusjs-wrap'), loadDisqusjs) else { loadDisqusjs() - !{ theme.comments.count ? 'GLOBAL_CONFIG_SITE.isPost && getCount()' : '' } + !{ theme.comments.count ? `GLOBAL_CONFIG_SITE.pageType === 'post' && getCount()` : '' } } } else { window.loadOtherComment = loadDisqusjs diff --git a/layout/includes/third-party/comments/facebook_comments.pug b/layout/includes/third-party/comments/facebook_comments.pug index 7b070d4..c62fa30 100644 --- a/layout/includes/third-party/comments/facebook_comments.pug +++ b/layout/includes/third-party/comments/facebook_comments.pug @@ -3,7 +3,7 @@ script. (()=>{ - const isShuoshuo = GLOBAL_CONFIG_SITE.isShuoshuo + const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'== 'shuoshuo' const loadFBComment = (el = document, path) => { if (isShuoshuo) { diff --git a/layout/includes/third-party/comments/giscus.pug b/layout/includes/third-party/comments/giscus.pug index bdc189f..c370e98 100644 --- a/layout/includes/third-party/comments/giscus.pug +++ b/layout/includes/third-party/comments/giscus.pug @@ -5,7 +5,7 @@ script. (() => { - const isShuoshuo = GLOBAL_CONFIG_SITE.isShuoshuo + const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo' const option = !{JSON.stringify(option)} const getGiscusTheme = theme => theme === 'dark' ? '!{dark_theme}' : '!{light_theme}' diff --git a/layout/includes/third-party/comments/gitalk.pug b/layout/includes/third-party/comments/gitalk.pug index 0b0a93e..d13bcb3 100644 --- a/layout/includes/third-party/comments/gitalk.pug +++ b/layout/includes/third-party/comments/gitalk.pug @@ -2,7 +2,7 @@ script. (() => { - const isShuoshuo = GLOBAL_CONFIG_SITE.isShuoshuo + const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo' const option = !{JSON.stringify(option)} const commentCount = n => { diff --git a/layout/includes/third-party/comments/livere.pug b/layout/includes/third-party/comments/livere.pug index 4c396c7..f55cd96 100644 --- a/layout/includes/third-party/comments/livere.pug +++ b/layout/includes/third-party/comments/livere.pug @@ -2,7 +2,7 @@ script. (() => { - const isShuoshuo = GLOBAL_CONFIG_SITE.isShuoshuo + const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo' const loadLivere = (el, path) => { window.livereOptions = { diff --git a/layout/includes/third-party/comments/remark42.pug b/layout/includes/third-party/comments/remark42.pug index 0a1fbe7..67980cb 100644 --- a/layout/includes/third-party/comments/remark42.pug +++ b/layout/includes/third-party/comments/remark42.pug @@ -2,7 +2,7 @@ script. (() => { - const isShuoshuo = GLOBAL_CONFIG_SITE.isShuoshuo + const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo' const option = !{JSON.stringify(option)} const loadScript = src => { diff --git a/layout/includes/third-party/comments/twikoo.pug b/layout/includes/third-party/comments/twikoo.pug index ac91bcf..31fec77 100644 --- a/layout/includes/third-party/comments/twikoo.pug +++ b/layout/includes/third-party/comments/twikoo.pug @@ -3,7 +3,7 @@ script. (() => { - const isShuoshuo = GLOBAL_CONFIG_SITE.isShuoshuo + const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo' const option = !{JSON.stringify(option)} const getCount = () => { @@ -33,7 +33,7 @@ script. path: isShuoshuo ? path : (option && option.path) || path }) - !{count ? 'GLOBAL_CONFIG_SITE.isPost && getCount()' : ''} + !{count ? `GLOBAL_CONFIG_SITE.pageType === 'post' && getCount()` : ''} isShuoshuo && (window.shuoshuoComment.destroyTwikoo = () => { if (el.children.length) { diff --git a/layout/includes/third-party/comments/utterances.pug b/layout/includes/third-party/comments/utterances.pug index aaabd6f..4836557 100644 --- a/layout/includes/third-party/comments/utterances.pug +++ b/layout/includes/third-party/comments/utterances.pug @@ -5,7 +5,7 @@ script. (() => { - const isShuoshuo = GLOBAL_CONFIG_SITE.isShuoshuo + const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo' const option = !{JSON.stringify(option)} const getUtterancesTheme = theme => theme === 'dark' ? '#{dark_theme}' : '#{light_theme}' diff --git a/layout/includes/third-party/comments/valine.pug b/layout/includes/third-party/comments/valine.pug index 4cdd140..9ac2c43 100644 --- a/layout/includes/third-party/comments/valine.pug +++ b/layout/includes/third-party/comments/valine.pug @@ -7,7 +7,7 @@ if site.data.valine script. (() => { - const isShuoshuo = GLOBAL_CONFIG_SITE.isShuoshuo + const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo' const option = !{JSON.stringify(option)} const initValine = (el, path) => { diff --git a/layout/includes/third-party/comments/waline.pug b/layout/includes/third-party/comments/waline.pug index 8f914f4..99657e7 100644 --- a/layout/includes/third-party/comments/waline.pug +++ b/layout/includes/third-party/comments/waline.pug @@ -4,7 +4,7 @@ script. (() => { let initFn = window.walineFn || null - const isShuoshuo = GLOBAL_CONFIG_SITE.isShuoshuo + const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo' const option = !{JSON.stringify(option)} const destroyWaline = ele => ele.destroy() diff --git a/layout/includes/third-party/math/chartjs.pug b/layout/includes/third-party/math/chartjs.pug index fb1554f..235d8e4 100644 --- a/layout/includes/third-party/math/chartjs.pug +++ b/layout/includes/third-party/math/chartjs.pug @@ -2,9 +2,6 @@ script. (() => { - const $chartjs = document.querySelectorAll('#article-container .chartjs-container') - if ($chartjs.length === 0) return - const applyThemeDefaultsConfig = theme => { if (theme === 'dark-mode') { Chart.defaults.color = "!{fontColor.dark}" @@ -20,7 +17,7 @@ script. // Recursively traverse the config object and automatically apply theme-specific color schemes const applyThemeConfig = (obj, theme) => { if (typeof obj !== 'object' || obj === null) return - + Object.keys(obj).forEach(key => { const value = obj[key] // If the property is an object and has theme-specific options, apply them @@ -35,10 +32,10 @@ script. }) } - const runChartJS = () => { + const runChartJS = ele => { window.loadChartJS = true - Array.from($chartjs).forEach((item, index) => { + Array.from(ele).forEach((item, index) => { const chartSrc = item.firstElementChild const chartID = item.getAttribute('data-chartjs-id') || ('chartjs-' + index) // Use custom ID or default ID const width = item.getAttribute('data-width') @@ -80,12 +77,15 @@ script. } const loadChartJS = () => { - window.loadChartJS ? runChartJS() : btf.getScript('!{url_for(theme.asset.chartjs)}').then(runChartJS) + const chartJSEle = document.querySelectorAll('#article-container .chartjs-container') + if (chartJSEle.length === 0) return + + window.loadChartJS ? runChartJS(chartJSEle) : btf.getScript('!{url_for(theme.asset.chartjs)}').then(() => runChartJS(chartJSEle)) } // Listen for theme change events - btf.addGlobalFn('themeChange', runChartJS, 'chartjs') - btf.addGlobalFn('encrypt', runChartJS, 'chartjs') + btf.addGlobalFn('themeChange', loadChartJS, 'chartjs') + btf.addGlobalFn('encrypt', loadChartJS, 'chartjs') window.pjax ? loadChartJS() : document.addEventListener('DOMContentLoaded', loadChartJS) })() diff --git a/layout/includes/third-party/math/index.pug b/layout/includes/third-party/math/index.pug index 217eb72..93302f8 100644 --- a/layout/includes/third-party/math/index.pug +++ b/layout/includes/third-party/math/index.pug @@ -1,10 +1,10 @@ case theme.math.use when 'mathjax' - if (theme.math.per_page && (is_post() || is_page())) || page.mathjax + if (theme.math.per_page && (['post','page'].includes(globalPageType))) || page.mathjax include ./mathjax.pug when 'katex' - if (theme.math.per_page && (is_post() || is_page())) || page.katex + if (theme.math.per_page && (['post','page'].includes(globalPageType))) || page.katex include ./katex.pug if theme.mermaid.enable diff --git a/layout/includes/third-party/math/mathjax.pug b/layout/includes/third-party/math/mathjax.pug index 75775b5..f0483ef 100644 --- a/layout/includes/third-party/math/mathjax.pug +++ b/layout/includes/third-party/math/mathjax.pug @@ -29,7 +29,7 @@ script. } } } - + const script = document.createElement('script') script.src = '!{url_for(theme.asset.mathjax)}' script.id = 'MathJax-script' diff --git a/layout/includes/third-party/newest-comments/artalk.pug b/layout/includes/third-party/newest-comments/artalk.pug index af669ee..4e703b2 100644 --- a/layout/includes/third-party/newest-comments/artalk.pug +++ b/layout/includes/third-party/newest-comments/artalk.pug @@ -18,7 +18,7 @@ script. if (predefinedAvatarCdn && predefinedAvatarDefault) { return { avatarCdn: predefinedAvatarCdn, avatarDefault: avatarDefaultFormat(predefinedAvatarDefault) } } - + try { const res = await fetch('!{server}/api/v2/conf') const result = await res.json() @@ -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/common.pug b/layout/includes/third-party/newest-comments/common.pug index 093bd27..f3b88f5 100644 --- a/layout/includes/third-party/newest-comments/common.pug +++ b/layout/includes/third-party/newest-comments/common.pug @@ -6,7 +6,7 @@ script. content = content.replace(/]+>/ig, '[!{_p("aside.card_newest_comments.image")}]') // replace image link content = content.replace(/]+?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 ` - ` + Group Image Gallery +
+ +

${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 `
` } 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 += `
${mdContent}
` - } + // 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 + ? `
+
+
${renderMd(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(title)}
+
+
${renderMd(content)}
+
` + ) + .join('') - const tlTitleHtml = `
${tlChildTitle}
` - 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() }