feat: gallery 標籤外掛增加 json 獲取

feat: 本地搜索,點擊文章內容也會跳轉到相應頁面
feat: 添加 docsearch
feat: 標籤頁支持配置 orderby 和 order 參數
feat: card_tags 可配置 orderby 和 order 參數
fix: 修復本地搜索,輸入 ?d 報錯的 bug closed #1192
fix: 修復 waline 在 pjax 模式下 css 沒有加載的 bug
fix: 修復 artalk 最新評論無法加載 closed #1191
fix: 修復 card_author 和 card_announcement 設為 false 後,aside 卡片沒有間距的 bug closed #1174
improvement: getCSS 重構
This commit is contained in:
Jerry
2023-02-12 16:56:18 +08:00
parent 08fabdc2d1
commit c579dad041
27 changed files with 174 additions and 81 deletions

View File

@@ -59,6 +59,15 @@ local_search:
preload: false preload: false
CDN: CDN:
# Docsearch
# https://docsearch.algolia.com/
docsearch:
enable: false
appId:
apiKey:
indexName:
option:
# Math (數學) # Math (數學)
# -------------------------------------- # --------------------------------------
# About the per_page # About the per_page
@@ -686,6 +695,8 @@ aside:
enable: true enable: true
limit: 40 # if set 0 will show all limit: 40 # if set 0 will show all
color: false color: false
orderby: random # Order of tags, random/name/length
order: 1 # Sort of order. 1, asc for ascending; -1, desc for descending
sort_order: # Don't modify the setting unless you know how it works sort_order: # Don't modify the setting unless you know how it works
card_archives: card_archives:
enable: true enable: true
@@ -912,6 +923,8 @@ CDN:
# algolia_js: # algolia_js:
# algolia_search_v4: # algolia_search_v4:
# instantsearch_v4: # instantsearch_v4:
# docsearch_js:
# docsearch_css:
# pjax: # pjax:
# gitalk: # gitalk:
# gitalk_css: # gitalk_css:

View File

@@ -22,14 +22,6 @@ div
if theme.pangu.enable if theme.pangu.enable
!= partial("includes/third-party/pangu.pug", {}, { cache: true }) != partial("includes/third-party/pangu.pug", {}, { cache: true })
//- search
if theme.algolia_search.enable
script(src=url_for(theme.asset.algolia_search_v4))
script(src=url_for(theme.asset.instantsearch_v4))
script(src=url_for(theme.asset.algolia_js))
else if theme.local_search.enable
script(src=url_for(theme.asset.local_search))
.js-pjax .js-pjax
if needLoadCountJs if needLoadCountJs
!= partial("includes/third-party/card-post-count/index", {}, { cache: true }) != partial("includes/third-party/card-post-count/index", {}, { cache: true })

View File

@@ -7,7 +7,7 @@ nav#nav
span.site-name=config.title span.site-name=config.title
#menus #menus
if (theme.algolia_search.enable || theme.local_search.enable) if (theme.algolia_search.enable || theme.local_search.enable || theme.docsearch.enable)
#search-button #search-button
a.site-page.social-icon.search(href="javascript:void(0);") a.site-page.social-icon.search(href="javascript:void(0);")
i.fas.fa-search.fa-fw i.fas.fa-search.fa-fw

View File

@@ -44,5 +44,5 @@ html(lang=config.language data-theme=theme.display_mode class=htmlClassHideAside
include ./404.pug include ./404.pug
include ./rightside.pug include ./rightside.pug
!=partial('includes/third-party/search/index', {}, {cache: true}) include ./additional-js.pug
include ./additional-js.pug !=partial('includes/third-party/search/index', {}, {cache: true})

View File

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

View File

@@ -14,9 +14,13 @@ script.
}, !{JSON.stringify(option)})) }, !{JSON.stringify(option)}))
} }
if (typeof Waline === 'object') initWaline() const walineCSSLoad = document.getElementById('waline-css')
if (typeof Waline === 'object') {
walineCSSLoad ? initWaline() : getCSS('!{url_for(theme.asset.waline_css)}','waline-css').then(initWaline)
}
else { else {
getCSS('!{url_for(theme.asset.waline_css)}').then(() => { getCSS('!{url_for(theme.asset.waline_css)}','waline-css').then(() => {
getScript('!{url_for(theme.asset.waline_js)}').then(initWaline) getScript('!{url_for(theme.asset.waline_js)}').then(initWaline)
}) })
} }

View File

