Compare commits

...

10 Commits
5.0.0 ... 5.1.0

38 changed files with 459 additions and 254 deletions

View File

@@ -86,7 +86,7 @@ npm i hexo-theme-butterfly
- [x] Share (Sharejs/Addtoany)
- [X] Comment (Disqus/Disqusjs/Livere/Gitalk/Valine/Waline/Utterances/Facebook Comments/Twikoo/Giscus/Remark42/artalk)
- [x] Multiple Comment System Support
- [x] Online Chats (Chatra/Tidio/Daovoice/Crisp)
- [x] Online Chats (Chatra/Tidio/Crisp)
- [x] Web analytics
- [x] Google AdSense
- [x] Webmaster Verification
@@ -98,6 +98,7 @@ npm i hexo-theme-butterfly
- [x] Busuanzi visitor counter
- [x] Medium Zoom/Fancybox
- [x] Mermaid
- [x] Chart.js
- [x] Justified Gallery
- [x] Lazyload images
- [x] Instantpage/Pangu/Snackbar notification toast/PWA......

View File

@@ -86,7 +86,7 @@ theme: butterfly
- [x] 多種分享系統Sharejs/Addtoany
- [X] 多種評論系統Disqus/Disqusjs/Livere/Gitalk/Valine/Waline/Utterances/Facebook Comments/Twikoo/Giscus/Remark42/artalk
- [x] 支持雙評論部署
- [x] 多種在線聊天Chatra/Tidio/Daovoice/Crisp
- [x] 多種在線聊天Chatra/Tidio/Crisp
- [x] 多種分析系統
- [x] 谷歌廣告/手動廣告位置
- [x] 各種站長驗證
@@ -98,6 +98,7 @@ theme: butterfly
- [x] 不蒜子訪問統計
- [x] 兩種大圖模式Medium Zoom/Fancybox
- [x] Mermaid 圖表顯示
- [x] Chart.js 圖表顯示
- [x] 照片牆
- [x] 圖片懶加載
- [x] Instantpage/Pangu/Snackbar彈窗/PWA......

View File

@@ -602,7 +602,7 @@ facebook_comments:
pageSize: 10
# Choose: social / time / reverse_time
order_by: social
lang: zh_TW
lang: en_US
# Twikoo
# https://github.com/imaegoo/twikoo
@@ -645,7 +645,7 @@ artalk:
# --------------------------------------
chat:
# Choose: chatra/tidio/daovoice/crisp
# Choose: chatra/tidio/crisp
# Leave it empty if you don't need chat
use:
# Chat Button [recommend]
@@ -662,10 +662,6 @@ chatra:
tidio:
public_key:
# http://dashboard.daovoice.io/app
daovoice:
app_id:
# https://crisp.chat/en/
crisp:
website_id:
@@ -924,6 +920,25 @@ mermaid:
light: default
dark: dark
# chartjs
# see https://www.chartjs.org/docs/latest/
chartjs:
enable: false
# Do not modify unless you understand how they work.
# The default settings are only used when the MD syntax is not specified.
# General font color for the chart
fontColor:
light: "rgba(0, 0, 0, 0.8)"
dark: "rgba(255, 255, 255, 0.8)"
# General border color for the chart
borderColor:
light: "rgba(0, 0, 0, 0.1)"
dark: "rgba(255, 255, 255, 0.2)"
# Background color for scale labels on radar and polar area charts
scale_ticks_backdropColor:
light: "transparent"
dark: "transparent"
# Note - Bootstrap Callout
note:
# Note tag style values:
@@ -1050,6 +1065,7 @@ CDN:
# canvas_fluttering_ribbon:
# canvas_nest:
# canvas_ribbon:
# chartjs:
# click_heart:
# clickShowText:
# disqusjs:

View File

