diff --git a/README.md b/README.md index a45b670..d997872 100644 --- a/README.md +++ b/README.md @@ -124,7 +124,6 @@ npm install hexo-renderer-pug hexo-renderer-stylus --save - [x] **Theme Customization** - Custom site color schemes - [x] **Chart Support** - Mermaid flowcharts/Chart.js data charts - [x] **Music Notation** - ABCJS music notation support -- [x] **Music Player** - APlayer/Meting music playback - [x] **Article Series** - Series article organization - [x] **Instantpage** - Page preloading acceleration - [x] **Snackbar** - Elegant notification messages diff --git a/README_CN.md b/README_CN.md index f1bb1b5..1387625 100644 --- a/README_CN.md +++ b/README_CN.md @@ -124,7 +124,6 @@ npm install hexo-renderer-pug hexo-renderer-stylus --save - [x] **主題定製** - 自訂網站配色方案 - [x] **圖表支援** - Mermaid 流程圖/Chart.js 數據圖表 - [x] **音樂符號** - ABCJS 音樂記譜法支援 -- [x] **音樂播放器** - APlayer/Meting 音樂播放功能 - [x] **系列文章** - 系列文章組織功能 - [x] **Instantpage** - 頁面預載入加速 - [x] **Snackbar** - 優雅的提示訊息 diff --git a/_config.yml b/_config.yml index 36c9211..932f048 100644 --- a/_config.yml +++ b/_config.yml @@ -993,11 +993,6 @@ pjax: exclude: # - /xxxxxx/ -# Inject the css and script (aplayer/meting) -aplayerInject: - enable: false - per_page: true - # Snackbar - Toast Notification # https://github.com/polonel/SnackBar # position: top-left / top-center / top-right / bottom-left / bottom-center / bottom-right @@ -1088,8 +1083,6 @@ CDN: # activate_power_mode: # algolia_js: # algolia_search: - # aplayer_css: - # aplayer_js: # artalk_css: # artalk_js: # blueimp_md5: @@ -1122,7 +1115,6 @@ CDN: # mathjax: # medium_zoom: # mermaid: - # meting_js: # prismjs_autoloader: # prismjs_js: # prismjs_lineNumber_js: diff --git a/layout/includes/additional-js.pug b/layout/includes/additional-js.pug index 9585a7b..c79049a 100644 --- a/layout/includes/additional-js.pug +++ b/layout/includes/additional-js.pug @@ -41,10 +41,6 @@ div != partial("includes/third-party/effect", {}, { cache: true }) != partial("includes/third-party/chat/index", {}, { cache: true }) - if theme.aplayerInject && theme.aplayerInject.enable - if theme.pjax.enable || theme.aplayerInject.per_page || page.aplayer - include ./third-party/aplayer.pug - if theme.pjax.enable != partial("includes/third-party/pjax", {}, { cache: true }) diff --git a/layout/includes/page/shuoshuo.pug b/layout/includes/page/shuoshuo.pug index fd93334..de4dbf6 100644 --- a/layout/includes/page/shuoshuo.pug +++ b/layout/includes/page/shuoshuo.pug @@ -161,7 +161,7 @@ container.innerHTML = content window.lazyLoadInstance && window.lazyLoadInstance.update() - btf.loadLightbox(document.querySelectorAll('#article-container img:not(.no-lightbox)')) + document.dispatchEvent(new Event('shuoshuo:rendered')) } const setupNavEvents = (nav) => { diff --git a/layout/includes/third-party/aplayer.pug b/layout/includes/third-party/aplayer.pug deleted file mode 100644 index 4e86609..0000000 --- a/layout/includes/third-party/aplayer.pug +++ /dev/null @@ -1,23 +0,0 @@ -link(rel='stylesheet' href=url_for(theme.asset.aplayer_css) media="print" onload="this.media='all'") -script(src=url_for(theme.asset.aplayer_js)) -script(src=url_for(theme.asset.meting_js)) -if theme.pjax.enable - script. - (() => { - const destroyAplayer = () => { - if (window.aplayers) { - for (let i = 0; i < window.aplayers.length; i++) { - if (!window.aplayers[i].options.fixed) { - window.aplayers[i].destroy() - } - } - } - } - - const runMetingJS = () => { - typeof loadMeting === 'function' && document.getElementsByClassName('aplayer').length && loadMeting() - } - - btf.addGlobalFn('pjaxSend', destroyAplayer, 'destroyAplayer') - btf.addGlobalFn('pjaxComplete', loadMeting, 'runMetingJS') - })() diff --git a/layout/includes/third-party/comments/index.pug b/layout/includes/third-party/comments/index.pug index eeaf339..2696b85 100644 --- a/layout/includes/third-party/comments/index.pug +++ b/layout/includes/third-party/comments/index.pug @@ -43,4 +43,13 @@ hr.custom-hr when 'Remark42' #remark42 when 'Artalk' + if theme.artalk.vote + .artalk-vote + span.artalk-page-vote-up + i.fas.fa-thumbs-up + span.artalk-page-vote-up-count + span.artalk-page-vote-down + i.fas.fa-thumbs-down + span.artalk-page-vote-down-count #artalk-wrap + diff --git a/package.json b/package.json index f7ee6a9..db03c50 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hexo-theme-butterfly", - "version": "5.6.1", + "version": "5.7.0", "description": "A Simple and Card UI Design theme for Hexo", "main": "package.json", "scripts": { @@ -26,7 +26,7 @@ "hexo-renderer-pug": "^3.0.0", "hexo-renderer-stylus": "^3.0.1", "hexo-util": "^4.0.0", - "moment-timezone": "^0.6.2" + "moment-timezone": "^0.6.3" }, "homepage": "https://butterfly.js.org/", "author": "Jerry ", diff --git a/plugins.yml b/plugins.yml index ae5f39c..156daf2 100644 --- a/plugins.yml +++ b/plugins.yml @@ -10,22 +10,14 @@ algolia_search: name: algoliasearch file: dist/lite/builds/browser.umd.js version: 5.56.0 -aplayer_css: - name: aplayer - file: dist/APlayer.min.css - version: 1.10.1 -aplayer_js: - name: aplayer - file: dist/APlayer.min.js - version: 1.10.1 artalk_css: name: artalk file: dist/Artalk.css - version: 2.9.1 + version: 2.10.0 artalk_js: name: artalk file: dist/Artalk.js - version: 2.9.1 + version: 2.10.0 blueimp_md5: name: blueimp-md5 file: js/md5.min.js @@ -66,12 +58,12 @@ docsearch_css: name: '@docsearch/css' other_name: docsearch-css file: dist/style.css - version: 4.6.3 + version: 4.7.0 docsearch_js: name: '@docsearch/js' other_name: docsearch-js file: dist/umd/index.js - version: 4.6.3 + version: 4.7.0 egjs_infinitegrid: name: '@egjs/infinitegrid' other_name: egjs-infinitegrid @@ -80,13 +72,13 @@ egjs_infinitegrid: fancybox: name: '@fancyapps/ui' file: dist/fancybox/fancybox.umd.js - version: 6.1.14 other_name: fancyapps-ui + version: 6.1.14 fancybox_css: name: '@fancyapps/ui' file: dist/fancybox/fancybox.css - version: 6.1.14 other_name: fancyapps-ui + version: 6.1.14 fireworks: name: butterfly-extsrc file: dist/fireworks.min.js @@ -112,12 +104,12 @@ katex: name: katex file: dist/katex.min.css other_name: KaTeX - version: 0.17.0 + version: 0.18.1 katex_copytex: name: katex file: dist/contrib/copy-tex.min.js other_name: KaTeX - version: 0.17.0 + version: 0.18.1 lazyload: name: vanilla-lazyload file: dist/lazyload.iife.min.js @@ -134,10 +126,6 @@ mermaid: name: mermaid file: dist/mermaid.min.js version: 11.16.0 -meting_js: - name: butterfly-extsrc - file: metingjs/dist/Meting.min.js - version: 1.1.6 pace_default_css: name: pace-js other_name: pace @@ -186,7 +174,7 @@ snackbar_css: twikoo: name: twikoo file: dist/twikoo.all.min.js - version: 1.7.14 + version: 1.7.15 typed: name: typed.js file: dist/typed.umd.js diff --git a/scripts/common/default_config.js b/scripts/common/default_config.js index 5ae0cd4..dc7ce74 100644 --- a/scripts/common/default_config.js +++ b/scripts/common/default_config.js @@ -378,6 +378,7 @@ module.exports = { server: null, site: null, visitor: false, + vote: false, option: null }, chat: { @@ -555,10 +556,6 @@ module.exports = { enable: false, exclude: null }, - aplayerInject: { - enable: false, - per_page: true - }, snackbar: { enable: false, position: 'bottom-left', diff --git a/scripts/events/404.js b/scripts/events/404.js index 157ef06..9d18c7c 100644 --- a/scripts/events/404.js +++ b/scripts/events/404.js @@ -6,7 +6,8 @@ 'use strict' hexo.extend.generator.register('404', function (locals) { - if (!hexo.theme.config.error_404.enable) return + const error404 = hexo.theme.config.error_404 || {} + if (!error404.enable) return return { path: '404.html', layout: ['page'], diff --git a/scripts/events/cdn.js b/scripts/events/cdn.js index cc1ac27..04ec649 100644 --- a/scripts/events/cdn.js +++ b/scripts/events/cdn.js @@ -8,11 +8,19 @@ const { version } = require('../../package.json') const path = require('path') +// Cache plugins.yml to avoid re-rendering on every generate +let cachedPlugins = null +const loadPlugins = hexo => { + if (cachedPlugins) return cachedPlugins + cachedPlugins = hexo.render.renderSync({ path: path.join(hexo.theme_dir, '/plugins.yml'), engine: 'yaml' }) + return cachedPlugins +} + hexo.extend.filter.register('before_generate', () => { const themeConfig = hexo.theme.config const { CDN } = themeConfig - const thirdPartySrc = hexo.render.renderSync({ path: path.join(hexo.theme_dir, '/plugins.yml'), engine: 'yaml' }) + const thirdPartySrc = loadPlugins(hexo) const internalSrc = { main: { name: 'hexo-theme-butterfly', diff --git a/scripts/events/stylus.js b/scripts/events/stylus.js index 486a83e..e350f69 100644 --- a/scripts/events/stylus.js +++ b/scripts/events/stylus.js @@ -5,7 +5,7 @@ 'use strict' hexo.extend.filter.register('stylus:renderer', style => { - const { syntax_highlighter: syntaxHighlighter, highlight, prismjs } = hexo.config + const { syntax_highlighter: syntaxHighlighter, highlight = {}, prismjs = {} } = hexo.config let { enable: highlightEnable, line_number: highlightLineNumber } = highlight let { enable: prismjsEnable, line_number: prismjsLineNumber } = prismjs diff --git a/scripts/helpers/aside_archives.js b/scripts/helpers/aside_archives.js index e742f63..b390928 100644 --- a/scripts/helpers/aside_archives.js +++ b/scripts/helpers/aside_archives.js @@ -69,18 +69,14 @@ hexo.extend.helper.register('aside_archives', function (options = {}) { // Use template literal for better readability const archiveHeader = ` -
- - ${_p('aside.card_archives')} - ${ - data.length > limitedData.length - ? `${_p('aside.card_archives')}${ + data.length > limitedData.length + ? ` ` - : '' - } -
+ : '' + } ` // Use map for generating list items, join for performance diff --git a/scripts/helpers/aside_categories.js b/scripts/helpers/aside_categories.js index 050d722..7fe9a6f 100644 --- a/scripts/helpers/aside_categories.js +++ b/scripts/helpers/aside_categories.js @@ -89,11 +89,7 @@ hexo.extend.helper.register('aside_categories', function (categories, options = ` : '' - return `
- - ${this._p('aside.card_categories')} - ${moreButton} -
+ return `
${this._p('aside.card_categories')}${moreButton}
` diff --git a/scripts/helpers/page.js b/scripts/helpers/page.js index a9cf5bb..f0d72e4 100644 --- a/scripts/helpers/page.js +++ b/scripts/helpers/page.js @@ -157,8 +157,40 @@ hexo.extend.helper.register('shuoshuoFN', (data, page) => { processedData.forEach(item => { const parsed = moment.utc(item.date) item.date = moment.tz(parsed.format('YYYY-MM-DD HH:mm:ss'), timezone).format('YYYY-MM-DD HH:mm:ss') - // markdown + + // Render the content using Hexo's rendering engine to process any tags or markdown + const mockPost = { + content: item.content, + source: item.source || page.source || '', + full_source: page.full_source || '', + path: page.path || '', + layout: item.layout || page.layout || 'post', + raw: item.raw || item.content, + draft: false, + published: true + } + try { + hexo.execFilterSync('before_post_render', mockPost, { context: hexo }) + } catch (e) { + // ignore third-party plugin errors, fallback to markdown-only rendering + } + item.content = mockPost.content + + const codeBlocks = [] + item.content = item.content.replace(/([\s\S]*?)<\/hexoPostRenderCodeBlock>/g, (_, c) => { + codeBlocks.push(c) + return `` + }) + + try { + item.content = hexo.extend.tag.env.renderString(item.content, {}) + } catch (e) { + // ignore tag plugin errors, fallback to markdown-only rendering + } + item.content = hexo.render.renderSync({ text: item.content, engine: 'markdown' }) + + item.content = item.content.replace(//g, (_, i) => codeBlocks[+i]) }) return processedData diff --git a/scripts/tag/series.js b/scripts/tag/series.js index 8eb9d79..9608062 100644 --- a/scripts/tag/series.js +++ b/scripts/tag/series.js @@ -23,7 +23,7 @@ function buildSeriesGroups () { groups[p.series].push({ title: p.title, path: p.path, - date: p.date.unix() + date: p.date.valueOf() }) } } diff --git a/source/css/_global/index.styl b/source/css/_global/index.styl index 996265e..c0e2c88 100644 --- a/source/css/_global/index.styl +++ b/source/css/_global/index.styl @@ -131,6 +131,10 @@ body *::-webkit-scrollbar-track background-color: transparent +// make the scrollbar transparent when fancybox is open +html.with-fancybox + --scrollbar-color: transparent + input::placeholder color: var(--font-color) diff --git a/source/css/_layout/third-party.styl b/source/css/_layout/third-party.styl index 54ab8c7..534bf8d 100644 --- a/source/css/_layout/third-party.styl +++ b/source/css/_layout/third-party.styl @@ -28,6 +28,107 @@ .wl-comment-actions > button:not(last-child) padding-right: 4px +if hexo-config('artalk.vote') + .artalk-vote + display: flex + justify-content: center + align-items: center + margin: 4px 0 20px + gap: 10px + + .artalk-page-vote-up, + .artalk-page-vote-down + position: relative + display: inline-flex + align-items: center + overflow: hidden + padding: 5px 16px + border: 1px solid alpha($theme-color, .15) + gap: 6px + addBorderRadius(8) + background: var(--card-bg) + color: var(--font-color) + font-weight: 500 + font-size: .95em + cursor: pointer + transition: all .2s ease + user-select: none + + i, + .artalk-page-vote-up-count, + .artalk-page-vote-down-count + position: relative + z-index: 1 + + i + font-size: 1em + line-height: 1 + + .artalk-page-vote-up-count, + .artalk-page-vote-down-count + min-width: 1em + text-align: center + font-weight: 600 + font-size: .9em + font-variant-numeric: tabular-nums + + &::after + position: absolute + top: 50% + left: 50% + z-index: 0 + width: 8px + height: 8px + border-radius: 50% + content: '' + opacity: 0 + pointer-events: none + + &:hover + border-color: $theme-color + + &:active + transform: scale(.95) + + &::after + animation: artalkVoteRipple .6s ease-out forwards + + .artalk-page-vote-up + &:hover, + &.active + border-color: $theme-color + background: alpha($theme-color, .1) + color: $theme-color + + &::after + background: alpha($theme-color, .5) + + .artalk-page-vote-down + &:hover, + &.active + border-color: $light-red + background: alpha($light-red, .1) + color: $light-red + + &::after + background: alpha($light-red, .5) + + +maxWidth600() + gap: 8px + + .artalk-page-vote-up, + .artalk-page-vote-down + padding: 4px 12px + + @keyframes artalkVoteRipple + 0% + opacity: .8 + transform: translate(-50%, -50%) scale(0) + + 100% + opacity: 0 + transform: translate(-50%, -50%) scale(20) + if hexo-config('valine.bg') #vcomment textarea @@ -193,26 +294,6 @@ if hexo-config('math.use') right: 0 bottom: 0 -.aplayer - color: $font-black - -.container - .aplayer - margin: 0 0 20px - - if hexo-config('beautify.enable') - ol, - ul - margin: 0 - padding: 0 - - li - margin: 0 - padding: 0 15px - - &:before - content: none - .snackbar-container.snackbar-css addBorderRadius(5) opacity: .85 !important diff --git a/source/css/_mode/darkmode.styl b/source/css/_mode/darkmode.styl index cef3662..c002a76 100644 --- a/source/css/_mode/darkmode.styl +++ b/source/css/_mode/darkmode.styl @@ -105,9 +105,6 @@ if hexo-config('darkmode.enable') || hexo-config('display_mode') == 'dark' code background: $code-background - .aplayer - filter: brightness(.8) - kbd border-color: #696969 background-color: #525252 diff --git a/source/js/main.js b/source/js/main.js index 5fc6dca..4cca9aa 100644 --- a/source/js/main.js +++ b/source/js/main.js @@ -267,23 +267,23 @@ document.addEventListener('DOMContentLoaded', () => { const fetchUrl = async url => { try { const response = await fetch(url) - if (!response.ok) throw new Error(`HTTP ${response.status}`) + if (!response.ok) throw new Error(`HTTP ${response.status}`) return await response.json() - } catch (error) { - console.error('Failed to fetch URL:', error) - throw error - } + } catch (error) { + console.error('Failed to fetch URL:', error) + throw error + } } const runJustifiedGallery = (container, data, config) => { - const { isButton, tabs } = config - const limit = Math.max(1, Number(config.limit) || 20) - const firstLimit = Math.max(1, Number(config.firstLimit) || limit) + const { isButton, tabs } = config + const limit = Math.max(1, Number(config.limit) || 20) + const firstLimit = Math.max(1, Number(config.firstLimit) || limit) const dataLength = data.length - const maxGroupKey = dataLength - ? Math.ceil(Math.max(0, dataLength - firstLimit) / limit) + 1 - : 0 + const maxGroupKey = dataLength + ? Math.ceil(Math.max(0, dataLength - firstLimit) / limit) + 1 + : 0 // Gallery configuration const igConfig = { @@ -300,19 +300,19 @@ document.addEventListener('DOMContentLoaded', () => { let isLayoutHidden = false // Utility functions - const sanitizeString = str => String(str ?? '') - .replace(/&/g, '&') - .replace(/"/g, '"') - .replace(/'/g, ''') - .replace(//g, '>') + const sanitizeString = str => String(str ?? '') + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(/'/g, ''') + .replace(//g, '>') const createImageItem = item => { const alt = item.alt ? `alt="${sanitizeString(item.alt)}"` : '' const title = item.title ? `title="${sanitizeString(item.title)}"` : '' - const url = item.url ? sanitizeString(item.url) : '' + const url = item.url ? sanitizeString(item.url) : '' return `
- +
` } @@ -381,13 +381,13 @@ document.addEventListener('DOMContentLoaded', () => { btf.setLoading.add(container) ig.on('renderComplete', handleRenderComplete) - if (isButton && dataLength) { + if (isButton && dataLength) { appendItems(1, firstLimit, true) - } else if (dataLength) { + } else if (dataLength) { ig.on('requestAppend', handleRequestAppend) ig.renderItems() - } else { - btf.setLoading.remove(container) + } else { + btf.setLoading.remove(container) } btf.addGlobalFn('pjaxSendOnce', () => ig.destroy()) @@ -412,9 +412,9 @@ document.addEventListener('DOMContentLoaded', () => { container.textContent = '' try { const data = element.getAttribute('data-type') === 'url' ? await fetchUrl(content) : JSON.parse(content) - if (!Array.isArray(data)) throw new TypeError('Gallery data must be an array') + if (!Array.isArray(data)) throw new TypeError('Gallery data must be an array') runJustifiedGallery(container, data, config) - element.classList.add('loaded') + element.classList.add('loaded') } catch (error) { console.error('Gallery data parsing failed:', error) } @@ -456,7 +456,7 @@ document.addEventListener('DOMContentLoaded', () => { // 檢查文檔高度是否小於視窗高度 const checkDocumentHeight = () => { - if (document.body.scrollHeight <= window.innerHeight + 56) { + if (document.body.scrollHeight <= window.innerHeight + 56) { $rightside.classList.add('rightside-show') return true } @@ -472,8 +472,8 @@ document.addEventListener('DOMContentLoaded', () => { let flag = '' const scrollTask = btf.rafThrottle(() => { - if (checkDocumentHeight()) return - + if (checkDocumentHeight()) return + const currentTop = window.scrollY || document.documentElement.scrollTop const isDown = scrollDirection(currentTop) if (currentTop > 56) { @@ -501,14 +501,14 @@ document.addEventListener('DOMContentLoaded', () => { $header.classList.remove('nav-fixed', 'nav-visible') } $rightside.classList.remove('rightside-show') - } - - isShowPercent && rightsideScrollPercent(currentTop) - }) - - checkDocumentHeight() - btf.addEventListenerPjax(window, 'scroll', scrollTask, { passive: true }) - } + } + + isShowPercent && rightsideScrollPercent(currentTop) + }) + + checkDocumentHeight() + btf.addEventListenerPjax(window, 'scroll', scrollTask, { passive: true }) + } /** * toc, anchor @@ -995,8 +995,11 @@ document.addEventListener('DOMContentLoaded', () => { GLOBAL_CONFIG_SITE.pageType === 'home' && scrollDownInIndex() scrollFn() - forPostFn() - GLOBAL_CONFIG_SITE.pageType !== 'shuoshuo' && btf.switchComments(document) + if (GLOBAL_CONFIG_SITE.pageType !== 'shuoshuo') { + forPostFn() + btf.switchComments(document) + } + openMobileMenu() } @@ -1012,4 +1015,6 @@ document.addEventListener('DOMContentLoaded', () => { fn() }) }) + + document.addEventListener('shuoshuo:rendered', forPostFn) }) diff --git a/source/js/utils.js b/source/js/utils.js index e60c86e..37dffde 100644 --- a/source/js/utils.js +++ b/source/js/utils.js @@ -243,9 +243,10 @@ ], right: ['slideshow', 'thumbs', 'close'] } - } + }, + hideScrollbar: false } - } else { + } else { options = { Hash: false, Carousel: { @@ -274,7 +275,8 @@ maxScale: 4 } } - } + }, + hideScrollbar: false } }