@@ -1,4 +1,4 @@
- const { server, option } = theme.artalk - const { server, site, option } = theme.artalk
- const avatarCdn = option !== null && option.gravatar ? option.gravatar.mirror : 'https://sdn.geekzu.org/avatar/' - const avatarCdn = option !== null && option.gravatar ? option.gravatar.mirror : 'https://sdn.geekzu.org/avatar/'
- const avatarDefault = option !== null && option.gravatar ? option.gravatar.default : 'mp' - const avatarDefault = option !== null && option.gravatar ? option.gravatar.default : 'mp'
@@ -45,8 +45,15 @@ script.
window.pjax && window.pjax.refresh($dom) window.pjax && window.pjax.refresh($dom)
} }
const headerList = {
method: 'POST',
headers: {
'Origin': window.location.origin
}
}
const getComment = () => { const getComment = () => {
fetch('!{server}/api/stat?type=latest_comments&limit=!{theme.newest_comments.limit}',{method: "POST"}) fetch('!{server}/api/stat?type=latest_comments&limit=!{theme.newest_comments.limit}&site_name=!{site}', headerList)
.then(response => response.json()) .then(response => response.json())
.then(d => { .then(d => {
const artalk = d.data.map(function (e) { const artalk = d.data.map(function (e) {

View File

@@ -16,3 +16,7 @@
.algolia-poweredBy .algolia-poweredBy
#search-mask #search-mask
script(src=url_for(theme.asset.algolia_search_v4))
script(src=url_for(theme.asset.instantsearch_v4))
script(src=url_for(theme.asset.algolia_js))

View File

@@ -0,0 +1,27 @@
- const { appId, apiKey, indexName, option } = theme.docsearch
.docsearch-wrap
#docsearch(style="display:none")
link(rel="stylesheet" href=url_for(theme.asset.docsearch_css))
script(src=url_for(theme.asset.docsearch_js))
script.
(() => {
docsearch(Object.assign({
appId: '!{appId}',
apiKey: '!{apiKey}',
indexName: '!{indexName}',
container: '#docsearch',
}, !{JSON.stringify(option)}))
const searchClickFn = () => {
document.querySelector('#search-button > .search').addEventListener('click', () => {
document.querySelector('.DocSearch-Button').click()
})
}
searchClickFn()
window.addEventListener('pjax:complete', searchClickFn)
})()

View File

@@ -1,4 +1,6 @@
if theme.algolia_search.enable if theme.algolia_search.enable
include ./algolia.pug include ./algolia.pug
else if theme.local_search.enable else if theme.local_search.enable
include ./local-search.pug include ./local-search.pug
else if theme.docsearch.enable
include ./docsearch.pug

View File

@@ -17,4 +17,6 @@
hr hr
#local-search-results #local-search-results
#search-mask #search-mask
script(src=url_for(theme.asset.local_search))

View File

@@ -5,8 +5,10 @@ if theme.aside.card_tags.enable
i.fas.fa-tags i.fas.fa-tags
span= _p('aside.card_tags') span= _p('aside.card_tags')
- let tagLimit = theme.aside.card_tags.limit === 0 ? 0 : theme.aside.card_tags.limit || 40 - let { limit, orderby, order } = theme.aside.card_tags
- limit = limit === 0 ? 0 : limit || 40
if theme.aside.card_tags.color if theme.aside.card_tags.color
.card-tag-cloud!= cloudTags({source: site.tags, minfontsize: 1.15, maxfontsize: 1.45, limit: tagLimit, unit: 'em'}) .card-tag-cloud!= cloudTags({source: site.tags, orderby: orderby, order: order, minfontsize: 1.15, maxfontsize: 1.45, limit: tagLimit, unit: 'em'})
else else
.card-tag-cloud!= tagcloud({min_font: 1.1, max_font: 1.5, amount: tagLimit , color: true, start_color: '#999', end_color: '#99a9bf', unit: 'em'}) .card-tag-cloud!= tagcloud({orderby: orderby, order: order, min_font: 1.1, max_font: 1.5, amount: limit , color: true, start_color: '#999', end_color: '#99a9bf', unit: 'em'})

View File

@@ -19,7 +19,7 @@
//- page //- page
!=partial('includes/widget/card_author', {}, {cache: true}) !=partial('includes/widget/card_author', {}, {cache: true})
!=partial('includes/widget/card_announcement', {}, {cache: true}) !=partial('includes/widget/card_announcement', {}, {cache: true})
!=partial('includes/widget/card_top_self', {}, {cache: true}) !=partial('includes/widget/card_top_self', {}, {cache: true})
.sticky_layout .sticky_layout
if showToc if showToc

View File

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

View File

@@ -198,3 +198,13 @@ pace_default_css:
other_name: pace other_name: pace
file: themes/blue/pace-theme-minimal.css file: themes/blue/pace-theme-minimal.css
version: 1.2.4 version: 1.2.4
docsearch_js:
name: '@docsearch/js'
other_name: docsearch-js
file: dist/umd/index.js
version: 3.3.2
docsearch_css:
name: '@docsearch/css'
other_name: docsearch-css
file: dist/style.css
version: 3.3.2

View File

@@ -61,12 +61,18 @@ hexo.extend.helper.register('inject_head_js', function () {
` `
const getCSS = ` const getCSS = `
win.getCSS = url => new Promise((resolve, reject) => { win.getCSS = (url,id = false) => new Promise((resolve, reject) => {
const link = document.createElement('link') const link = document.createElement('link')
link.rel = 'stylesheet' link.rel = 'stylesheet'
link.href = url link.href = url
link.onload = () => resolve() if (id) link.id = id
link.onerror = () => reject() link.onerror = reject
link.onload = link.onreadystatechange = function() {
const loadState = this.readyState
if (loadState && loadState !== 'loaded' && loadState !== 'complete') return
link.onload = link.onreadystatechange = null
resolve()
}
document.head.appendChild(link) document.head.appendChild(link)
}) })
` `

View File

@@ -24,11 +24,8 @@ hexo.extend.helper.register('page_description', function () {
hexo.extend.helper.register('cloudTags', function (options = {}) { hexo.extend.helper.register('cloudTags', function (options = {}) {
const env = this const env = this
let source = options.source let { source, minfontsize, maxfontsize, limit, unit, orderby, order } = options
const minfontsize = options.minfontsize unit = unit || 'px'
const maxfontsize = options.maxfontsize
const limit = options.limit
const unit = options.unit || 'px'
let result = '' let result = ''
if (limit > 0) { if (limit > 0) {
@@ -43,7 +40,7 @@ hexo.extend.helper.register('cloudTags', function (options = {}) {
}) })
const length = sizes.length - 1 const length = sizes.length - 1
source.forEach(tag => { source.sort(orderby, order).forEach(tag => {
const ratio = length ? sizes.indexOf(tag.length) / length : 0 const ratio = length ? sizes.indexOf(tag.length) / length : 0
const size = minfontsize + ((maxfontsize - minfontsize) * ratio) const size = minfontsize + ((maxfontsize - minfontsize) * ratio)
let style = `font-size: ${parseFloat(size.toFixed(2))}${unit};` let style = `font-size: ${parseFloat(size.toFixed(2))}${unit};`

View File

@@ -3,6 +3,7 @@
* galleryGroup and gallery * galleryGroup and gallery
* {% galleryGroup [name] [descr] [url] [img] %} * {% galleryGroup [name] [descr] [url] [img] %}
* {% gallery [lazyload],[rowHeight],[limit] %} * {% gallery [lazyload],[rowHeight],[limit] %}
* {% gallery url,[url],[lazyload],[rowHeight],[limit] %}
*/ */
'use strict' 'use strict'
@@ -12,28 +13,42 @@ const urlFor = require('hexo-util').url_for.bind(hexo)
function gallery (args, content) { function gallery (args, content) {
const { data, languages } = hexo.theme.i18n const { data, languages } = hexo.theme.i18n
args = args.join(' ').split(',') args = args.join(' ').split(',')
const rowHeight = args[1] || 220 let rowHeight, limit, lazyload, type, dataStr
const limit = args[2] || 10
const lazyload = args[0] === 'true' // url,[link],[lazyload],[rowHeight],[limit]
const regex = /!\[(.*?)\]\(([^\s]*)\s*(?:["'](.*?)["']?)?\s*\)/g if (args[0] === 'url') {
const lazyloadClass = lazyload ? 'lazyload' : '' dataStr = args[1]
let m lazyload = args[2] === 'true'
const arr = [] rowHeight = args[3] || 220
while ((m = regex.exec(content)) !== null) { limit = args[4] || 10
if (m.index === regex.lastIndex) { type = ' url'
regex.lastIndex++ } else {
rowHeight = args[1] || 220
limit = args[2] || 10
lazyload = args[0] === 'true'
type = ' data'
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]
})
} }
arr.push({ dataStr = JSON.stringify(arr)
url: m[2],
alt: m[1],
title: m[3]
})
} }
const lazyloadClass = lazyload ? 'lazyload' : ''
return `<div class="gallery"> return `<div class="gallery">
<div class="fj-gallery ${lazyloadClass}" data-rowHeight="${rowHeight}" data-limit="${limit}"> <div class="fj-gallery ${lazyloadClass + type}" data-rowHeight="${rowHeight}" data-limit="${limit}">
<span class="gallery-data">${JSON.stringify(arr)}</span> <span class="gallery-data">${dataStr}</span>
</div> </div>
<button class="gallery-load-more"><span>${data[languages[0]].load_more}</span><i class="fa-solid fa-arrow-down"></i></button> <button class="gallery-load-more"><span>${data[languages[0]].load_more}</span><i class="fa-solid fa-arrow-down"></i></button>
</div>` </div>`

View File

@@ -6,7 +6,7 @@
--hr-before-color: lighten($theme-hr-color, 30%) --hr-before-color: lighten($theme-hr-color, 30%)
--search-bg: $search-bg --search-bg: $search-bg
--search-input-color: $search-input-color --search-input-color: $search-input-color
--search-result-title: $search-result-title --search-a-color: $search-a-color
--preloader-bg: $preloader-bg --preloader-bg: $preloader-bg
--preloader-color: $preloader-word-color --preloader-color: $preloader-word-color
--tab-border-color: $tab-border-color --tab-border-color: $tab-border-color
@@ -50,8 +50,8 @@ body
// scrollbar - chrome/safari // scrollbar - chrome/safari
*::-webkit-scrollbar *::-webkit-scrollbar
width: 8px width: 5px
height: 8px height: 5px
*::-webkit-scrollbar-thumb *::-webkit-scrollbar-thumb
background: var(--scrollbar-color) background: var(--scrollbar-color)

View File

@@ -291,8 +291,8 @@
background: $theme-toc-color background: $theme-toc-color
color: $toc-active-color color: $toc-active-color
:only-child .sticky_layout:only-child
> .card-widget > :first-child
margin-top: 0 margin-top: 0
.card-more-btn .card-more-btn

View File

@@ -6,7 +6,7 @@ if hexo-config('darkmode.enable') || hexo-config('display_mode') == 'dark'
--hr-before-color: alpha(#FFFFFF, .7) --hr-before-color: alpha(#FFFFFF, .7)
--search-bg: #121212 --search-bg: #121212
--search-input-color: alpha(#FFFFFF, .7) --search-input-color: alpha(#FFFFFF, .7)
--search-result-title: alpha(#FFFFFF, .9) --search-a-color: alpha(#FFFFFF, .7)
--preloader-bg: darken(#121212, 2) --preloader-bg: darken(#121212, 2)
--preloader-color: alpha(#FFFFFF, .7) --preloader-color: alpha(#FFFFFF, .7)
--tab-border-color: #2c2c2c --tab-border-color: #2c2c2c

View File

@@ -16,7 +16,7 @@
@extend .list-beauty @extend .list-beauty
a a
color: var(--search-result-title) color: var(--search-a-color)
&:hover &:hover
color: $search-color color: $search-color

View File

@@ -43,13 +43,14 @@
a a
display: block display: block
color: var(--search-result-title) color: var(--search-a-color)
font-weight: 600
cursor: pointer
&:hover &:hover
color: $search-color color: $search-color
.search-result-title
font-weight: 600
.search-result .search-result
margin: 0 0 8px margin: 0 0 8px
word-break: break-word word-break: break-word

View File

@@ -82,7 +82,7 @@ $search-bg = #f6f8fa
$search-input-color = $font-black $search-input-color = $font-black
$search-color = $theme-color $search-color = $theme-color
$search-keyword-highlight = #F47466 $search-keyword-highlight = #F47466
$search-result-title = $font-black $search-a-color = $font-black
// comments // comments
$comments-switch-first-text = $bright-blue $comments-switch-first-text = $bright-blue
$comments-switch-second-text = $light-orange $comments-switch-second-text = $light-orange

View File

@@ -243,8 +243,8 @@ document.addEventListener('DOMContentLoaded', function () {
return str return str
} }
const lazyloadFn = (i, arr) => { const lazyloadFn = (i, arr, limit) => {
const loadItem = i.getAttribute('data-limit') const loadItem = limit
const arrLength = arr.length const arrLength = arr.length
if (arrLength > loadItem) i.insertAdjacentHTML('beforeend', htmlStr(arr.splice(0, loadItem))) if (arrLength > loadItem) i.insertAdjacentHTML('beforeend', htmlStr(arr.splice(0, loadItem)))
else { else {
@@ -254,29 +254,43 @@ document.addEventListener('DOMContentLoaded', function () {
return arrLength > loadItem ? loadItem : arrLength return arrLength > loadItem ? loadItem : arrLength
} }
ele.forEach(item => { const fetchUrl = async (url) => {
const arr = JSON.parse(item.querySelector('.gallery-data').textContent) const response = await fetch(url)
return await response.json()
}
const runJustifiedGallery = (item, arr) => {
if (!item.classList.contains('lazyload')) item.innerHTML = htmlStr(arr) if (!item.classList.contains('lazyload')) item.innerHTML = htmlStr(arr)
else { else {
lazyloadFn(item, arr)
const limit = item.getAttribute('data-limit') const limit = item.getAttribute('data-limit')
lazyloadFn(item, arr, limit)
const clickBtnFn = () => { const clickBtnFn = () => {
const lastItemLength = lazyloadFn(item, arr) const lastItemLength = lazyloadFn(item, arr, limit)
fjGallery(item, 'appendImages', item.querySelectorAll(`.fj-gallery-item:nth-last-child(-n+${lastItemLength})`)) fjGallery(item, 'appendImages', item.querySelectorAll(`.fj-gallery-item:nth-last-child(-n+${lastItemLength})`))
btf.loadLightbox(item.querySelectorAll('img')) btf.loadLightbox(item.querySelectorAll('img'))
lastItemLength < limit && item.nextElementSibling.removeEventListener('click', clickBtnFn) lastItemLength < limit && item.nextElementSibling.removeEventListener('click', clickBtnFn)
} }
item.nextElementSibling.addEventListener('click', clickBtnFn) item.nextElementSibling.addEventListener('click', clickBtnFn)
} }
}) btf.initJustifiedGallery(item)
btf.loadLightbox(item.querySelectorAll('img'))
}
const addJustifiedGallery = () => {
ele.forEach(item => {
item.classList.contains('url')
? fetchUrl(item.textContent).then(res => { runJustifiedGallery(item, res) })
: runJustifiedGallery(item, JSON.parse(item.textContent))
})
}
if (window.fjGallery) { if (window.fjGallery) {
setTimeout(() => { btf.initJustifiedGallery(ele) }, 100) addJustifiedGallery()
return return
} }
getCSS(`${GLOBAL_CONFIG.source.justifiedGallery.css}`) getCSS(`${GLOBAL_CONFIG.source.justifiedGallery.css}`)
getScript(`${GLOBAL_CONFIG.source.justifiedGallery.js}`).then(() => { btf.initJustifiedGallery(ele) }) getScript(`${GLOBAL_CONFIG.source.justifiedGallery.js}`).then(addJustifiedGallery)
} }
/** /**

View File

@@ -153,24 +153,18 @@ window.addEventListener('load', () => {
// highlight all keywords // highlight all keywords
keywords.forEach(keyword => { keywords.forEach(keyword => {
let regexStr = keyword matchContent = matchContent.replaceAll(keyword, '<span class="search-keyword">' + keyword + '</span>')
const specialRegex = /[^\w\s]+/ // match special characters dataTitle = dataTitle.replaceAll(keyword, '<span class="search-keyword">' + keyword + '</span>')
if (keyword.length === 1 && specialRegex.test(keyword)) {
regexStr = `\\${keyword}`
}
const regS = new RegExp(regexStr, 'gi')
matchContent = matchContent.replace(regS, '<span class="search-keyword">' + keyword + '</span>')
dataTitle = dataTitle.replace(regS, '<span class="search-keyword">' + keyword + '</span>')
}) })
str += '<div class="local-search__hit-item"><a href="' + dataUrl + '" class="search-result-title">' + dataTitle + '</a>' str += '<div class="local-search__hit-item"><a href="' + dataUrl + '"><span class="search-result-title">' + dataTitle + '</span>'
count += 1 count += 1
if (dataContent !== '') { if (dataContent !== '') {
str += '<p class="search-result">' + pre + matchContent + post + '</p>' str += '<p class="search-result">' + pre + matchContent + post + '</p>'
} }
} }
str += '</div>' str += '</a></div>'
} }
}) })
if (count === 0) { if (count === 0) {

View File

@@ -252,7 +252,7 @@ const btf = {
}, },
initJustifiedGallery: function (selector) { initJustifiedGallery: function (selector) {
selector.forEach(function (i) { const runJustifiedGallery = i => {
if (!btf.isHidden(i)) { if (!btf.isHidden(i)) {
fjGallery(i, { fjGallery(i, {
itemSelector: '.fj-gallery-item', itemSelector: '.fj-gallery-item',
@@ -263,7 +263,10 @@ const btf = {
} }
}) })
} }
}) }
if (Array.from(selector).length === 0) runJustifiedGallery(selector)
else selector.forEach(i => { runJustifiedGallery(i) })
}, },
updateAnchor: (anchor) => { updateAnchor: (anchor) => {