@@ -60,47 +60,38 @@
if block
block
mixin otherPV()
if theme.umami_analytics.enable && theme.umami_analytics.UV_PV.page_pv
+pvBlock('', '', '')
span#umamiPV(data-path=url_for(page.path))
i.fa-solid.fa-spinner.fa-spin
else if theme.busuanzi.page_pv
+pvBlock('', 'post-meta-pv-cv', '')
span#busuanzi_value_page_pv
i.fa-solid.fa-spinner.fa-spin
- const commentUse = comments.use && comments.use[0]
if page.comments !== false && commentUse && !comments.lazyload
case commentUse
when 'Valine'
if theme.valine.visitor
if commentUse === 'Valine' && theme.valine.visitor
+pvBlock(url_for(page.path), 'leancloud_visitors', page.title)
span.leancloud-visitors-count
i.fa-solid.fa-spinner.fa-spin
when 'Waline'
if theme.waline.pageview
else if commentUse === 'Waline' && theme.waline.pageview
+pvBlock('', '', '')
span.waline-pageview-count(data-path=url_for(page.path))
i.fa-solid.fa-spinner.fa-spin
when 'Twikoo'
if theme.twikoo.visitor
else if commentUse === 'Twikoo' && theme.twikoo.visitor
+pvBlock('', '', '')
span#twikoo_visitors
i.fa-solid.fa-spinner.fa-spin
when 'Artalk'
if theme.artalk.visitor
else if commentUse === 'Artalk' && theme.artalk.visitor
+pvBlock('', '', '')
span#ArtalkPV
i.fa-solid.fa-spinner.fa-spin
default
if theme.umami_analytics.enable && theme.umami_analytics.UV_PV.page_pv
+pvBlock('', '', '')
span#umamiPV(data-path=url_for(page.path))
i.fa-solid.fa-spinner.fa-spin
else if theme.busuanzi.page_pv
+pvBlock('', 'post-meta-pv-cv', '')
span#busuanzi_value_page_pv
i.fa-solid.fa-spinner.fa-spin
else
if theme.umami_analytics.enable && theme.umami_analytics.UV_PV.page_pv
+pvBlock('', '', '')
span#umamiPV(data-path=url_for(page.path))
i.fa-solid.fa-spinner.fa-spin
else if theme.busuanzi.page_pv
+pvBlock('', 'post-meta-pv-cv', '')
span#busuanzi_value_page_pv
i.fa-solid.fa-spinner.fa-spin
+otherPV()
else
+otherPV()
if comments.count && !comments.lazyload && page.comments !== false && comments.use
- var whichCount = comments.use[0]

View File

@@ -105,15 +105,9 @@ mixin indexPostUI()
i.fa-solid.fa-spinner.fa-spin
//- Display the article introduction on homepage
case theme.index_post_content.method
when false
- break
when 1
.content!= article.description
when 2
.content!= article.description || truncate(article.content, theme.index_post_content.length)
default
.content!= truncate(article.content, theme.index_post_content.length)
- const content = postDesc(article)
if content
.content!=content
if theme.ad && theme.ad.index
if (index + 1) % 3 === 0

View File

@@ -21,6 +21,8 @@
let start = 0
const container = document.getElementById('article-container')
const showDateAndTine = date => new Date(date).toLocaleString()
const addData = data => {
const cLength = data.length
const end = start + 10 > cLength ? cLength : start + 10
@@ -34,16 +36,19 @@
</div>
<div class="shuoshuo-info">
<div class="shuoshuo-author">${item.author || '!{config.author}'}</div>
<div class="shuoshuo-date">${btf.diffDate(item.date, true)}</div>
<time class="shuoshuo-date" title="${showDateAndTine(item.date)}">${btf.diffDate(item.date, true)}</time>
</div>
</div>
<div class="shuoshuo-content">
${item.content}
</div>
<div class="shuoshuo-footer">
${item.tags && item.tags.length ? `
<div class="shuoshuo-footer">
<div class="shuoshuo-tags">
${item.tags.map(tag => `<span class="shuoshuo-tag">${tag}</span>`).join('')}
</div>
</div>` : ''}
</div>
</div>
`
@@ -94,10 +99,11 @@
img.no-lightbox(src=i.avatar || url_for(theme.avatar.img))
.shuoshuo-info
.shuoshuo-author=i.author || config.author
.shuoshuo-date=relative_date(i.date)
time.shuoshuo-date(title=full_date(i.date))=date(i.date)
.shuoshuo-content
!=markdown(i.content)
.shuoshuo-footer
if i.tags
.shuoshuo-tags
each tag in i.tags
span.shuoshuo-tag=tag

View File

@@ -1,2 +1,2 @@
.tag-cloud-list.is-center
.tag-cloud-list.text-center
!=cloudTags({source: site.tags, orderby: page.orderby || 'random', order: page.order || 1, minfontsize: 1.2, maxfontsize: 1.5, limit: 0, unit: 'em'})

View File

@@ -7,30 +7,28 @@
}
if is_post()
- let prev = theme.post_pagination === 1 ? page.prev : page.next
- let next = theme.post_pagination === 1 ? page.next : page.prev
nav#pagination.pagination-post
if(prev)
- var hasPageNext = next ? 'pull-left' : 'pull-full'
a.prev-post(class=hasPageNext href=url_for(prev.path) title=prev.title)
if prev.cover_type === 'img'
img.cover(src=url_for(prev.cover) onerror=`onerror=null;src='${url_for(theme.error_img.post_page)}'` alt='cover of previous post')
else
.cover(style=`background: ${prev.cover || 'var(--default-bg-color)'}`)
.pagination-info
.label=_p('pagination.prev')
.prev_info=prev.title
- let paginationOrder = theme.post_pagination === 1 ? { prev: page.prev, next: page.next } : { prev: page.next, next: page.prev }
if(next)
- var hasPagePrev = prev ? 'pull-right' : 'pull-full'
a.next-post(class=hasPagePrev href=url_for(next.path) title=next.title)
if next.cover_type === 'img'
img.cover(src=url_for(next.cover) onerror=`onerror=null;src='${url_for(theme.error_img.post_page)}'` alt='cover of next post')
nav#pagination.pagination-post
each direction, key in paginationOrder
if direction
- 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: ${next.cover || 'var(--default-bg-color)'}`)
.pagination-info
.label=_p('pagination.next')
.next_info=next.title
.cover(style=`background: ${direction.cover || 'var(--default-bg-color)'}`)
.info(class=key === 'prev' ? '' : 'text-right')
.info-1
.info-item-1=_p(`pagination.${key}`)
.info-item-2!=direction.title
if getPostDesc
.info-2
.info-item-1!=getPostDesc
else
nav#pagination
.pagination

