From f87eeaa2f594701ee943bee66693ef1889e37b24 Mon Sep 17 00:00:00 2001 From: bisnsh Date: Sat, 4 Oct 2025 16:12:42 +0800 Subject: [PATCH] scripts --- .../scripts/common/default_config.js | 598 ++++++++++++++++++ themes/butterfly/scripts/events/init.js | 78 ++- .../scripts/filters/post_lazyload.js | 17 +- themes/butterfly/scripts/helpers/page.js | 30 +- themes/butterfly/scripts/tag/hide.js | 2 +- themes/butterfly/scripts/tag/score.js | 4 +- 6 files changed, 709 insertions(+), 20 deletions(-) create mode 100644 themes/butterfly/scripts/common/default_config.js diff --git a/themes/butterfly/scripts/common/default_config.js b/themes/butterfly/scripts/common/default_config.js new file mode 100644 index 0000000..d8ae8c2 --- /dev/null +++ b/themes/butterfly/scripts/common/default_config.js @@ -0,0 +1,598 @@ +// Butterfly 主題默認配置 +// Default configuration for Butterfly theme + +module.exports = { + nav: { + logo: null, + display_title: true, + display_post_title: true, + fixed: false + }, + menu: null, + code_blocks: { + theme: 'light', + macStyle: false, + height_limit: false, + word_wrap: false, + copy: true, + language: true, + shrink: false, + fullpage: false + }, + social: null, + favicon: '/img/favicon.png', + avatar: { + img: '/img/butterfly-icon.png', + effect: false + }, + disable_top_img: false, + default_top_img: null, + index_img: null, + archive_img: null, + tag_img: null, + tag_per_img: null, + category_img: null, + category_per_img: null, + footer_img: false, + background: null, + cover: { + index_enable: true, + aside_enable: true, + archives_enable: true, + default_cover: null + }, + error_img: { + flink: '/img/friend_404.gif', + post_page: '/img/404.jpg' + }, + error_404: { + enable: false, + subtitle: 'Page Not Found', + background: '/img/error-page.png' + }, + post_meta: { + page: { + date_type: 'created', + date_format: 'date', + categories: true, + tags: false, + label: true + }, + post: { + position: 'left', + date_type: 'both', + date_format: 'date', + categories: true, + tags: true, + label: true + } + }, + index_site_info_top: null, + index_top_img_height: null, + subtitle: { + enable: false, + effect: true, + typed_option: null, + source: false, + sub: null + }, + index_layout: 3, + index_post_content: { + method: 3, + length: 500 + }, + 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: 365, + position: 'top', + message_prev: 'It has been', + message_next: 'days since the last update, the content of the article may be outdated.' + }, + footer: { + nav: null, + owner: { + enable: true, + since: 2025 + }, + copyright: { + enable: true, + version: true + }, + custom_text: null + }, + 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_newest_comments: { + enable: false, + sort_order: null, + limit: 6, + storage: 10, + avatar: true + }, + 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_post_series: { + enable: true, + series_title: false, + orderBy: 'date', + order: -1 + }, + card_webinfo: { + enable: true, + post_count: true, + last_push_date: true, + sort_order: null, + runtime_date: null + } + }, + rightside_bottom: null, + 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 + }, + rightside_config_animation: true, + anchor: { + auto_update: false, + click_to_scroll: false + }, + photofigcaption: false, + copy: { + enable: true, + copyright: { + enable: false, + limit_count: 150 + } + }, + wordcount: { + enable: false, + post_wordcount: true, + min2read: true, + total_wordcount: true + }, + busuanzi: { + site_uv: true, + site_pv: true, + page_pv: true + }, + math: { + use: null, + per_page: true, + hide_scrollbar: false, + mathjax: { + enableMenu: true, + tags: 'none' + }, + katex: { + copy_tex: false + } + }, + search: { + use: null, + placeholder: null, + algolia_search: { + hitsPerPage: 6 + }, + local_search: { + preload: false, + top_n_per_article: 1, + unescape: false, + pagination: { + enable: false, + hitsPerPage: 8 + }, + CDN: null + }, + docsearch: { + appId: null, + apiKey: null, + indexName: null, + option: null + } + }, + share: { + use: 'sharejs', + sharejs: { + sites: 'facebook,x,wechat,weibo,qq' + }, + addtoany: { + item: 'facebook,x,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', + js: null, + option: null + }, + facebook_comments: { + app_id: null, + user_id: null, + pageSize: 10, + order_by: 'social', + lang: 'en_US' + }, + twikoo: { + envId: null, + region: null, + visitor: false, + option: null + }, + giscus: { + repo: null, + repo_id: null, + category_id: null, + light_theme: 'light', + dark_theme: 'dark', + js: null, + option: null + }, + remark42: { + host: null, + siteId: null, + option: null + }, + artalk: { + server: null, + site: null, + visitor: false, + option: null + }, + chat: { + use: null, + rightside_button: false, + button_hide_show: false + }, + chatra: { + id: null + }, + tidio: { + public_key: null + }, + crisp: { + website_id: null + }, + google_tag_manager: { + tag_id: null, + domain: 'https://www.googletagmanager.com' + }, + baidu_analytics: null, + google_analytics: null, + cloudflare_analytics: null, + microsoft_clarity: null, + umami_analytics: { + enable: false, + serverURL: null, + script_name: 'script.js', + website_id: null, + option: null, + UV_PV: { + site_uv: false, + site_pv: false, + page_pv: false, + token: null + } + }, + google_adsense: { + enable: false, + auto_ads: true, + js: 'https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js', + client: null, + enable_page_level_ads: true + }, + ad: { + index: null, + aside: null, + post: null + }, + site_verification: null, + category_ui: null, + tag_ui: null, + rounded_corners_ui: true, + text_align_justify: false, + mask: { + header: true, + footer: true + }, + preloader: { + enable: false, + source: 1, + pace_css_url: null + }, + enter_transitions: true, + 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 + }, + 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 + }, + lightbox: null, + series: { + enable: false, + orderBy: 'title', + order: 1, + number: true + }, + abcjs: { + enable: false, + per_page: true + }, + mermaid: { + enable: false, + code_write: false, + theme: { + light: 'default', + 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, + 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, + lazyload: { + enable: false, + native: false, + field: 'site', + placeholder: null, + blur: false + }, + pwa: { + enable: false, + manifest: null, + apple_touch_icon: null, + favicon_32_32: null, + favicon_16_16: null, + mask_icon: null + }, + Open_Graph_meta: { + enable: true, + option: null + }, + structured_data: { + enable: false, + alternate_name: null + }, + css_prefix: true, + inject: { + head: null, + bottom: null + }, + CDN: { + internal_provider: 'local', + third_party_provider: 'jsdelivr', + version: true, + custom_format: null, + option: null + } +} diff --git a/themes/butterfly/scripts/events/init.js b/themes/butterfly/scripts/events/init.js index 36304d8..e8dfadf 100644 --- a/themes/butterfly/scripts/events/init.js +++ b/themes/butterfly/scripts/events/init.js @@ -1,14 +1,26 @@ -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') +const { deepMerge } = require('hexo-util') +const path = require('path') - if (hexoVer < 5.3) { +// Cache default config to avoid repeated file reads +let cachedDefaultConfig = null + +/** + * Check Hexo version and configuration + */ +function checkHexoEnvironment (hexo) { + const { version, log, locals } = hexo + + const [major, minor] = version.split('.').map(Number) + const requiredMajor = 5 + const requiredMinor = 3 + + if (major < requiredMajor || (major === requiredMajor && minor < requiredMinor)) { log.error('Please update Hexo to V5.3.0 or higher!') log.error('請把 Hexo 升級到 V5.3.0 或更高的版本!') process.exit(-1) } + // Check for deprecated configuration file if (locals.get) { const data = locals.get('data') if (data && data.butterfly) { @@ -17,4 +29,58 @@ hexo.extend.filter.register('before_generate', () => { process.exit(-1) } } -}) +} + +/** + * Load default configuration + */ +function loadDefaultConfig () { + if (cachedDefaultConfig) { + return cachedDefaultConfig + } + + const configPath = path.join(__dirname, '../common/default_config.js') + cachedDefaultConfig = require(configPath) + return cachedDefaultConfig +} + +/** + * Process comment system configuration + */ +function processCommentConfig (themeConfig) { + const { comments } = themeConfig + if (!comments || !comments.use) { + return + } + + let { use } = comments + + if (!Array.isArray(use)) { + use = typeof use === 'string' ? use.split(',') : [use] + } + + use = use + .map(item => { + if (typeof item !== 'string') return item + return item.trim().toLowerCase().replace(/\b[a-z]/g, s => s.toUpperCase()) + }) + .filter(Boolean) + + // Handle Disqus and Disqusjs conflict + if (use.includes('Disqus') && use.includes('Disqusjs')) { + hexo.log.warn('Disqus and Disqusjs conflict detected, keeping only the first one') + hexo.log.warn('檢測到 Disqus 和 Disqusjs 衝突,只保留第一個') + use = [use[0]] + } + + themeConfig.comments.use = use +} + +hexo.extend.filter.register('before_generate', () => { + checkHexoEnvironment(hexo) + + const defaultConfig = loadDefaultConfig() + hexo.theme.config = deepMerge(defaultConfig, hexo.theme.config) + + processCommentConfig(hexo.theme.config) +}, 1) diff --git a/themes/butterfly/scripts/filters/post_lazyload.js b/themes/butterfly/scripts/filters/post_lazyload.js index 7ad6339..a0dc341 100644 --- a/themes/butterfly/scripts/filters/post_lazyload.js +++ b/themes/butterfly/scripts/filters/post_lazyload.js @@ -1,7 +1,7 @@ /** * Butterfly - * lazyload - * replace src to data-lazy-src + * Lazyload filter + * Replace src with data-lazy-src for lazy loading */ 'use strict' @@ -10,11 +10,20 @@ const urlFor = require('hexo-util').url_for.bind(hexo) const lazyload = htmlContent => { if (hexo.theme.config.lazyload.native) { - return htmlContent.replace(/()/ig, '$1 loading=\'lazy\'$2') + // Use more precise replacement: only replace img tags in HTML, not content inside script tags + return htmlContent.replace(/(]*?\bloading=)(?:\s[^>]*?)?>)(?![^<]*<\/script>)/gi, match => { + return match.replace(/>$/, ' loading=\'lazy\'>') + }) } const bg = hexo.theme.config.lazyload.placeholder ? urlFor(hexo.theme.config.lazyload.placeholder) : 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7' - return htmlContent.replace(/(]*?\bdata-lazy-src=)(?:\s[^>]*?)?\ssrc=)(?:"([^"]*)"|'([^']*)'|([^\s>]+))(?![^<]*<\/script>)/gi, (match, prefix, srcDoubleQuote, srcSingleQuote, srcNoQuote) => { + const src = srcDoubleQuote || srcSingleQuote || srcNoQuote + return `${prefix}"${bg}" data-lazy-src="${src}"` + }) } hexo.extend.filter.register('after_render:html', data => { diff --git a/themes/butterfly/scripts/helpers/page.js b/themes/butterfly/scripts/helpers/page.js index 60819d3..b99b0c4 100644 --- a/themes/butterfly/scripts/helpers/page.js +++ b/themes/butterfly/scripts/helpers/page.js @@ -13,7 +13,7 @@ hexo.extend.helper.register('postDesc', data => { hexo.extend.helper.register('cloudTags', function (options = {}) { const env = this - let { source, minfontsize, maxfontsize, limit, unit = 'px', orderby, order } = options + let { source, minfontsize, maxfontsize, limit, unit = 'px', orderby, order, page = 'tags' } = options if (limit > 0) { source = source.limit(limit) @@ -29,14 +29,19 @@ hexo.extend.helper.register('cloudTags', function (options = {}) { return `rgb(${Math.max(r, 50)}, ${Math.max(g, 50)}, ${Math.max(b, 50)})` } - const generateStyle = (size, unit) => - `font-size: ${parseFloat(size.toFixed(2)) + unit}; color: ${getRandomColor()};` + const generateStyle = (size, unit, page) => { + if (page === 'tags') { + return `font-size: ${parseFloat(size.toFixed(2)) + unit}; background-color: ${getRandomColor()};` + } else { + return `font-size: ${parseFloat(size.toFixed(2)) + unit}; color: ${getRandomColor()};` + } + } const length = sizes.length - 1 const result = source.sort(orderby, order).map(tag => { const ratio = length ? sizes.indexOf(tag.length) / length : 0 const size = minfontsize + ((maxfontsize - minfontsize) * ratio) - const style = generateStyle(size, unit) + const style = generateStyle(size, unit, page) return `${tag.name}` }).join('') @@ -48,7 +53,7 @@ hexo.extend.helper.register('urlNoIndex', function (url = null, trailingIndex = }) hexo.extend.helper.register('md5', function (path) { - return crypto.createHash('md5').update(decodeURI(this.url_for(path))).digest('hex') + return crypto.createHash('md5').update(decodeURI(this.url_for(path, { relative: false }))).digest('hex') }) hexo.extend.helper.register('injectHtml', data => { @@ -65,7 +70,7 @@ hexo.extend.helper.register('findArchivesTitle', function (page, menu, date) { const defaultTitle = this._p('page.archives') if (!menu) return defaultTitle - const loop = (m) => { + const loop = m => { for (const key in m) { if (typeof m[key] === 'object') { const result = loop(m[key]) @@ -81,7 +86,7 @@ hexo.extend.helper.register('findArchivesTitle', function (page, menu, date) { return loop(menu) || defaultTitle }) -hexo.extend.helper.register('getBgPath', function(path) { +hexo.extend.helper.register('getBgPath', function (path) { if (!path) return '' const absoluteUrlPattern = /^(?:[a-z][a-z\d+.-]*:)?\/\//i @@ -127,6 +132,8 @@ hexo.extend.helper.register('shuoshuoFN', (data, page) => { finalResult.forEach(item => { const utcDate = moment.utc(item.date).format('YYYY-MM-DD HH:mm:ss') item.date = moment.tz(utcDate, hexo.config.timezone).format('YYYY-MM-DD HH:mm:ss') + // markdown + item.content = hexo.render.renderSync({ text: item.content, engine: 'markdown' }) }) return finalResult @@ -150,3 +157,12 @@ hexo.extend.helper.register('getVersion', () => { const { version } = require('../../package.json') return { hexo: hexo.version, theme: version } }) + +hexo.extend.helper.register('safeJSON', data => { + // Safely serialize JSON for embedding in