Compare commits

...
5 Commits
24 changed files with 339 additions and 237 deletions
-1
View File
@@ -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
-1
View File
@@ -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** - 優雅的提示訊息
-8
View File
@@ -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:
-4
View File
@@ -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 })
+1 -1
View File
@@ -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) => {
-23
View File
@@ -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')
})()
+9
View File
@@ -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
+1 -1
View File
@@ -1,4 +1,4 @@
- var pjaxExclude = 'a:not([target="_blank"])'
- var pjaxExclude = 'a:not([target="_blank"]):not([data-fancybox])'
if theme.pjax.exclude
each val in theme.pjax.exclude
- pjaxExclude += `:not([href="${val}"])`
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "hexo-theme-butterfly",
"version": "5.6.0",
"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 <my@crazywong.com>",
+9 -21
View File
@@ -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
+1 -4
View File
@@ -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',
+2 -1
View File
@@ -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'],
+9 -1
View File
@@ -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',
+1 -1
View File
@@ -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
+5 -9
View File
@@ -69,18 +69,14 @@ hexo.extend.helper.register('aside_archives', function (options = {}) {
// Use template literal for better readability
const archiveHeader = `
<div class="item-headline">
<i class="fas fa-archive"></i>
<span>${_p('aside.card_archives')}</span>
${
data.length > limitedData.length
? `<a class="card-more-btn" href="${urlFor(archiveDir)}/"
<div class="item-headline"><i class="fas fa-archive"></i><span>${_p('aside.card_archives')}</span>${
data.length > limitedData.length
? `<a class="card-more-btn" href="${urlFor(archiveDir)}/"
title="${_p('aside.more_button')}">
<i class="fas fa-angle-right"></i>
</a>`
: ''
}
</div>
: ''
}</div>
`
// Use map for generating list items, join for performance
+1 -5
View File
@@ -89,11 +89,7 @@ hexo.extend.helper.register('aside_categories', function (categories, options =
<i class="fas fa-angle-right"></i></a>`
: ''
return `<div class="item-headline">
<i class="fas fa-folder-open"></i>
<span>${this._p('aside.card_categories')}</span>
${moreButton}
</div>
return `<div class="item-headline"><i class="fas fa-folder-open"></i><span>${this._p('aside.card_categories')}</span>${moreButton}</div>
<ul class="card-category-list${isExpand && list.result ? ' expandBtn' : ''}" id="aside-cat-list">
${list.result}
</ul>`
+33 -1
View File
@@ -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(/<hexoPostRenderCodeBlock>([\s\S]*?)<\/hexoPostRenderCodeBlock>/g, (_, c) => {
codeBlocks.push(c)
return `<!--CODEBLOCK_${codeBlocks.length - 1}-->`
})
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(/<!--CODEBLOCK_(\d+)-->/g, (_, i) => codeBlocks[+i])
})
return processedData
+18 -16
View File
@@ -1,22 +1,24 @@
'use strict'
// Read pre-built groups from tag/series.js (hexo._seriesGroups)
// and sort each group according to the sidebar card config.
hexo.extend.helper.register('groupPosts', function () {
const getGroupArray = array => {
return array.reduce((groups, item) => {
const key = item.series
if (key) {
groups[key] = groups[key] || []
groups[key].push(item)
}
return groups
}, {})
}
const groups = hexo._seriesGroups || {}
const { orderBy = 'date', order = 1 } = this.theme.aside.card_post_series
const sortPosts = posts => {
const { orderBy = 'date', order = 1 } = this.theme.aside.card_post_series
if (orderBy === 'title') return posts.sort('title', order)
return posts.sort('date', order)
}
const result = {}
Object.keys(groups).forEach(key => {
const arr = groups[key].slice() // copy to avoid mutating the shared data
if (orderBy === 'title') {
arr.sort((a, b) => {
const cmp = a.title.toUpperCase().localeCompare(b.title.toUpperCase())
return order === 1 ? cmp : -cmp
})
} else {
arr.sort((a, b) => order === 1 ? a.date - b.date : b.date - a.date)
}
result[key] = arr
})
return getGroupArray(sortPosts(this.site.posts))
return result
})
+45 -24
View File
@@ -10,27 +10,42 @@
'use strict'
const urlFor = require('hexo-util').url_for.bind(hexo)
const groups = {}
hexo.extend.filter.register('before_generate', () => {
Object.keys(groups).forEach(k => delete groups[k])
})
// Iterate all posts once and group by series name.
// Result is stored on hexo._seriesGroups, shared by both tag and helper.
function buildSeriesGroups () {
const groups = {}
const posts = hexo.model('Post').toArray()
for (let i = 0, len = posts.length; i < len; i++) {
const p = posts[i]
if (p.series) {
if (!groups[p.series]) groups[p.series] = []
groups[p.series].push({
title: p.title,
path: p.path,
date: p.date.valueOf()
})
}
}
return groups
}
// Build once before the first render (hexo generate / hexo server initial load).
// before_generate runs after tag rendering, so we need this as a fallback.
hexo.extend.filter.register('before_post_render', data => {
if (!hexo.theme.config.series.enable) return data
const { layout, series } = data
if (layout === 'post' && series) {
if (!groups[series]) groups[series] = []
groups[series].push({
title: data.title,
path: data.path,
date: data.date.unix()
})
if (!hexo._seriesGroups) {
hexo._seriesGroups = buildSeriesGroups()
}
return data
})
// Rebuild after each generate cycle in hexo server to reflect latest post data.
hexo.extend.filter.register('before_generate', () => {
if (!hexo.theme.config.series.enable) return
hexo._seriesGroups = buildSeriesGroups()
})
function series (args) {
const { series } = hexo.theme.config
if (!series.enable) {
@@ -38,24 +53,30 @@ function series (args) {
return ''
}
const seriesArr = args.length ? groups[args[0]] : groups[this.series]
const groupName = args.length ? args[0] : this.series
if (!seriesArr) {
hexo.log.warn(`There is no series named "${args[0]}"`)
if (!groupName) {
hexo.log.warn('No series specified')
return ''
}
const isAsc = (series.order || 1) === 1 // 1: asc, -1: desc
const isSortByTitle = series.orderBy === 'title'
const groups = hexo._seriesGroups || {}
const source = groups[groupName]
const compareFn = (a, b) => {
const itemA = isSortByTitle ? a.title.toUpperCase() : a.date
const itemB = isSortByTitle ? b.title.toUpperCase() : b.date
return itemA < itemB ? (isAsc ? -1 : 1) : itemA > itemB ? (isAsc ? 1 : -1) : 0
if (!source || !source.length) {
hexo.log.warn(`There is no series named "${groupName}"`)
return ''
}
seriesArr.sort(compareFn)
const isAsc = (series.order || 1) === 1
const isSortByTitle = series.orderBy === 'title'
// .slice() to copy before sorting, so we don't mutate the shared data
const seriesArr = source.slice().sort((a, b) => {
const itemA = isSortByTitle ? a.title.toUpperCase() : a.date
const itemB = isSortByTitle ? b.title.toUpperCase() : b.date
return itemA < itemB ? (isAsc ? -1 : 1) : itemA > itemB ? (isAsc ? 1 : -1) : 0
})
const listItems = seriesArr.map(ele =>
`<li><a href="${urlFor(ele.path)}" title="${ele.title}">${ele.title}</a></li>`
+4
View File
@@ -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)
+101 -20
View File
@@ -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
-3
View File
@@ -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
+7 -2
View File
@@ -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)
})
+4 -2
View File
@@ -243,7 +243,8 @@
],
right: ['slideshow', 'thumbs', 'close']
}
}
},
hideScrollbar: false
}
} else {
options = {
@@ -274,7 +275,8 @@
maxScale: 4
}
}
}
},
hideScrollbar: false
}
}