View File

@@ -2,9 +2,9 @@ if theme.menu
#sidebar
#menu-mask
#sidebar-menus
.avatar-img.is-center
.avatar-img.text-center
img(src=url_for(theme.avatar.img) onerror=`onerror=null;src='${theme.error_img.flink}'` alt="avatar")
.site-data.is-center
.site-data.text-center
a(href=url_for(config.archive_dir) + '/')
.headline= _p('aside.articles')
.length-num= site.posts.length

View File

@@ -1,12 +1,12 @@
script.
(() => {
const abcjsInit = () => {
const abcjsFn = () => {
const abcjsFn = () => setTimeout(() => {
document.querySelectorAll(".abc-music-sheet").forEach(ele => {
if (ele.children.length > 0) return
ABCJS.renderAbc(ele, ele.innerHTML, {responsive: 'resize'})
})
}
}, 100)
typeof ABCJS === 'object' ? abcjsFn()
: btf.getScript('!{url_for(theme.asset.abcjs_basic_js)}').then(abcjsFn)

View File

@@ -1,40 +0,0 @@
//- https://guide.daocloud.io/daovoice/javascript-api-5869833.html
script.
(() => {
(function(i,s,o,g,r,a,m){i["DaoVoiceObject"]=r;i[r]=i[r]||function(){(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;a.charset="utf-8";m.parentNode.insertBefore(a,m)})(window,document,"script",('https:' == document.location.protocol ? 'https:' : 'http:') + "//widget.daovoice.io/widget/!{theme.daovoice.app_id}.js","daovoice")
const isChatBtn = !{theme.chat.rightside_button}
const isChatHideShow = !{theme.chat.button_hide_show}
daovoice('init', {
app_id: '!{theme.daovoice.app_id}',},{
launcher: {
disableLauncherIcon: isChatBtn
},
});
daovoice('update');
if (isChatBtn) {
window.chatBtnFn = () => {
const isShow = document.getElementById('daodream-messenger').classList.contains('daodream-messenger-active')
isShow ? daovoice('hide') : daovoice('show')
}
} else if (isChatHideShow) {
window.chatBtn = {
hide: () => {
daovoice('update', {},{
launcher: {
disableLauncherIcon: true
}
})
},
show: () => {
daovoice('update', {}, {
launcher: {
disableLauncherIcon: false
}
})
}
}
}
})()

View File

@@ -3,7 +3,5 @@ case theme.chat.use
include ./chatra.pug
when 'tidio'
include ./tidio.pug
when 'daovoice'
include ./daovoice.pug
when 'crisp'
include ./crisp.pug

View File

@@ -0,0 +1,91 @@
- const { fontColor, borderColor, scale_ticks_backdropColor } = theme.chartjs
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}"
Chart.defaults.borderColor = "!{borderColor.dark}"
Chart.defaults.scale.ticks.backdropColor = "!{scale_ticks_backdropColor.dark}"
} else {
Chart.defaults.color = "!{fontColor.light}"
Chart.defaults.borderColor = "!{borderColor.light}"
Chart.defaults.scale.ticks.backdropColor = "!{scale_ticks_backdropColor.light}"
}
}
// 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
if (typeof value === 'object' && value !== null) {
if (value[theme]) {
obj[key] = value[theme] // Apply the value for the current theme
} else {
// Recursively process child objects
applyThemeConfig(value, theme)
}
}
})
}
const runChartJS = () => {
window.loadChartJS = true
Array.from($chartjs).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')
const existingCanvas = document.getElementById(chartID)
// If a canvas already exists, remove it to avoid rendering duplicates
if (existingCanvas) {
existingCanvas.parentNode.remove()
}
const chartDefinition = chartSrc.textContent
const canvas = document.createElement('canvas')
canvas.id = chartID
const div = document.createElement('div')
div.className = 'chartjs-wrap'
if (width) {
div.style.width = width
}
div.appendChild(canvas)
chartSrc.insertAdjacentElement('afterend', div)
const ctx = document.getElementById(chartID).getContext('2d')
const config = JSON.parse(chartDefinition)
const theme = document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark-mode' : 'light-mode'
// Set default styles (initial setup)
applyThemeDefaultsConfig(theme)
// Automatically traverse the config and apply dual-mode color schemes
applyThemeConfig(config, theme)
new Chart(ctx, config)
})
}
const loadChartJS = () => {
window.loadChartJS ? runChartJS() : btf.getScript('!{url_for(theme.asset.chartjs)}').then(runChartJS)
}
// Listen for theme change events
btf.addGlobalFn('themeChange', runChartJS, 'chartjs')
btf.addGlobalFn('encrypt', runChartJS, 'chartjs')
window.pjax ? loadChartJS() : document.addEventListener('DOMContentLoaded', loadChartJS)
})()

