init...
This commit is contained in:
18
themes/butterfly/scripts/events/404.js
Normal file
18
themes/butterfly/scripts/events/404.js
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Butterfly
|
||||
* 404 error page
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
hexo.extend.generator.register('404', function (locals) {
|
||||
if (!hexo.theme.config.error_404.enable) return
|
||||
return {
|
||||
path: '404.html',
|
||||
layout: ['page'],
|
||||
data: {
|
||||
type: '404',
|
||||
top_img: false
|
||||
}
|
||||
}
|
||||
})
|
||||
97
themes/butterfly/scripts/events/cdn.js
Normal file
97
themes/butterfly/scripts/events/cdn.js
Normal file
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* Butterfly
|
||||
* Merge CDN
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
const { version } = require('../../package.json')
|
||||
const path = require('path')
|
||||
|
||||
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 internalSrc = {
|
||||
main: {
|
||||
name: 'hexo-theme-butterfly',
|
||||
file: 'js/main.js',
|
||||
version
|
||||
},
|
||||
utils: {
|
||||
name: 'hexo-theme-butterfly',
|
||||
file: 'js/utils.js',
|
||||
version
|
||||
},
|
||||
translate: {
|
||||
name: 'hexo-theme-butterfly',
|
||||
file: 'js/tw_cn.js',
|
||||
version
|
||||
},
|
||||
local_search: {
|
||||
name: 'hexo-theme-butterfly',
|
||||
file: 'js/search/local-search.js',
|
||||
version
|
||||
},
|
||||
algolia_js: {
|
||||
name: 'hexo-theme-butterfly',
|
||||
file: 'js/search/algolia.js',
|
||||
version
|
||||
}
|
||||
}
|
||||
|
||||
const minFile = file => {
|
||||
return file.replace(/(?<!\.min)\.(js|css)$/g, ext => '.min' + ext)
|
||||
}
|
||||
|
||||
const createCDNLink = (data, type, cond = '') => {
|
||||
Object.keys(data).forEach(key => {
|
||||
let { name, version, file, other_name } = data[key]
|
||||
const cdnjs_name = other_name || name
|
||||
const cdnjs_file = file.replace(/^[lib|dist]*\/|browser\//g, '')
|
||||
const min_cdnjs_file = minFile(cdnjs_file)
|
||||
if (cond === 'internal') file = `source/${file}`
|
||||
const min_file = minFile(file)
|
||||
const verType = CDN.version ? (type === 'local' ? `?v=${version}` : `@${version}`) : ''
|
||||
|
||||
const value = {
|
||||
version,
|
||||
name,
|
||||
file,
|
||||
cdnjs_file,
|
||||
min_file,
|
||||
min_cdnjs_file,
|
||||
cdnjs_name
|
||||
}
|
||||
|
||||
const cdnSource = {
|
||||
local: cond === 'internal' ? `${cdnjs_file + verType}` : `/pluginsSrc/${name}/${file + verType}`,
|
||||
jsdelivr: `https://cdn.jsdelivr.net/npm/${name}${verType}/${min_file}`,
|
||||
unpkg: `https://unpkg.com/${name}${verType}/${file}`,
|
||||
cdnjs: `https://cdnjs.cloudflare.com/ajax/libs/${cdnjs_name}/${version}/${min_cdnjs_file}`,
|
||||
custom: (CDN.custom_format || '').replace(/\$\{(.+?)\}/g, (match, $1) => value[$1])
|
||||
}
|
||||
|
||||
data[key] = cdnSource[type]
|
||||
})
|
||||
|
||||
if (cond === 'internal') data.main_css = 'css/index.css' + (CDN.version ? `?v=${version}` : '')
|
||||
return data
|
||||
}
|
||||
|
||||
// delete null value
|
||||
const deleteNullValue = obj => {
|
||||
if (!obj) return
|
||||
for (const i in obj) {
|
||||
obj[i] === null && delete obj[i]
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
themeConfig.asset = Object.assign(
|
||||
createCDNLink(internalSrc, CDN.internal_provider, 'internal'),
|
||||
createCDNLink(thirdPartySrc, CDN.third_party_provider),
|
||||
deleteNullValue(CDN.option)
|
||||
)
|
||||
})
|
||||
14
themes/butterfly/scripts/events/comment.js
Normal file
14
themes/butterfly/scripts/events/comment.js
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Capitalize the first letter of comment name
|
||||
*/
|
||||
|
||||
hexo.extend.filter.register('before_generate', () => {
|
||||
const themeConfig = hexo.theme.config
|
||||
let { use } = themeConfig.comments
|
||||
if (!use) return
|
||||
if (typeof use === 'string') {
|
||||
use = use.split(',')
|
||||
}
|
||||
const newArray = use.map(item => item.toLowerCase().replace(/\b[a-z]/g, s => s.toUpperCase()))
|
||||
themeConfig.comments.use = newArray
|
||||
})
|
||||
20
themes/butterfly/scripts/events/init.js
Normal file
20
themes/butterfly/scripts/events/init.js
Normal file
@@ -0,0 +1,20 @@
|
||||
hexo.extend.filter.register('before_generate', () => {
|
||||
// Get first two digits of the Hexo version number
|
||||
const { version, log, locals } = hexo
|
||||
const hexoVer = version.replace(/(^.*\..*)\..*/, '$1')
|
||||
|
||||
if (hexoVer < 5.3) {
|
||||
log.error('Please update Hexo to V5.3.0 or higher!')
|
||||
log.error('請把 Hexo 升級到 V5.3.0 或更高的版本!')
|
||||
process.exit(-1)
|
||||
}
|
||||
|
||||
if (locals.get) {
|
||||
const data = locals.get('data')
|
||||
if (data && data.butterfly) {
|
||||
log.error("'butterfly.yml' is deprecated. Please use '_config.butterfly.yml'")
|
||||
log.error("'butterfly.yml' 已經棄用,請使用 '_config.butterfly.yml'")
|
||||
process.exit(-1)
|
||||
}
|
||||
}
|
||||
})
|
||||
545
themes/butterfly/scripts/events/merge_config.js
Normal file
545
themes/butterfly/scripts/events/merge_config.js
Normal file
@@ -0,0 +1,545 @@
|
||||
hexo.extend.filter.register('before_generate', () => {
|
||||
const defaultConfig = {
|
||||
nav: {
|
||||
logo: null,
|
||||
display_title: true,
|
||||
fixed: false
|
||||
},
|
||||
menu: null,
|
||||
highlight_theme: 'light',
|
||||
highlight_copy: true,
|
||||
highlight_lang: true,
|
||||
highlight_shrink: false,
|
||||
highlight_height_limit: false,
|
||||
code_word_wrap: false,
|
||||
social: null,
|
||||
favicon: '/img/favicon.png',
|
||||
avatar: {
|
||||
img: 'https://i.loli.net/2021/02/24/5O1day2nriDzjSu.png',
|
||||
effect: false
|
||||
},
|
||||
disable_top_img: false,
|
||||
index_img: null,
|
||||
default_top_img: null,
|
||||
archive_img: null,
|
||||
tag_img: null,
|
||||
tag_per_img: null,
|
||||
category_img: null,
|
||||
category_per_img: null,
|
||||
cover: {
|
||||
index_enable: true,
|
||||
aside_enable: true,
|
||||
archives_enable: true,
|
||||
position: 'both',
|
||||
default_cover: null
|
||||
},
|
||||
error_img: {
|
||||
flink: '/img/friend_404.gif',
|
||||
post_page: '/img/404.jpg'
|
||||
},
|
||||
error_404: {
|
||||
enable: false,
|
||||
subtitle: 'Page Not Found',
|
||||
background: 'https://i.loli.net/2020/05/19/aKOcLiyPl2JQdFD.png'
|
||||
},
|
||||
post_meta: {
|
||||
page: {
|
||||
date_type: 'created',
|
||||
date_format: 'date',
|
||||
categories: true,
|
||||
tags: false,
|
||||
label: true
|
||||
},
|
||||
post: {
|
||||
date_type: 'both',
|
||||
date_format: 'date',
|
||||
categories: true,
|
||||
tags: true,
|
||||
label: true
|
||||
}
|
||||
},
|
||||
index_post_content: {
|
||||
method: 3,
|
||||
length: 500
|
||||
},
|
||||
anchor: {
|
||||
auto_update: false,
|
||||
click_to_scroll: false
|
||||
},
|
||||
photofigcaption: false,
|
||||
copy: {
|
||||
enable: true,
|
||||
copyright: {
|
||||
enable: false,
|
||||
limit_count: 50
|
||||
}
|
||||
},
|
||||
toc: {
|
||||
post: true,
|
||||
page: false,
|
||||
number: true,
|
||||
expand: false,
|
||||
style_simple: false,
|
||||
scroll_percent: true
|
||||
},
|
||||
post_copyright: {
|
||||
enable: true,
|
||||
decode: false,
|
||||
author_href: null,
|
||||
license: 'CC BY-NC-SA 4.0',
|
||||
license_url: 'https://creativecommons.org/licenses/by-nc-sa/4.0/'
|
||||
},
|
||||
reward: {
|
||||
enable: false,
|
||||
text: null,
|
||||
QR_code: null
|
||||
},
|
||||
post_edit: {
|
||||
enable: false,
|
||||
url: null
|
||||
},
|
||||
related_post: {
|
||||
enable: true,
|
||||
limit: 6,
|
||||
date_type: 'created'
|
||||
},
|
||||
post_pagination: 1,
|
||||
noticeOutdate: {
|
||||
enable: false,
|
||||
style: 'flat',
|
||||
limit_day: 500,
|
||||
position: 'top',
|
||||
message_prev: 'It has been',
|
||||
message_next: 'days since the last update, the content of the article may be outdated.'
|
||||
},
|
||||
footer: {
|
||||
owner: {
|
||||
enable: true,
|
||||
since: 2020
|
||||
},
|
||||
custom_text: null,
|
||||
copyright: true
|
||||
},
|
||||
aside: {
|
||||
enable: true,
|
||||
hide: false,
|
||||
button: true,
|
||||
mobile: true,
|
||||
position: 'right',
|
||||
display: {
|
||||
archive: true,
|
||||
tag: true,
|
||||
category: true
|
||||
},
|
||||
card_author: {
|
||||
enable: true,
|
||||
description: null,
|
||||
button: {
|
||||
enable: true,
|
||||
icon: 'fab fa-github',
|
||||
text: 'Follow Me',
|
||||
link: 'https://github.com/xxxxxx'
|
||||
}
|
||||
},
|
||||
card_announcement: {
|
||||
enable: true,
|
||||
content: 'This is my Blog'
|
||||
},
|
||||
card_recent_post: {
|
||||
enable: true,
|
||||
limit: 5,
|
||||
sort: 'date',
|
||||
sort_order: null
|
||||
},
|
||||
card_categories: {
|
||||
enable: true,
|
||||
limit: 8,
|
||||
expand: 'none',
|
||||
sort_order: null
|
||||
},
|
||||
card_tags: {
|
||||
enable: true,
|
||||
limit: 40,
|
||||
color: false,
|
||||
orderby: 'random',
|
||||
order: 1,
|
||||
sort_order: null
|
||||
},
|
||||
card_archives: {
|
||||
enable: true,
|
||||
type: 'monthly',
|
||||
format: 'MMMM YYYY',
|
||||
order: -1,
|
||||
limit: 8,
|
||||
sort_order: null
|
||||
},
|
||||
card_webinfo: {
|
||||
enable: true,
|
||||
post_count: true,
|
||||
last_push_date: true,
|
||||
sort_order: null
|
||||
},
|
||||
card_post_series: {
|
||||
enable: true,
|
||||
series_title: false,
|
||||
orderBy: 'date',
|
||||
order: -1
|
||||
}
|
||||
},
|
||||
busuanzi: {
|
||||
site_uv: true,
|
||||
site_pv: true,
|
||||
page_pv: true
|
||||
},
|
||||
runtimeshow: {
|
||||
enable: false,
|
||||
publish_date: null
|
||||
},
|
||||
newest_comments: {
|
||||
enable: false,
|
||||
sort_order: null,
|
||||
limit: 6,
|
||||
storage: 10,
|
||||
avatar: true
|
||||
},
|
||||
translate: {
|
||||
enable: false,
|
||||
default: '繁',
|
||||
defaultEncoding: 2,
|
||||
translateDelay: 0,
|
||||
msgToTraditionalChinese: '繁',
|
||||
msgToSimplifiedChinese: '簡'
|
||||
},
|
||||
readmode: true,
|
||||
darkmode: {
|
||||
enable: true,
|
||||
button: true,
|
||||
autoChangeMode: false,
|
||||
start: null,
|
||||
end: null
|
||||
},
|
||||
rightside_scroll_percent: false,
|
||||
rightside_item_order: {
|
||||
enable: false,
|
||||
hide: null,
|
||||
show: null
|
||||
},
|
||||
mathjax: {
|
||||
enable: false,
|
||||
per_page: false
|
||||
},
|
||||
katex: {
|
||||
enable: false,
|
||||
per_page: false,
|
||||
hide_scrollbar: true
|
||||
},
|
||||
algolia_search: {
|
||||
enable: false,
|
||||
hits: {
|
||||
per_page: 6
|
||||
}
|
||||
},
|
||||
local_search: {
|
||||
enable: false,
|
||||
preload: false,
|
||||
top_n_per_article: 1,
|
||||
unescape: false,
|
||||
CDN: null
|
||||
},
|
||||
docsearch: {
|
||||
enable: false,
|
||||
appId: null,
|
||||
apiKey: null,
|
||||
indexName: null,
|
||||
option: null
|
||||
},
|
||||
sharejs: {
|
||||
enable: true,
|
||||
sites: 'facebook,twitter,wechat,weibo,qq'
|
||||
},
|
||||
addtoany: {
|
||||
enable: false,
|
||||
item: 'facebook,twitter,wechat,sina_weibo,facebook_messenger,email,copy_link'
|
||||
},
|
||||
comments: {
|
||||
use: null,
|
||||
text: true,
|
||||
lazyload: false,
|
||||
count: false,
|
||||
card_post_count: false
|
||||
},
|
||||
disqus: {
|
||||
shortname: null,
|
||||
apikey: null
|
||||
},
|
||||
disqusjs: {
|
||||
shortname: null,
|
||||
apikey: null,
|
||||
option: null
|
||||
},
|
||||
livere: {
|
||||
uid: null
|
||||
},
|
||||
gitalk: {
|
||||
client_id: null,
|
||||
client_secret: null,
|
||||
repo: null,
|
||||
owner: null,
|
||||
admin: null,
|
||||
option: null
|
||||
},
|
||||
valine: {
|
||||
appId: null,
|
||||
appKey: null,
|
||||
avatar: 'monsterid',
|
||||
serverURLs: null,
|
||||
bg: null,
|
||||
visitor: false,
|
||||
option: null
|
||||
},
|
||||
waline: {
|
||||
serverURL: null,
|
||||
bg: null,
|
||||
pageview: false,
|
||||
option: null
|
||||
},
|
||||
utterances: {
|
||||
repo: null,
|
||||
issue_term: 'pathname',
|
||||
light_theme: 'github-light',
|
||||
dark_theme: 'photon-dark'
|
||||
},
|
||||
facebook_comments: {
|
||||
app_id: null,
|
||||
user_id: null,
|
||||
pageSize: 10,
|
||||
order_by: 'social',
|
||||
lang: 'zh_TW'
|
||||
},
|
||||
twikoo: {
|
||||
envId: null,
|
||||
region: null,
|
||||
visitor: false,
|
||||
option: null
|
||||
},
|
||||
giscus: {
|
||||
repo: null,
|
||||
repo_id: null,
|
||||
category_id: null,
|
||||
theme: {
|
||||
light: 'light',
|
||||
dark: 'dark'
|
||||
},
|
||||
option: null
|
||||
},
|
||||
remark42: {
|
||||
host: null,
|
||||
siteId: null,
|
||||
option: null
|
||||
},
|
||||
artalk: {
|
||||
server: null,
|
||||
site: null,
|
||||
visitor: false,
|
||||
option: null
|
||||
},
|
||||
chat_btn: false,
|
||||
chat_hide_show: false,
|
||||
chatra: {
|
||||
enable: false,
|
||||
id: null
|
||||
},
|
||||
tidio: {
|
||||
enable: false,
|
||||
public_key: null
|
||||
},
|
||||
daovoice: {
|
||||
enable: false,
|
||||
app_id: null
|
||||
},
|
||||
crisp: {
|
||||
enable: false,
|
||||
website_id: null
|
||||
},
|
||||
messenger: {
|
||||
enable: false,
|
||||
pageID: null,
|
||||
lang: 'zh_TW'
|
||||
},
|
||||
baidu_analytics: null,
|
||||
google_analytics: null,
|
||||
cloudflare_analytics: null,
|
||||
microsoft_clarity: null,
|
||||
google_adsense: {
|
||||
enable: false,
|
||||
auto_ads: true,
|
||||
js: 'https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js',
|
||||
client: null,
|
||||
enable_page_level_ads: true
|
||||
},
|
||||
site_verification: null,
|
||||
index_site_info_top: null,
|
||||
index_top_img_height: null,
|
||||
category_ui: null,
|
||||
tag_ui: null,
|
||||
text_align_justify: false,
|
||||
background: null,
|
||||
footer_bg: false,
|
||||
mask: {
|
||||
header: true,
|
||||
footer: true
|
||||
},
|
||||
rightside_bottom: null,
|
||||
enter_transitions: true,
|
||||
activate_power_mode: {
|
||||
enable: false,
|
||||
colorful: true,
|
||||
shake: true,
|
||||
mobile: false
|
||||
},
|
||||
canvas_ribbon: {
|
||||
enable: false,
|
||||
size: 150,
|
||||
alpha: 0.6,
|
||||
zIndex: -1,
|
||||
click_to_change: false,
|
||||
mobile: false
|
||||
},
|
||||
canvas_fluttering_ribbon: {
|
||||
enable: false,
|
||||
mobile: false
|
||||
},
|
||||
canvas_nest: {
|
||||
enable: false,
|
||||
color: '0,0,255',
|
||||
opacity: 0.7,
|
||||
zIndex: -1,
|
||||
count: 99,
|
||||
mobile: false
|
||||
},
|
||||
fireworks: {
|
||||
enable: false,
|
||||
zIndex: 9999,
|
||||
mobile: false
|
||||
},
|
||||
click_heart: {
|
||||
enable: false,
|
||||
mobile: false
|
||||
},
|
||||
clickShowText: {
|
||||
enable: false,
|
||||
text: null,
|
||||
fontSize: '15px',
|
||||
random: false,
|
||||
mobile: false
|
||||
},
|
||||
display_mode: 'light',
|
||||
beautify: {
|
||||
enable: false,
|
||||
field: 'post',
|
||||
'title-prefix-icon': null,
|
||||
'title-prefix-icon-color': null
|
||||
},
|
||||
font: {
|
||||
'global-font-size': null,
|
||||
'code-font-size': null,
|
||||
'font-family': null,
|
||||
'code-font-family': null
|
||||
},
|
||||
blog_title_font: {
|
||||
font_link: null,
|
||||
'font-family': null
|
||||
},
|
||||
hr_icon: {
|
||||
enable: true,
|
||||
icon: null,
|
||||
'icon-top': null
|
||||
},
|
||||
subtitle: {
|
||||
enable: false,
|
||||
effect: true,
|
||||
typed_option: null,
|
||||
source: false,
|
||||
sub: null
|
||||
},
|
||||
preloader: {
|
||||
enable: false,
|
||||
source: 1,
|
||||
pace_css_url: null
|
||||
},
|
||||
wordcount: {
|
||||
enable: false,
|
||||
post_wordcount: true,
|
||||
min2read: true,
|
||||
total_wordcount: true
|
||||
},
|
||||
medium_zoom: false,
|
||||
fancybox: true,
|
||||
series: {
|
||||
enable: true,
|
||||
orderBy: 'title',
|
||||
order: 1,
|
||||
number: true
|
||||
},
|
||||
abcjs: {
|
||||
enable: false,
|
||||
per_page: true
|
||||
},
|
||||
mermaid: {
|
||||
enable: false,
|
||||
theme: {
|
||||
light: 'default',
|
||||
dark: 'dark'
|
||||
}
|
||||
},
|
||||
note: {
|
||||
style: 'flat',
|
||||
icons: true,
|
||||
border_radius: 3,
|
||||
light_bg_offset: 0
|
||||
},
|
||||
pjax: {
|
||||
enable: false,
|
||||
exclude: null
|
||||
},
|
||||
aplayerInject: {
|
||||
enable: false,
|
||||
per_page: true
|
||||
},
|
||||
snackbar: {
|
||||
enable: false,
|
||||
position: 'bottom-left',
|
||||
bg_light: '#49b1f5',
|
||||
bg_dark: '#1f1f1f'
|
||||
},
|
||||
instantpage: false,
|
||||
pangu: {
|
||||
enable: false,
|
||||
field: 'site'
|
||||
},
|
||||
lazyload: {
|
||||
enable: false,
|
||||
field: 'site',
|
||||
placeholder: null,
|
||||
blur: false
|
||||
},
|
||||
Open_Graph_meta: {
|
||||
enable: true,
|
||||
option: null
|
||||
},
|
||||
css_prefix: true,
|
||||
inject: {
|
||||
head: null,
|
||||
bottom: null
|
||||
},
|
||||
CDN: {
|
||||
internal_provider: 'local',
|
||||
third_party_provider: 'jsdelivr',
|
||||
version: true,
|
||||
custom_format: null,
|
||||
option: null
|
||||
}
|
||||
}
|
||||
|
||||
hexo.theme.config = Object.assign(defaultConfig, hexo.theme.config)
|
||||
}, 1)
|
||||
23
themes/butterfly/scripts/events/stylus.js
Normal file
23
themes/butterfly/scripts/events/stylus.js
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Stylus renderer
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
hexo.extend.filter.register('stylus:renderer', style => {
|
||||
const { syntax_highlighter: syntaxHighlighter, highlight, prismjs } = hexo.config
|
||||
let { enable: highlightEnable, line_number: highlightLineNumber } = highlight
|
||||
let { enable: prismjsEnable, line_number: prismjsLineNumber } = prismjs
|
||||
|
||||
// for hexo > 7.0
|
||||
if (syntaxHighlighter) {
|
||||
highlightEnable = syntaxHighlighter === 'highlight.js'
|
||||
prismjsEnable = syntaxHighlighter === 'prismjs'
|
||||
}
|
||||
|
||||
style.define('$highlight_enable', highlightEnable)
|
||||
.define('$highlight_line_number', highlightLineNumber)
|
||||
.define('$prismjs_enable', prismjsEnable)
|
||||
.define('$prismjs_line_number', prismjsLineNumber)
|
||||
// .import(`${this.source_dir.replace(/\\/g, '/')}_data/css/*`)
|
||||
})
|
||||
13
themes/butterfly/scripts/events/welcome.js
Normal file
13
themes/butterfly/scripts/events/welcome.js
Normal file
@@ -0,0 +1,13 @@
|
||||
hexo.on('ready', () => {
|
||||
const { version } = require('../../package.json')
|
||||
hexo.log.info(`
|
||||
===================================================================
|
||||
##### # # ##### ##### ###### ##### ###### # # #
|
||||
# # # # # # # # # # # # #
|
||||
##### # # # # ##### # # ##### # #
|
||||
# # # # # # # ##### # # #
|
||||
# # # # # # # # # # # #
|
||||
##### #### # # ###### # # # ###### #
|
||||
${version}
|
||||
===================================================================`)
|
||||
})
|
||||
27
themes/butterfly/scripts/filters/post_lazyload.js
Normal file
27
themes/butterfly/scripts/filters/post_lazyload.js
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Butterfly
|
||||
* lazyload
|
||||
* replace src to data-lazy-src
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
const urlFor = require('hexo-util').url_for.bind(hexo)
|
||||
|
||||
const lazyload = htmlContent => {
|
||||
const bg = hexo.theme.config.lazyload.placeholder ? urlFor(hexo.theme.config.lazyload.placeholder) : 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'
|
||||
return htmlContent.replace(/(<img.*? src=)/ig, `$1 "${bg}" data-lazy-src=`)
|
||||
}
|
||||
|
||||
hexo.extend.filter.register('after_render:html', data => {
|
||||
const { enable, field } = hexo.theme.config.lazyload
|
||||
if (!enable || field !== 'site') return
|
||||
return lazyload(data)
|
||||
})
|
||||
|
||||
hexo.extend.filter.register('after_post_render', data => {
|
||||
const { enable, field } = hexo.theme.config.lazyload
|
||||
if (!enable || field !== 'post') return
|
||||
data.content = lazyload(data.content)
|
||||
return data
|
||||
})
|
||||
40
themes/butterfly/scripts/filters/random_cover.js
Normal file
40
themes/butterfly/scripts/filters/random_cover.js
Normal file
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Butterfly
|
||||
* ramdom cover
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
hexo.extend.filter.register('before_post_render', data => {
|
||||
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}`
|
||||
}
|
||||
|
||||
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)
|
||||
return defaultCover[num]
|
||||
}
|
||||
|
||||
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
|
||||
})
|
||||
113
themes/butterfly/scripts/helpers/aside_archives.js
Normal file
113
themes/butterfly/scripts/helpers/aside_archives.js
Normal file
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* Butterfly
|
||||
* for aside archives
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
hexo.extend.helper.register('aside_archives', function (options = {}) {
|
||||
const { config } = this
|
||||
const archiveDir = config.archive_dir
|
||||
const { timezone } = config
|
||||
const lang = toMomentLocale(this.page.lang || this.page.language || config.language)
|
||||
let { format } = options
|
||||
const type = options.type || 'monthly'
|
||||
const { transform } = options
|
||||
const showCount = Object.prototype.hasOwnProperty.call(options, 'show_count') ? options.show_count : true
|
||||
const order = options.order || -1
|
||||
const compareFunc = type === 'monthly'
|
||||
? (yearA, monthA, yearB, monthB) => yearA === yearB && monthA === monthB
|
||||
: (yearA, monthA, yearB, monthB) => yearA === yearB
|
||||
const limit = options.limit
|
||||
let result = ''
|
||||
|
||||
if (!format) {
|
||||
format = type === 'monthly' ? 'MMMM YYYY' : 'YYYY'
|
||||
}
|
||||
|
||||
const posts = this.site.posts.sort('date', order)
|
||||
if (!posts.length) return result
|
||||
|
||||
const data = []
|
||||
let length = 0
|
||||
|
||||
posts.forEach(post => {
|
||||
// Clone the date object to avoid pollution
|
||||
let date = post.date.clone()
|
||||
|
||||
if (timezone) date = date.tz(timezone)
|
||||
|
||||
const year = date.year()
|
||||
const month = date.month() + 1
|
||||
const lastData = data[length - 1]
|
||||
|
||||
if (!lastData || !compareFunc(lastData.year, lastData.month, year, month)) {
|
||||
if (lang) date = date.locale(lang)
|
||||
const name = date.format(format)
|
||||
length = data.push({
|
||||
name,
|
||||
year,
|
||||
month,
|
||||
count: 1
|
||||
})
|
||||
} else {
|
||||
lastData.count++
|
||||
}
|
||||
})
|
||||
|
||||
const link = item => {
|
||||
let url = `${archiveDir}/${item.year}/`
|
||||
|
||||
if (type === 'monthly') {
|
||||
if (item.month < 10) url += '0'
|
||||
url += `${item.month}/`
|
||||
}
|
||||
|
||||
return this.url_for(url)
|
||||
}
|
||||
|
||||
const len = data.length
|
||||
const Judge = limit === 0 ? len : Math.min(len, limit)
|
||||
|
||||
result += `<div class="item-headline"><i class="fas fa-archive"></i><span>${this._p('aside.card_archives')}</span>`
|
||||
|
||||
if (len > Judge) {
|
||||
result += `<a class="card-more-btn" href="${this.url_for(archiveDir)}/" title="${this._p('aside.more_button')}">
|
||||
<i class="fas fa-angle-right"></i></a>`
|
||||
}
|
||||
|
||||
result += '</div><ul class="card-archive-list">'
|
||||
|
||||
for (let i = 0; i < Judge; i++) {
|
||||
const item = data[i]
|
||||
|
||||
result += '<li class="card-archive-list-item">'
|
||||
|
||||
result += `<a class="card-archive-list-link" href="${link(item)}">`
|
||||
result += '<span class="card-archive-list-date">'
|
||||
result += transform ? transform(item.name) : item.name
|
||||
result += '</span>'
|
||||
|
||||
if (showCount) {
|
||||
result += `<span class="card-archive-list-count">${item.count}</span>`
|
||||
}
|
||||
result += '</a>'
|
||||
result += '</li>'
|
||||
}
|
||||
|
||||
result += '</ul>'
|
||||
return result
|
||||
})
|
||||
|
||||
const toMomentLocale = function (lang) {
|
||||
if (lang === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
// moment.locale('') equals moment.locale('en')
|
||||
// moment.locale(null) equals moment.locale('en')
|
||||
if (!lang || lang === 'en' || lang === 'default') {
|
||||
return 'en'
|
||||
}
|
||||
return lang.toLowerCase().replace('_', '-')
|
||||
}
|
||||
99
themes/butterfly/scripts/helpers/aside_categories.js
Normal file
99
themes/butterfly/scripts/helpers/aside_categories.js
Normal file
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* Butterfly
|
||||
* for aside categories
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
hexo.extend.helper.register('aside_categories', function (categories, options) {
|
||||
if (!options && (!categories || !Object.prototype.hasOwnProperty.call(categories, 'length'))
|
||||
) {
|
||||
options = categories
|
||||
categories = this.site.categories
|
||||
}
|
||||
|
||||
if (!categories || !categories.length) return ''
|
||||
options = options || {}
|
||||
const { config } = this
|
||||
const showCount = Object.prototype.hasOwnProperty.call(options, 'show_count')
|
||||
? options.show_count
|
||||
: true
|
||||
const depth = options.depth ? parseInt(options.depth, 10) : 0
|
||||
const orderby = options.orderby || 'name'
|
||||
const order = options.order || 1
|
||||
const categoryDir = this.url_for(config.category_dir)
|
||||
const limit = options.limit === 0 ? categories.length : options.limit
|
||||
const isExpand = options.expand !== 'none'
|
||||
const expandClass = isExpand && options.expand === true ? 'expand' : ''
|
||||
const buttonLabel = this._p('aside.more_button')
|
||||
const prepareQuery = (parent) => {
|
||||
const query = {}
|
||||
if (parent) { query.parent = parent } else { query.parent = { $exists: false } }
|
||||
return categories.find(query).sort(orderby, order).filter((cat) => cat.length)
|
||||
}
|
||||
let expandBtn = ''
|
||||
|
||||
const hierarchicalList = (t, level, parent, topparent = true) => {
|
||||
let result = ''
|
||||
const isTopParent = topparent
|
||||
if (t > 0) {
|
||||
prepareQuery(parent).forEach((cat, i) => {
|
||||
if (t > 0) {
|
||||
t = t - 1
|
||||
let child
|
||||
if (!depth || level + 1 < depth) {
|
||||
const childList = hierarchicalList(t, level + 1, cat._id, false)
|
||||
child = childList[0]
|
||||
t = childList[1]
|
||||
}
|
||||
|
||||
const parentClass = isExpand && isTopParent && child ? 'parent' : ''
|
||||
|
||||
result += `<li class="card-category-list-item ${parentClass}">`
|
||||
|
||||
result += `<a class="card-category-list-link" href="${this.url_for(cat.path)}">`
|
||||
|
||||
result += `<span class="card-category-list-name">${cat.name}</span>`
|
||||
|
||||
if (showCount) {
|
||||
result += `<span class="card-category-list-count">${cat.length}</span>`
|
||||
}
|
||||
|
||||
if (isExpand && isTopParent && child) {
|
||||
expandBtn = ' expandBtn'
|
||||
result += `<i class="fas fa-caret-left ${expandClass}"></i>`
|
||||
}
|
||||
|
||||
result += '</a>'
|
||||
|
||||
if (child) {
|
||||
result += `<ul class="card-category-list child">${child}</ul>`
|
||||
}
|
||||
|
||||
result += '</li>'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return [result, t]
|
||||
}
|
||||
|
||||
const list = hierarchicalList(limit, 0)
|
||||
|
||||
const moreButton = function () {
|
||||
if (categories.length <= limit) return ''
|
||||
const moreHtml = `<a class="card-more-btn" href="${categoryDir}/" title="${buttonLabel}">
|
||||
<i class="fas fa-angle-right"></i></a>`
|
||||
|
||||
return moreHtml
|
||||
}
|
||||
|
||||
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${expandBtn}" id="aside-cat-list">
|
||||
${list[0]}
|
||||
</ul>`
|
||||
})
|
||||
58
themes/butterfly/scripts/helpers/findArchiveLength.js
Normal file
58
themes/butterfly/scripts/helpers/findArchiveLength.js
Normal file
@@ -0,0 +1,58 @@
|
||||
hexo.extend.helper.register('getArchiveLength', function () {
|
||||
const { archive_generator: archiveGenerator } = hexo.config
|
||||
if (archiveGenerator && archiveGenerator.enable === false) return this.site.posts.length
|
||||
const { yearly, monthly, daily } = archiveGenerator
|
||||
const { year, month, day } = this.page
|
||||
if (yearly === false || !year) return this.site.posts.length
|
||||
|
||||
const posts = this.site.posts.sort('date')
|
||||
|
||||
const compareFunc = (type, y1, m1, d1, y2, m2, d2) => {
|
||||
switch (type) {
|
||||
case 'year':
|
||||
return y1 === y2
|
||||
case 'month':
|
||||
return y1 === y2 && m1 === m2
|
||||
case 'day':
|
||||
return y1 === y2 && m1 === m2 && d1 === d2
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const generateDateObj = (type) => {
|
||||
return posts.reduce((dateObj, post) => {
|
||||
const date = post.date.clone()
|
||||
const year = date.year()
|
||||
const month = date.month() + 1
|
||||
const day = date.date()
|
||||
const lastData = dateObj[dateObj.length - 1]
|
||||
|
||||
if (!lastData || !compareFunc(type, lastData.year, lastData.month, lastData.day, year, month, day)) {
|
||||
const name = type === 'year' ? year : type === 'month' ? `${year}-${month}` : `${year}-${month}-${day}`
|
||||
dateObj.push({
|
||||
name,
|
||||
year,
|
||||
month,
|
||||
day,
|
||||
count: 1
|
||||
})
|
||||
} else {
|
||||
lastData.count++
|
||||
}
|
||||
|
||||
return dateObj
|
||||
}, [])
|
||||
}
|
||||
|
||||
const data = this.fragment_cache('createArchiveObj', () => {
|
||||
const dateObjs = []
|
||||
if (yearly) dateObjs.push(...generateDateObj('year'))
|
||||
if (monthly) dateObjs.push(...generateDateObj('month'))
|
||||
if (daily) dateObjs.push(...generateDateObj('day'))
|
||||
return dateObjs
|
||||
})
|
||||
|
||||
const name = month ? (day ? `${year}-${month}-${day}` : `${year}-${month}`) : year
|
||||
return data.find(item => item.name === name).count
|
||||
})
|
||||
183
themes/butterfly/scripts/helpers/inject_head_js.js
Normal file
183
themes/butterfly/scripts/helpers/inject_head_js.js
Normal file
@@ -0,0 +1,183 @@
|
||||
/**
|
||||
* Butterfly
|
||||
* inject js to head
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
hexo.extend.helper.register('inject_head_js', function () {
|
||||
const { darkmode, aside } = this.theme
|
||||
const start = darkmode.start || 6
|
||||
const end = darkmode.end || 18
|
||||
const { theme_color } = hexo.theme.config
|
||||
const themeColorLight = (theme_color && theme_color.enable && theme_color.meta_theme_color_light) || '#ffffff'
|
||||
const themeColorDark = (theme_color && theme_color.enable && theme_color.meta_theme_color_dark) || '#0d0d0d'
|
||||
|
||||
const createLocalStore = () => {
|
||||
return `
|
||||
win.saveToLocal = {
|
||||
set: (key, value, ttl) => {
|
||||
if (ttl === 0) return
|
||||
const now = Date.now()
|
||||
const expiry = now + ttl * 86400000
|
||||
const item = {
|
||||
value,
|
||||
expiry
|
||||
}
|
||||
localStorage.setItem(key, JSON.stringify(item))
|
||||
},
|
||||
|
||||
get: key => {
|
||||
const itemStr = localStorage.getItem(key)
|
||||
|
||||
if (!itemStr) {
|
||||
return undefined
|
||||
}
|
||||
const item = JSON.parse(itemStr)
|
||||
const now = Date.now()
|
||||
|
||||
if (now > item.expiry) {
|
||||
localStorage.removeItem(key)
|
||||
return undefined
|
||||
}
|
||||
return item.value
|
||||
}
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/questions/16839698/jquery-getscript-alternative-in-native-javascript
|
||||
const createGetScript = () => {
|
||||
return `
|
||||
win.getScript = (url, attr = {}) => new Promise((resolve, reject) => {
|
||||
const script = document.createElement('script')
|
||||
script.src = url
|
||||
script.async = true
|
||||
script.onerror = reject
|
||||
script.onload = script.onreadystatechange = function() {
|
||||
const loadState = this.readyState
|
||||
if (loadState && loadState !== 'loaded' && loadState !== 'complete') return
|
||||
script.onload = script.onreadystatechange = null
|
||||
resolve()
|
||||
}
|
||||
|
||||
Object.keys(attr).forEach(key => {
|
||||
script.setAttribute(key, attr[key])
|
||||
})
|
||||
|
||||
document.head.appendChild(script)
|
||||
})
|
||||
`
|
||||
}
|
||||
|
||||
const createGetCSS = () => {
|
||||
return `
|
||||
win.getCSS = (url, id = false) => new Promise((resolve, reject) => {
|
||||
const link = document.createElement('link')
|
||||
link.rel = 'stylesheet'
|
||||
link.href = url
|
||||
if (id) link.id = id
|
||||
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)
|
||||
})
|
||||
`
|
||||
}
|
||||
|
||||
const createDarkmodeJs = () => {
|
||||
if (!darkmode.enable) return ''
|
||||
|
||||
let darkmodeJs = `
|
||||
win.activateDarkMode = () => {
|
||||
document.documentElement.setAttribute('data-theme', 'dark')
|
||||
if (document.querySelector('meta[name="theme-color"]') !== null) {
|
||||
document.querySelector('meta[name="theme-color"]').setAttribute('content', '${themeColorDark}')
|
||||
}
|
||||
}
|
||||
win.activateLightMode = () => {
|
||||
document.documentElement.setAttribute('data-theme', 'light')
|
||||
if (document.querySelector('meta[name="theme-color"]') !== null) {
|
||||
document.querySelector('meta[name="theme-color"]').setAttribute('content', '${themeColorLight}')
|
||||
}
|
||||
}
|
||||
const t = saveToLocal.get('theme')
|
||||
`
|
||||
|
||||
const autoChangeMode = darkmode.autoChangeMode
|
||||
|
||||
if (autoChangeMode === 1) {
|
||||
darkmodeJs += `
|
||||
const isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
const isLightMode = window.matchMedia('(prefers-color-scheme: light)').matches
|
||||
const isNotSpecified = window.matchMedia('(prefers-color-scheme: no-preference)').matches
|
||||
const hasNoSupport = !isDarkMode && !isLightMode && !isNotSpecified
|
||||
|
||||
if (t === undefined) {
|
||||
if (isLightMode) activateLightMode()
|
||||
else if (isDarkMode) activateDarkMode()
|
||||
else if (isNotSpecified || hasNoSupport) {
|
||||
const now = new Date()
|
||||
const hour = now.getHours()
|
||||
const isNight = hour <= ${start} || hour >= ${end}
|
||||
isNight ? activateDarkMode() : activateLightMode()
|
||||
}
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addListener(e => {
|
||||
if (saveToLocal.get('theme') === undefined) {
|
||||
e.matches ? activateDarkMode() : activateLightMode()
|
||||
}
|
||||
})
|
||||
} else if (t === 'light') activateLightMode()
|
||||
else activateDarkMode()
|
||||
`
|
||||
} else if (autoChangeMode === 2) {
|
||||
darkmodeJs += `
|
||||
const now = new Date()
|
||||
const hour = now.getHours()
|
||||
const isNight = hour <= ${start} || hour >= ${end}
|
||||
if (t === undefined) isNight ? activateDarkMode() : activateLightMode()
|
||||
else if (t === 'light') activateLightMode()
|
||||
else activateDarkMode()
|
||||
`
|
||||
} else {
|
||||
darkmodeJs += `
|
||||
if (t === 'dark') activateDarkMode()
|
||||
else if (t === 'light') activateLightMode()
|
||||
`
|
||||
}
|
||||
|
||||
return darkmodeJs
|
||||
}
|
||||
|
||||
const createAsideStatus = () => {
|
||||
if (!aside.enable || !aside.button) return ''
|
||||
|
||||
return `
|
||||
const asideStatus = saveToLocal.get('aside-status')
|
||||
if (asideStatus !== undefined) {
|
||||
if (asideStatus === 'hide') {
|
||||
document.documentElement.classList.add('hide-aside')
|
||||
} else {
|
||||
document.documentElement.classList.remove('hide-aside')
|
||||
}
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
const createDetectApple = () => {
|
||||
return `
|
||||
const detectApple = () => {
|
||||
if(/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)){
|
||||
document.documentElement.classList.add('apple')
|
||||
}
|
||||
}
|
||||
detectApple()
|
||||
`
|
||||
}
|
||||
|
||||
return `<script>(win=>{${createLocalStore() + createGetScript() + createGetCSS() + createDarkmodeJs() + createAsideStatus() + createDetectApple()}})(window)</script>`
|
||||
})
|
||||
88
themes/butterfly/scripts/helpers/page.js
Normal file
88
themes/butterfly/scripts/helpers/page.js
Normal file
@@ -0,0 +1,88 @@
|
||||
'use strict'
|
||||
|
||||
const { stripHTML, escapeHTML, prettyUrls } = require('hexo-util')
|
||||
const crypto = require('crypto')
|
||||
|
||||
hexo.extend.helper.register('page_description', function () {
|
||||
const { config, page } = this
|
||||
let description = page.description || page.content || page.title || config.description
|
||||
|
||||
if (description) {
|
||||
description = escapeHTML(stripHTML(description).substring(0, 150)
|
||||
.trim()
|
||||
).replace(/\n/g, ' ')
|
||||
return description
|
||||
}
|
||||
})
|
||||
|
||||
hexo.extend.helper.register('cloudTags', function (options = {}) {
|
||||
const env = this
|
||||
let { source, minfontsize, maxfontsize, limit, unit, orderby, order } = options
|
||||
unit = unit || 'px'
|
||||
|
||||
let result = ''
|
||||
if (limit > 0) {
|
||||
source = source.limit(limit)
|
||||
}
|
||||
|
||||
const sizes = []
|
||||
source.sort('length').forEach(tag => {
|
||||
const { length } = tag
|
||||
if (sizes.includes(length)) return
|
||||
sizes.push(length)
|
||||
})
|
||||
|
||||
const length = sizes.length - 1
|
||||
source.sort(orderby, order).forEach(tag => {
|
||||
const ratio = length ? sizes.indexOf(tag.length) / length : 0
|
||||
const size = minfontsize + ((maxfontsize - minfontsize) * ratio)
|
||||
let style = `font-size: ${parseFloat(size.toFixed(2))}${unit};`
|
||||
const color = 'rgb(' + Math.floor(Math.random() * 201) + ', ' + Math.floor(Math.random() * 201) + ', ' + Math.floor(Math.random() * 201) + ')' // 0,0,0 -> 200,200,200
|
||||
style += ` color: ${color}`
|
||||
result += `<a href="${env.url_for(tag.path)}" style="${style}">${tag.name}</a>`
|
||||
})
|
||||
return result
|
||||
})
|
||||
|
||||
hexo.extend.helper.register('urlNoIndex', function (url = null, trailingIndex = false, trailingHtml = false) {
|
||||
return prettyUrls(url || this.url, { trailing_index: trailingIndex, trailing_html: trailingHtml })
|
||||
})
|
||||
|
||||
hexo.extend.helper.register('md5', function (path) {
|
||||
return crypto.createHash('md5').update(decodeURI(this.url_for(path))).digest('hex')
|
||||
})
|
||||
|
||||
hexo.extend.helper.register('injectHtml', function (data) {
|
||||
if (!data) return ''
|
||||
return data.join('')
|
||||
})
|
||||
|
||||
hexo.extend.helper.register('findArchivesTitle', function (page, menu, date) {
|
||||
if (page.year) {
|
||||
const dateStr = page.month ? `${page.year}-${page.month}` : `${page.year}`
|
||||
const dateFormat = page.month ? hexo.theme.config.aside.card_archives.format : 'YYYY'
|
||||
return date(dateStr, dateFormat)
|
||||
}
|
||||
|
||||
const defaultTitle = this._p('page.archives')
|
||||
if (!menu) return defaultTitle
|
||||
|
||||
const loop = (m) => {
|
||||
for (const key in m) {
|
||||
if (typeof m[key] === 'object') {
|
||||
loop(m[key])
|
||||
}
|
||||
|
||||
if (/\/archives\//.test(m[key])) {
|
||||
return key
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return loop(menu) || defaultTitle
|
||||
})
|
||||
|
||||
hexo.extend.helper.register('isImgOrUrl', function (path) {
|
||||
const imgTestReg = /\.(png|jpe?g|gif|svg|webp)(\?.*)?$/i
|
||||
return path.indexOf('//') !== -1 || imgTestReg.test(path)
|
||||
})
|
||||
100
themes/butterfly/scripts/helpers/related_post.js
Normal file
100
themes/butterfly/scripts/helpers/related_post.js
Normal file
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* Butterfly
|
||||
* Related Posts
|
||||
* According the tag
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
hexo.extend.helper.register('related_posts', function (currentPost, allPosts) {
|
||||
let relatedPosts = []
|
||||
currentPost.tags.forEach(function (tag) {
|
||||
allPosts.forEach(function (post) {
|
||||
if (isTagRelated(tag.name, post.tags)) {
|
||||
const relatedPost = {
|
||||
title: post.title,
|
||||
path: post.path,
|
||||
cover: post.cover,
|
||||
cover_type: post.cover_type,
|
||||
weight: 1,
|
||||
updated: post.updated,
|
||||
created: post.date
|
||||
}
|
||||
const index = findItem(relatedPosts, 'path', post.path)
|
||||
if (index !== -1) {
|
||||
relatedPosts[index].weight += 1
|
||||
} else {
|
||||
if (currentPost.path !== post.path) {
|
||||
relatedPosts.push(relatedPost)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
if (relatedPosts.length === 0) {
|
||||
return ''
|
||||
}
|
||||
let result = ''
|
||||
const hexoConfig = hexo.config
|
||||
const config = hexo.theme.config
|
||||
|
||||
const limitNum = config.related_post.limit || 6
|
||||
const dateType = config.related_post.date_type || 'created'
|
||||
const headlineLang = this._p('post.recommend')
|
||||
|
||||
relatedPosts = relatedPosts.sort(compare('weight'))
|
||||
|
||||
if (relatedPosts.length > 0) {
|
||||
result += '<div class="relatedPosts">'
|
||||
result += `<div class="headline"><i class="fas fa-thumbs-up fa-fw"></i><span>${headlineLang}</span></div>`
|
||||
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 += `<div><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">`
|
||||
} 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>`
|
||||
} 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="title">${title}</div>`
|
||||
result += '</div></a></div>'
|
||||
}
|
||||
|
||||
result += '</div></div>'
|
||||
return result
|
||||
}
|
||||
})
|
||||
|
||||
function isTagRelated (tagName, TBDtags) {
|
||||
let result = false
|
||||
TBDtags.forEach(function (tag) {
|
||||
if (tagName === tag.name) {
|
||||
result = true
|
||||
}
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
function findItem (arrayToSearch, attr, val) {
|
||||
for (let i = 0; i < arrayToSearch.length; i++) {
|
||||
if (arrayToSearch[i][attr] === val) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
function compare (attr) {
|
||||
return function (a, b) {
|
||||
const val1 = a[attr]
|
||||
const val2 = b[attr]
|
||||
return val2 - val1
|
||||
}
|
||||
}
|
||||
22
themes/butterfly/scripts/helpers/series.js
Normal file
22
themes/butterfly/scripts/helpers/series.js
Normal file
@@ -0,0 +1,22 @@
|
||||
'use strict'
|
||||
|
||||
hexo.extend.helper.register('groupPosts', function () {
|
||||
const getGroupArray = array => {
|
||||
const groups = {}
|
||||
array.forEach(item => {
|
||||
const Key = item.series
|
||||
if (!Key) return
|
||||
groups[Key] = groups[Key] || []
|
||||
groups[Key].push(item)
|
||||
})
|
||||
return groups
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
return getGroupArray(sortPosts(this.site.posts))
|
||||
})
|
||||
20
themes/butterfly/scripts/tag/button.js
Normal file
20
themes/butterfly/scripts/tag/button.js
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Button
|
||||
* {% btn url text icon option %}
|
||||
* option: color outline center block larger
|
||||
* color : default/blue/pink/red/purple/orange/green
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
const urlFor = require('hexo-util').url_for.bind(hexo)
|
||||
|
||||
const btn = args => {
|
||||
args = args.join(' ').split(',')
|
||||
const [url = '', text = '', icon = '', option = ''] = args.map(arg => arg.trim())
|
||||
|
||||
return `<a class="btn-beautify ${option}" href="${urlFor(url)}"
|
||||
title="${text}">${icon.length ? `<i class="${icon}"></i>` : ''}${text.length ? `<span>${text}</span>` : ''}</a>`
|
||||
}
|
||||
|
||||
hexo.extend.tag.register('btn', btn, { ends: false })
|
||||
39
themes/butterfly/scripts/tag/flink.js
Normal file
39
themes/butterfly/scripts/tag/flink.js
Normal file
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* flink
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
const urlFor = require('hexo-util').url_for.bind(hexo)
|
||||
|
||||
const flinkFn = (args, content) => {
|
||||
content = hexo.render.renderSync({ text: content, engine: 'yaml' })
|
||||
|
||||
let result = ''
|
||||
|
||||
content.forEach(i => {
|
||||
const className = i.class_name ? `<div class="flink-name">${i.class_name}</div>` : ''
|
||||
const classDesc = i.class_desc ? `<div class="flink-desc">${i.class_desc}</div>` : ''
|
||||
|
||||
let listResult = ''
|
||||
|
||||
i.link_list.forEach(j => {
|
||||
listResult += `
|
||||
<div class="flink-list-item">
|
||||
<a href="${j.link}" title="${j.name}" target="_blank">
|
||||
<div class="flink-item-icon">
|
||||
<img class="no-lightbox" src="${j.avatar}" onerror='this.onerror=null;this.src="${urlFor(hexo.theme.config.error_img.flink)}"' alt="${j.name}" />
|
||||
</div>
|
||||
<div class="flink-item-name">${j.name}</div>
|
||||
<div class="flink-item-desc" title="${j.descr}">${j.descr}</div>
|
||||
</a>
|
||||
</div>`
|
||||
})
|
||||
|
||||
result += `${className}${classDesc} <div class="flink-list">${listResult}</div>`
|
||||
})
|
||||
|
||||
return `<div class="flink">${result}</div>`
|
||||
}
|
||||
|
||||
hexo.extend.tag.register('flink', flinkFn, { ends: true })
|
||||
65
themes/butterfly/scripts/tag/gallery.js
Normal file
65
themes/butterfly/scripts/tag/gallery.js
Normal file
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Butterfly
|
||||
* galleryGroup and gallery
|
||||
* {% galleryGroup [name] [descr] [url] [img] %}
|
||||
*
|
||||
* {% gallery [button],%}
|
||||
* {% gallery url,[url],[button]%}
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
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 = ''
|
||||
|
||||
if (args[0] === 'url') {
|
||||
[type, dataStr, button] = args // url,[link],[lazyload]
|
||||
} 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]
|
||||
})
|
||||
}
|
||||
|
||||
dataStr = JSON.stringify(arr)
|
||||
}
|
||||
|
||||
return `<div class="gallery-container" data-type="${type}" data-button="${button}">
|
||||
<div class="gallery-data">${dataStr}</div>
|
||||
<div class="gallery-items">
|
||||
</div>
|
||||
</div>`
|
||||
}
|
||||
|
||||
const galleryGroup = args => {
|
||||
const [name, descr, url, img] = args
|
||||
const imgUrl = urlFor(img)
|
||||
const urlLink = urlFor(url)
|
||||
|
||||
return `<figure class="gallery-group">
|
||||
<img class="gallery-group-img no-lightbox" src='${imgUrl}' alt="Group Image Gallery">
|
||||
<figcaption>
|
||||
<div class="gallery-group-name">${name}</div>
|
||||
<p>${descr}</p>
|
||||
<a href='${urlLink}'></a>
|
||||
</figcaption>
|
||||
</figure>
|
||||
`
|
||||
}
|
||||
|
||||
hexo.extend.tag.register('gallery', gallery, { ends: true })
|
||||
hexo.extend.tag.register('galleryGroup', galleryGroup)
|
||||
65
themes/butterfly/scripts/tag/hide.js
Normal file
65
themes/butterfly/scripts/tag/hide.js
Normal file
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Butterfly
|
||||
* @example
|
||||
* hideInline
|
||||
* {% hideInline content,display,bg,color %}
|
||||
* content不能包含當引號,可用 '
|
||||
* hideBlock
|
||||
* {% hideBlock display,bg,color %}
|
||||
* content
|
||||
* {% endhideBlock %}
|
||||
* hideToggle
|
||||
* {% hideToggle display,bg,color %}
|
||||
* content
|
||||
* {% endhideToggle %}
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
const parseArgs = args => {
|
||||
return args.join(' ').split(',')
|
||||
}
|
||||
|
||||
const generateStyle = (bg, color) => {
|
||||
let style = 'style="'
|
||||
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 `<span class="hide-inline"><button type="button" class="hide-button" ${group}>${display}
|
||||
</button><span class="hide-content">${content}</span></span>`
|
||||
}
|
||||
|
||||
const hideBlock = (args, content) => {
|
||||
const [display = 'Click', bg = false, color = false] = parseArgs(args)
|
||||
const group = generateStyle(bg, color)
|
||||
|
||||
return `<div class="hide-block"><button type="button" class="hide-button" ${group}>${display}
|
||||
</button><div class="hide-content">${hexo.render.renderSync({ text: content, engine: 'markdown' })}</div></div>`
|
||||
}
|
||||
|
||||
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 `<details class="toggle" ${border}><summary class="toggle-button" ${group}>${display}</summary><div class="toggle-content">${hexo.render.renderSync({ text: content, engine: 'markdown' })}</div></details>`
|
||||
}
|
||||
|
||||
hexo.extend.tag.register('hideInline', hideInline)
|
||||
hexo.extend.tag.register('hideBlock', hideBlock, { ends: true })
|
||||
hexo.extend.tag.register('hideToggle', hideToggle, { ends: true })
|
||||
19
themes/butterfly/scripts/tag/inlineImg.js
Normal file
19
themes/butterfly/scripts/tag/inlineImg.js
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* inlineImg 圖片
|
||||
* @param {Array} args 圖片名稱和高度
|
||||
* @param {string} args[0] 圖片名稱
|
||||
* @param {number} args[1] 圖片高度
|
||||
* @returns {string} 圖片標籤
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
const urlFor = require('hexo-util').url_for.bind(hexo)
|
||||
|
||||
const inlineImg = ([img, height = '']) => {
|
||||
const heightStyle = height ? `style="height:${height}"` : ''
|
||||
const src = urlFor(img)
|
||||
return `<img class="inline-img" src="${src}" ${heightStyle} />`
|
||||
}
|
||||
|
||||
hexo.extend.tag.register('inlineImg', inlineImg, { ends: false })
|
||||
14
themes/butterfly/scripts/tag/label.js
Normal file
14
themes/butterfly/scripts/tag/label.js
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Butterfly
|
||||
* label
|
||||
* {% label text color %}
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
const addLabel = args => {
|
||||
const [text, className = 'default'] = args
|
||||
return `<mark class="hl-label ${className}">${text}</mark> `
|
||||
}
|
||||
|
||||
hexo.extend.tag.register('label', addLabel, { ends: false })
|
||||
17
themes/butterfly/scripts/tag/mermaid.js
Normal file
17
themes/butterfly/scripts/tag/mermaid.js
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Butterfly
|
||||
* mermaid
|
||||
* https://github.com/mermaid-js/mermaid
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
const { escapeHTML } = require('hexo-util')
|
||||
|
||||
const mermaid = (args, content) => {
|
||||
return `<div class="mermaid-wrap"><pre class="mermaid-src" hidden>
|
||||
${escapeHTML(content)}
|
||||
</pre></div>`
|
||||
}
|
||||
|
||||
hexo.extend.tag.register('mermaid', mermaid, { ends: true })
|
||||
27
themes/butterfly/scripts/tag/note.js
Normal file
27
themes/butterfly/scripts/tag/note.js
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* note.js
|
||||
* transplant from hexo-theme-next
|
||||
* Modify by Jerry
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
const postNote = (args, content) => {
|
||||
const styleConfig = hexo.theme.config.note.style
|
||||
const noteTag = ['flat', 'modern', 'simple', 'disabled']
|
||||
if (!noteTag.includes(args[args.length - 1])) {
|
||||
args.push(styleConfig)
|
||||
}
|
||||
|
||||
let icon = ''
|
||||
const iconArray = args[args.length - 2]
|
||||
if (iconArray && iconArray.startsWith('fa')) {
|
||||
icon = `<i class="note-icon ${iconArray}"></i>`
|
||||
args[args.length - 2] = 'icon-padding'
|
||||
}
|
||||
|
||||
return `<div class="note ${args.join(' ')}">${icon + hexo.render.renderSync({ text: content, engine: 'markdown' })}</div>`
|
||||
}
|
||||
|
||||
hexo.extend.tag.register('note', postNote, { ends: true })
|
||||
hexo.extend.tag.register('subnote', postNote, { ends: true })
|
||||
22
themes/butterfly/scripts/tag/score.js
Normal file
22
themes/butterfly/scripts/tag/score.js
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Music Score
|
||||
* {% score %}
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
const score = (args, content) => {
|
||||
const escapeHtmlTags = s => {
|
||||
const lookup = {
|
||||
'&': '&',
|
||||
'"': '"',
|
||||
'\'': ''',
|
||||
'<': '<',
|
||||
'>': '>'
|
||||
}
|
||||
return s.replace(/[&"'<>]/g, c => lookup[c])
|
||||
}
|
||||
return `<div class="abc-music-sheet">${escapeHtmlTags(content)}</div>`
|
||||
}
|
||||
|
||||
hexo.extend.tag.register('score', score, { ends: true })
|
||||
69
themes/butterfly/scripts/tag/series.js
Normal file
69
themes/butterfly/scripts/tag/series.js
Normal file
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* series plugin
|
||||
* Syntax:
|
||||
* {% series [series name] %}
|
||||
* Usage:
|
||||
* {% series %}
|
||||
* {% series series1 %}
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
const urlFor = require('hexo-util').url_for.bind(hexo)
|
||||
const groups = {}
|
||||
|
||||
hexo.extend.filter.register('before_post_render', data => {
|
||||
if (!hexo.theme.config.series.enable) return data
|
||||
const { layout, series } = data
|
||||
if (layout === 'post' && series) {
|
||||
groups[series] = groups[series] || []
|
||||
groups[series].push({
|
||||
title: data.title,
|
||||
path: data.path,
|
||||
date: data.date.unix()
|
||||
})
|
||||
}
|
||||
return data
|
||||
})
|
||||
|
||||
function series (args) {
|
||||
const { series } = hexo.theme.config
|
||||
if (!series.enable) {
|
||||
hexo.log.warn('Series plugin is disabled in the theme config')
|
||||
return ''
|
||||
}
|
||||
|
||||
const seriesArr = args.length ? groups[args[0]] : groups[this.series]
|
||||
|
||||
if (!seriesArr) {
|
||||
hexo.log.warn(`There is no series named "${args[0]}"`)
|
||||
return ''
|
||||
}
|
||||
|
||||
const isAsc = (series.order || 1) === 1 // 1: asc, -1: desc
|
||||
const isSortByTitle = series.orderBy === 'title'
|
||||
|
||||
const compareFn = (a, b) => {
|
||||
const itemA = isSortByTitle ? a.title.toUpperCase() : a.date
|
||||
const itemB = isSortByTitle ? b.title.toUpperCase() : b.date
|
||||
|
||||
if (itemA < itemB) {
|
||||
return isAsc ? -1 : 1
|
||||
}
|
||||
if (itemA > itemB) {
|
||||
return isAsc ? 1 : -1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
seriesArr.sort(compareFn)
|
||||
|
||||
let result = ''
|
||||
seriesArr.forEach(ele => {
|
||||
result += `<li><a href="${urlFor(ele.path)}" title="${ele.title}">${ele.title}</a></li>`
|
||||
})
|
||||
|
||||
return series.number ? `<ol>${result}</ol>` : `<ul>${result}</ul>`
|
||||
}
|
||||
|
||||
hexo.extend.tag.register('series', series, { ends: false })
|
||||
64
themes/butterfly/scripts/tag/tabs.js
Normal file
64
themes/butterfly/scripts/tag/tabs.js
Normal file
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Tabs
|
||||
* transplant from hexo-theme-next
|
||||
* modify by Jerry
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
const postTabs = (args, content) => {
|
||||
const tabBlock = /<!--\s*tab (.*?)\s*-->\n([\w\W\s\S]*?)<!--\s*endtab\s*-->/g
|
||||
args = args.join(' ').split(',')
|
||||
const tabName = args[0]
|
||||
const tabActive = Number(args[1]) || 0
|
||||
const matches = []
|
||||
let match
|
||||
let tabId = 0
|
||||
let tabNav = ''
|
||||
let tabContent = ''
|
||||
let noDefault = true
|
||||
|
||||
!tabName && hexo.log.warn('Tabs block must have unique name!')
|
||||
|
||||
while ((match = tabBlock.exec(content)) !== null) {
|
||||
matches.push(match[1], match[2])
|
||||
}
|
||||
|
||||
for (let i = 0; i < matches.length; i += 2) {
|
||||
const tabParameters = matches[i].split('@')
|
||||
let postContent = matches[i + 1]
|
||||
let tabCaption = tabParameters[0] || ''
|
||||
let tabIcon = tabParameters[1] || ''
|
||||
let tabHref = ''
|
||||
|
||||
postContent = hexo.render.renderSync({ text: postContent, engine: 'markdown' }).trim()
|
||||
|
||||
tabId += 1
|
||||
tabHref = (tabName + ' ' + tabId).toLowerCase().split(' ').join('-');
|
||||
|
||||
((tabCaption.length === 0) && (tabIcon.length === 0)) && (tabCaption = tabName + ' ' + tabId)
|
||||
|
||||
const isOnlyicon = tabIcon.length > 0 && tabCaption.length === 0 ? ' style="text-align: center;"' : ''
|
||||
const icon = tabIcon.trim()
|
||||
tabIcon.length > 0 && (tabIcon = `<i class="${icon}"${isOnlyicon}></i>`)
|
||||
|
||||
let isActive = ''
|
||||
if ((tabActive > 0 && tabActive === tabId) || (tabActive === 0 && tabId === 1)) {
|
||||
isActive = ' active'
|
||||
noDefault = false
|
||||
}
|
||||
tabNav += `<button type="button" class="tab ${isActive}" data-href="${tabHref}">${tabIcon + tabCaption.trim()}</button>`
|
||||
tabContent += `<div class="tab-item-content${isActive}" id="${tabHref}">${postContent}</div>`
|
||||
}
|
||||
|
||||
const toTop = '<div class="tab-to-top"><button type="button" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div>'
|
||||
|
||||
tabNav = `<ul class="nav-tabs${noDefault ? ' no-default' : ''}">${tabNav}</ul>`
|
||||
tabContent = `<div class="tab-contents">${tabContent}</div>`
|
||||
|
||||
return `<div class="tabs" id="${tabName.toLowerCase().split(' ').join('-')}">${tabNav + tabContent + toTop}</div>`
|
||||
}
|
||||
|
||||
hexo.extend.tag.register('tabs', postTabs, { ends: true })
|
||||
hexo.extend.tag.register('subtabs', postTabs, { ends: true })
|
||||
hexo.extend.tag.register('subsubtabs', postTabs, { ends: true })
|
||||
41
themes/butterfly/scripts/tag/timeline.js
Normal file
41
themes/butterfly/scripts/tag/timeline.js
Normal file
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* timeline
|
||||
* by Jerry
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
const timeLineFn = (args, content) => {
|
||||
const tlBlock = /<!--\s*timeline (.*?)\s*-->\n([\w\W\s\S]*?)<!--\s*endtimeline\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 += `<div class='timeline-item headline'><div class='timeline-item-title'><div class='item-circle'>${mdContent}</div></div></div>`
|
||||
}
|
||||
|
||||
const matches = []
|
||||
let match
|
||||
|
||||
while ((match = tlBlock.exec(content)) !== null) {
|
||||
matches.push(match[1])
|
||||
matches.push(match[2])
|
||||
}
|
||||
|
||||
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' })
|
||||
|
||||
const tlTitleHtml = `<div class='timeline-item-title'><div class='item-circle'>${tlChildTitle}</div></div>`
|
||||
const tlContentHtml = `<div class='timeline-item-content'>${tlChildContent}</div>`
|
||||
|
||||
result += `<div class='timeline-item'>${tlTitleHtml + tlContentHtml}</div>`
|
||||
}
|
||||
|
||||
return `<div class="timeline ${color}">${result}</div>`
|
||||
}
|
||||
|
||||
hexo.extend.tag.register('timeline', timeLineFn, { ends: true })
|
||||
Reference in New Issue
Block a user