View File

@@ -9,3 +9,6 @@ case theme.math.use
if theme.mermaid.enable
include ./mermaid.pug
if theme.chartjs.enable
include ./chartjs.pug

View File

@@ -6,7 +6,7 @@
button.search-close-button
i.fas.fa-times
#loading-database.is-center
#loading-database.text-center
i.fas.fa-spinner.fa-pulse
span= ' ' + _p("search.load_data")

View File

@@ -1,5 +1,5 @@
if theme.aside.card_author.enable
.card-widget.card-info.is-center
.card-widget.card-info.text-center
.avatar-img
img(src=url_for(theme.avatar.img) onerror=`this.onerror=null;this.src='` + url_for(theme.error_img.flink) + `'` alt="avatar")
.author-info-name= config.author

View File

@@ -1,6 +1,6 @@
{
"name": "hexo-theme-butterfly",
"version": "5.0.0",
"version": "5.1.0",
"description": "A Simple and Card UI Design theme for Hexo",
"main": "package.json",
"scripts": {

View File

@@ -8,8 +8,8 @@ activate_power_mode:
version: 1.1.4
algolia_search:
name: algoliasearch
file: dist/algoliasearch-lite.umd.js
version: 5.7.0
file: dist/lite/builds/browser.umd.js
version: 5.9.1
aplayer_css:
name: aplayer
file: dist/APlayer.min.css
@@ -42,6 +42,10 @@ canvas_ribbon:
name: butterfly-extsrc
file: dist/canvas-ribbon.min.js
version: 1.1.4
chartjs:
name: chart.js
file: dist/chart.umd.min.js
version: 4.4.5
clickShowText:
name: butterfly-extsrc
file: dist/click-show-text.min.js
@@ -107,7 +111,7 @@ instantpage:
instantsearch:
name: instantsearch.js
file: dist/instantsearch.production.min.js
version: 4.74.2
version: 4.75.1
katex:
name: katex
file: dist/katex.min.css
@@ -133,7 +137,7 @@ medium_zoom:
mermaid:
name: mermaid
file: dist/mermaid.min.js
version: 11.2.1
version: 11.3.0
meting_js:
name: butterfly-extsrc
file: metingjs/dist/Meting.min.js

View File

@@ -0,0 +1,36 @@
'use strict'
const { stripHTML, truncate } = require('hexo-util')
// Truncates the given content to a specified length, removing HTML tags and replacing newlines with spaces.
const truncateContent = (content, length) => {
return truncate(stripHTML(content), { length, separator: ' ' }).replace(/\n/g, ' ')
}
// Generates a post description based on the provided data and theme configuration.
const postDesc = (data, hexo) => {
const { description, content, postDesc } = data
if (postDesc) return postDesc
const { length, method } = hexo.theme.config.index_post_content
if (method === false) return
let result
switch (method) {
case 1:
result = description
break
case 2:
result = description || truncateContent(content, length)
break
default:
result = truncateContent(content, length)
}
data.postDesc = result
return result
}
module.exports = { truncateContent, postDesc }

View File

@@ -341,7 +341,7 @@ hexo.extend.filter.register('before_generate', () => {
user_id: null,
pageSize: 10,
order_by: 'social',
lang: 'zh_TW'
lang: 'en_US'
},
twikoo: {
envId: null,
@@ -380,9 +380,6 @@ hexo.extend.filter.register('before_generate', () => {
tidio: {
public_key: null
},
daovoice: {
app_id: null
},
crisp: {
website_id: null
},
@@ -512,6 +509,21 @@ hexo.extend.filter.register('before_generate', () => {
dark: 'dark'
}
},
chartjs: {
enable: false,
fontColor: {
light: 'rgba(0, 0, 0, 0.8)',
dark: 'rgba(255, 255, 255, 0.8)'
},
borderColor: {
light: 'rgba(0, 0, 0, 0.1)',
dark: 'rgba(255, 255, 255, 0.2)'
},
scale_ticks_backdropColor: {
light: 'transparent',
dark: 'transparent'
}
},
note: {
style: 'flat',
icons: true,

View File

@@ -1,10 +1,13 @@
'use strict'
const { stripHTML, prettyUrls, truncate } = require('hexo-util')
const { truncateContent, postDesc } = require('../common/postDesc')
const { prettyUrls } = require('hexo-util')
const crypto = require('crypto')
hexo.extend.helper.register('truncate', (content, length) => {
return truncate(stripHTML(content), { length, separator: ' ' }).replace(/\n/g, ' ')
hexo.extend.helper.register('truncate', truncateContent)
hexo.extend.helper.register('postDesc', data => {
return postDesc(data, hexo)
})
hexo.extend.helper.register('cloudTags', function (options = {}) {
@@ -82,7 +85,7 @@ hexo.extend.helper.register('getBgPath', path => {
const absoluteUrlPattern = /^(?:[a-z][a-z\d+.-]*:)?\/\//i
const relativeUrlPattern = /^(\.\/|\.\.\/|\/|[^/]+\/).*$/
const colorPattern = /^(#|rgb|rgba|hsl|hsla|linear-gradient|radial-gradient)/i
const colorPattern = /^(#|rgb|rgba|hsl|hsla)/i
if (colorPattern.test(path)) {
return `background-color: ${path};`

View File

@@ -1,3 +1,4 @@
/* eslint-disable camelcase */
/**
* Butterfly
* Related Posts
@@ -6,12 +7,15 @@
'use strict'
const { postDesc } = require('../common/postDesc')
hexo.extend.helper.register('related_posts', function (currentPost, allPosts) {
let relatedPosts = []
const tagsData = currentPost.tags
tagsData.length && tagsData.forEach(function (tag) {
allPosts.forEach(function (post) {
if (currentPost.path !== post.path && isTagRelated(tag.name, post.tags)) {
const getPostDesc = post.postDesc || postDesc(post, hexo)
const relatedPost = {
title: post.title,
path: post.path,
@@ -19,7 +23,8 @@ hexo.extend.helper.register('related_posts', function (currentPost, allPosts) {
cover_type: post.cover_type,
weight: 1,
updated: post.updated,
created: post.date
created: post.date,
postDesc: getPostDesc
}
const index = findItem(relatedPosts, 'path', post.path)
if (index !== -1) {
@@ -50,20 +55,27 @@ hexo.extend.helper.register('related_posts', function (currentPost, allPosts) {
result += '<div class="relatedPosts-list">'
for (let i = 0; i < Math.min(relatedPosts.length, limitNum); i++) {
const cover = relatedPosts[i].cover || 'var(--default-bg-color)'
const title = this.escape_html(relatedPosts[i].title)
result += `<a href="${this.url_for(relatedPosts[i].path)}" title="${title}">`
if (relatedPosts[i].cover_type === 'img') {
result += `<img class="cover" src="${this.url_for(cover)}" alt="cover">`
let { cover, title, path, cover_type, created, updated, postDesc } = relatedPosts[i]
const { escape_html, url_for, date } = this
cover = cover || 'var(--default-bg-color)'
title = escape_html(title)
const className = postDesc ? 'pagination-related' : 'pagination-related no-desc'
result += `<a class="${className}" href="${url_for(path)}" title="${title}">`
if (cover_type === 'img') {
result += `<img class="cover" src="${url_for(cover)}" alt="cover">`
} else {
result += `<div class="cover" style="background: ${cover}"></div>`
}
if (dateType === 'created') {
result += `<div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> ${this.date(relatedPosts[i].created, hexoConfig.date_format)}</div>`
result += `<div class="info text-center"><div class="info-1"><div class="info-item-1"><i class="far fa-calendar-alt fa-fw"></i> ${date(created, hexoConfig.date_format)}</div>`
} else {
result += `<div class="content is-center"><div class="date"><i class="fas fa-history fa-fw"></i> ${this.date(relatedPosts[i].updated, hexoConfig.date_format)}</div>`
result += `<div class="info text-center"><div class="info-1"><div class="info-item-1"><i class="fas fa-history fa-fw"></i> ${date(updated, hexoConfig.date_format)}</div>`
}
result += `<div class="info-item-2">${title}</div></div>`
if (postDesc) {
result += `<div class="info-2"><div class="info-item-1">${postDesc}</div></div>`
}
result += `<div class="title">${title}</div>`
result += '</div></a>'
}

49
scripts/tag/chartjs.js Normal file
View File

@@ -0,0 +1,49 @@
/**
* Butterfly
* chartjs
* https://www.chartjs.org/
* {% chartjs [width, abreast, chartId] %}
* <!-- chart -->
* <!-- endchart -->
* <!-- desc -->
* <!-- enddesc -->
* {% endchartjs %}
*/
'use strict'
const { escapeHTML } = require('hexo-util')
const chartjs = (args, content) => {
if (!content) return
const chartRegex = /<!--\s*chart\s*-->\n([\w\W\s\S]*?)<!--\s*endchart\s*-->/
const descRegex = /<!--\s*desc\s*-->\n([\w\W\s\S]*?)<!--\s*enddesc\s*-->/
const selfConfig = args.join(' ').trim()
const [width = '', layout = false, chartId = ''] = selfConfig.split(',').map(s => s.trim())
const chartMatch = content.match(chartRegex)
const descMatch = content.match(descRegex)
if (!chartMatch) {
hexo.log.warn('chartjs tag: chart content is required!')
return
}
const chartConfig = chartMatch && chartMatch[1] ? chartMatch[1] : ''
const descContent = descMatch && descMatch[1] ? descMatch[1] : ''
const renderedDesc = descContent ? hexo.render.renderSync({ text: descContent, engine: 'markdown' }).trim() : ''
const descDOM = renderedDesc ? `<div class="chartjs-desc">${renderedDesc}</div>` : ''
const abreastClass = layout ? ' chartjs-abreast' : ''
const widthStyle = width ? `data-width="${width}%"` : ''
return `<div class="chartjs-container${abreastClass}" data-chartjs-id="${chartId}" ${widthStyle}>
<pre class="chartjs-src" hidden>${escapeHTML(chartConfig)}</pre>
${descDOM}
</div>`
}
hexo.extend.tag.register('chartjs', chartjs, { ends: true })

View File

@@ -44,7 +44,7 @@ addBorderRadius(x = 6, hide = false)
.postImgHover
&:hover
.cover
opacity: .7
opacity: .5
transform: scale(1.1)
.cover

View File

@@ -179,14 +179,11 @@ if $site-name-font
#aside-content .author-info-description
font-family: $site-name-font
.is-center
.text-center
text-align: center
.pull-left
float: left
.pull-right
float: right
.text-right
text-align: right
img
&[src=''],

View File

@@ -159,8 +159,8 @@
a
display: flex
flex-direction: row
padding: 2px 8px
margin: 2px 0
padding: 2px 8px
color: var(--font-color)
transition: all .3s
addBorderRadius()
@@ -354,11 +354,12 @@
.headline
@extend .limit-one-line
color: var(--font-color)
font-size: .95em
.length-num
margin-top: -.32em
margin-top: -.45em
color: var(--text-highlight-color)
font-size: 1.4em
font-size: 1.2em
@keyframes more-btn-move
0%,

View File

@@ -146,10 +146,11 @@
position: fixed
top: -60px
z-index: 91
background: rgba(255, 255, 255, .8)
background: rgba(255, 255, 255, .7)
box-shadow: 0 5px 6px -5px alpha($grey, .6)
transition: transform .2s ease-in-out, opacity .2s ease-in-out
will-change: transform
backdrop-filter: blur(7px)
#blog-info
color: var(--font-color)
@@ -430,6 +431,7 @@
background-color: lighten($theme-color, 30%)
content: ''
transition: all .3s ease-in-out
addBorderRadius()
&:hover
&:after

View File

@@ -8,49 +8,28 @@
background: $theme-paginator-color
color: var(--white)
.pagination-info
position: absolute
top: 50%
padding: 20px 40px
width: 100%
transform: translate(0, -50%)
.prev_info,
.next_info
@extend .limit-one-line
color: var(--white)
font-weight: 500
.next-post
.pagination-info
text-align: right
.pull-full
.full-width
width: 100% !important
.prev-post .label,
.next-post .label
color: var(--light-grey)
text-transform: uppercase
font-size: 90%
.prev-post,
.next-post
@extend .postImgHover
position: relative
display: block
overflow: hidden
.pagination-related
width: 50%
height: 150px
+maxWidth768()
width: 100%
.info-1
.info-item-2
-webkit-line-clamp: 1
.info-2
.info-item-1
-webkit-line-clamp: 2
&.pagination-post
overflow: hidden
margin-top: 40px
width: 100%
background: $dark-black
addBorderRadius()
.layout
@@ -75,3 +54,50 @@
& > *:not(.space)
box-shadow: none
.pagination-related
position: relative
display: inline-block
overflow: hidden
background: $dark-black
vertical-align: bottom
@extend .postImgHover
&.next-post
.info
text-align: right
.info
.info-1,
.info-2
@extend .verticalCenter
padding: 20px 40px
color: var(--white)
transition: transform .3s, opacity .3s
.info-1
.info-item-1
color: var(--light-grey)
text-transform: uppercase
font-size: 90%
.info-item-2
@extend .limit-more-line
color: var(--white)
font-weight: 500
.info-2
opacity: 0
transform: translate(0, 0)
.info-item-1
@extend .limit-more-line
&:not(.no-desc):hover
.info-1
opacity: 0
transform: translate(0, -100%)
.info-2
opacity: 1
transform: translate(0, -50%)

View File

@@ -19,9 +19,9 @@ beautify()
&:before
position: absolute
top: calc(50% - 7px)
left: 0
color: $title-prefix-icon-color
content: $title-prefix-icon
left: 0
line-height: 1
transition: all .2s ease-out
@extend .fontawesomeIcon
@@ -119,16 +119,16 @@ beautify()
width: fit-content
a:not(.headerlink)
position relative
z-index 10
position: relative
z-index: 10
a.headerlink
position: absolute
top: 0
right: 0
left 0
bottom: 0
width 100%
left: 0
width: 100%
height: 100%
ol,

View File

@@ -8,15 +8,9 @@
& > .relatedPosts-list
& > a
position: relative
display: inline-block
overflow: hidden
margin: 3px
width: calc(33.333% - 6px)
height: 200px
background: $dark-black
vertical-align: bottom
@extend .postImgHover
addBorderRadius()
+maxWidth768()
@@ -27,15 +21,11 @@
+maxWidth600()
width: calc(100% - 4px)
.content
padding: 0 20px
@extend .verticalCenter
.date
color: var(--light-grey)
font-size: 90%
.title
@extend .limit-more-line
color: var(--white)
.info
.info-1
.info-item-2
-webkit-line-clamp: 2
.info-2
.info-item-1
-webkit-line-clamp: 3

View File

@@ -75,6 +75,32 @@ if hexo-config('mermaid.enable')
pre > code.mermaid
display: none
if hexo-config('chartjs.enable')
.chartjs-container
display: flex
flex-direction: column
justify-content: center
align-items: center
margin: 0 0 20px
text-align: center
gap: 20px
+maxWidth600()
.chartjs-wrap
width: 100% !important
&.chartjs-abreast
flex-direction: row
+maxWidth600()
flex-direction: column
.chartjs-wrap
width: -webkit-fill-available
canvas
display: inline-block !important
.utterances,
.fb-comments iframe
width: 100% !important

View File

@@ -10,6 +10,7 @@
.shuoshuo-item-header
display: flex
align-items: center
cursor: default
.shuoshuo-avatar
overflow: hidden

View File

@@ -59,6 +59,7 @@
height: 24px
text-align: center
line-height: 24px
addBorderRadius()
.ais-Pagination-item--selected
a
@@ -74,11 +75,11 @@
overflow-y: overlay
margin: 0 -20px
padding: 0 22px
max-height: calc(80vh - 240px)
max-height: calc(80vh - 220px)
+maxWidth768()
max-height: none
height: calc(var(--search-height) - 265px)
height: calc(var(--search-height) - 235px)
#algolia-info
div

View File

@@ -19,9 +19,6 @@
height: 100%
border-radius: 0
hr
margin: 20px auto
.search-nav
margin: 0 0 14px
color: $search-color
@@ -40,7 +37,7 @@
color: $search-color
hr
margin: 20px auto
margin: 15px auto
@extend .custom-hr
#search-mask

View File

@@ -19,30 +19,16 @@
display: none
.local-search-hit-item
position: relative
padding-left: 24px
line-height: 1.7
margin-left: 24px
padding-left: 3px
line-height: 1.8
&:hover
&:before
border-color: var(--pseudo-hover)
&:before
$w = .5em
position: absolute
top: .45em
left: 0
width: w = $w
height: h = w
border: 3px solid $search-color
border-radius: w
background: transparent
content: ''
line-height: h
transition: all .2s ease-in-out
&::marker
color: $search-color
font-weight: bold
font-style: italic
a
display: block
color: var(--search-a-color)
&:hover
@@ -53,17 +39,19 @@
.search-result
margin: 0 0 8px
word-break: break-all
font-size: .9em
.search-result-list
overflow-y: overlay
margin: 0 -20px
padding: 0 22px
max-height: calc(80vh - 200px)
max-height: calc(80vh - 180px)
+maxWidth768()
max-height: calc(var(--search-height) - 220px) !important
max-height: calc(var(--search-height) - 190px) !important
.search-keyword
background: transparent
color: $search-keyword-highlight
font-weight: bold
font-weight: 600

View File

@@ -206,7 +206,7 @@ document.addEventListener('DOMContentLoaded', () => {
const altValue = item.title || item.alt
if (!altValue) return
const ele = document.createElement('div')
ele.className = 'img-alt is-center'
ele.className = 'img-alt text-center'
ele.textContent = altValue
item.insertAdjacentElement('afterend', ele)
})

View File

@@ -85,9 +85,10 @@ window.addEventListener('load', () => {
document.querySelector('#algolia-info .algolia-stats')
]
const searchClient = typeof algoliasearch === 'function' ? algoliasearch : window['algoliasearch/lite'].liteClient
const search = instantsearch({
indexName,
searchClient: algoliasearch(appId, apiKey),
searchClient: searchClient(appId, apiKey),
searchFunction (helper) {
disableDiv.forEach(item => {
item.style.display = helper.state.query ? '' : 'none'

View File

@@ -150,16 +150,16 @@ class LocalSearch {
url.searchParams.append('highlight', keywords.join(' '))
if (slicesOfTitle.length !== 0) {
resultItem += `<div class="local-search-hit-item"><a href="${url.href}"><span class="search-result-title">${this.highlightKeyword(title, slicesOfTitle[0])}</span>`
resultItem += `<li class="local-search-hit-item"><a href="${url.href}"><span class="search-result-title">${this.highlightKeyword(title, slicesOfTitle[0])}</span>`
} else {
resultItem += `<div class="local-search-hit-item"><a href="${url.href}"><span class="search-result-title">${title}</span>`
resultItem += `<li class="local-search-hit-item"><a href="${url.href}"><span class="search-result-title">${title}</span>`
}
slicesOfContent.forEach(slice => {
resultItem += `<p class="search-result">${this.highlightKeyword(content, slice)}...</p></a>`
})
resultItem += '</div>'
resultItem += '</li>'
resultItems.push({
item: resultItem,
id: resultItems.length,
@@ -281,7 +281,7 @@ window.addEventListener('load', () => {
const stats = languages.hits_stats.replace(/\$\{hits}/, resultItems.length)
container.innerHTML = `<div class="search-result-list">${resultItems.map(result => result.item).join('')}</div>`
container.innerHTML = `<ol class="search-result-list">${resultItems.map(result => result.item).join('')}</ol>`
statsItem.innerHTML = `<hr><div class="search-result-stats">${stats}</div>`
window.pjax && window.pjax.refresh(container)
}