Compare commits

...

92 Commits
5.2.1 ... 5.5.2

Author SHA1 Message Date
Jerry
8fedddbb5a Merge branch 'dev' 2025-11-07 22:22:34 +08:00
Jerry
439014bbb6 chore: 升級版本至 5.5.2 並更新相依套件
- perf(highlight): 改善複製提示 UI,新增浮動動畫效果
- fix(shuoshuo): 為說說內容中的圖片新增延遲載入支援
- fix(mermaid): 支援在標籤助手中使用自訂配置選項
- fix: 使用 url_for() 處理主題資源路徑 (busuanzi, artalk)
- refactor(aside_archives): 重新命名 url_for 變數以保持一致性
- refactor(inject_head_js): 使用解構賦值命名方式
- chore(deps): 更新 hexo-util 至 4.0.0
- chore(plugins): 更新多個 CDN 套件版本
- style(highlight): 優化程式碼區塊工具列佈局與溢位處理
- fix(zh-CN): 修正分頁文字使用正確的簡體字「页」
2025-11-07 22:19:16 +08:00
Jerry
d369de91b8 Merge branch 'dev' 2025-10-02 14:36:13 +08:00
Jerry
2d4765202d Merge branch 'dev' of https://github.com/jerryc127/hexo-theme-butterfly into dev 2025-10-02 14:32:19 +08:00
Jerry
5e766ac40a chore: bump version to 5.5.1 and update dependencies
- Upgrade version from 5.5.0 to 5.5.1
- Update third-party dependencies: algolia, disqusjs, docsearch, fancybox, fontawesome, mermaid
- Replace Twitter with X in share configurations
- Enable CDN version numbers by default
- Fix shuoshuo page JSON security with safeJSON helper
- Improve image lazy loading regex to handle minified HTML
- Fix search result HTML structure and styling
- Add margin-top to search result numbering for better alignment
2025-10-02 14:32:08 +08:00
Jerry Wong
4c3a782610 Merge pull request #1736 from DeepChirp/fix-zh-cn
fix(languages): 将大陆地区用字改为简体(`頁`→`页`)
2025-09-09 18:31:48 +08:00
DeepChirp
8645a4355d fix(languages): 将大陆地区用字改为简体( 2025-09-09 16:36:25 +08:00
Jerry
88f3f2eef3 Merge branch 'dev' 2025-09-09 15:42:28 +08:00
Jerry
4226c95818 Merge branch 'dev' of https://github.com/jerryc127/hexo-theme-butterfly into dev 2025-09-09 15:40:14 +08:00
Jerry
67c95cee0c 🔍 搜索功能改進:
- 新增本地搜索分頁配置 (enablePagination, hitsPerPage)
- 重構 Algolia 搜索邏輯,支援多索引和更好的錯誤處理
- 優化搜索 UI 樣式,包括分頁按鈕和響應式設計
- 改進搜索結果顯示,新增編號和更好的高亮處理

📦 依賴項更新:
- 更新 plugins.yml 中的多個插件版本 (abcjs, algolia, aplayer 等)
- 更新 package.json 版本號為 5.5.0

🎨 UI/UX 優化:
- 改進側邊欄和目錄的動畫效果
- 優化樣式佈局,調整寬度百分比
- 新增說說頁面的分頁導航組件
- 改進右側邊欄按鈕樣式

🐛 錯誤處理和代碼優化:
- 修復 Umami Analytics 的錯誤處理和數據驗證
- 改進懶加載圖片的正則表達式,避免匹配腳本標籤
- 移除未使用的變數和改進代碼結構
- 新增說說內容的 Markdown 渲染支援

🔧 其他改進:
- 更新翻譯功能,移除箭頭函數語法以提升相容性
2025-09-09 15:40:08 +08:00
Jerry Wong
0a72f43fae Merge pull request #1732 from DeepChirp/umami-script-name
feat(umami): 支持自定义脚本名称
2025-09-08 20:04:49 +08:00
Jerry Wong
006414da98 Merge pull request #1734 from DeepChirp/fix-pjax-failure
fix(Pjax): 增加错误处理以防止Pjax失效
2025-09-08 20:04:20 +08:00
DeepChirp
c61f55a773 fix(Pjax): 增加错误处理以防止Pjax失效 2025-09-05 23:43:14 +08:00
DeepChirp
6feac51de7 feat(umami): 支持自定义脚本名称 2025-09-03 12:56:07 +08:00
Jerry Wong
fcd760011a Update FUNDING.yml 2025-08-27 11:35:54 +08:00
Jerry
ec1a226774 Fix merge conflicts 2025-08-19 14:55:15 +08:00
Jerry
5ee24defc3 feat: 預設關閉 structured_data
feat: 升級一些項目依賴
feat: 重寫 README.md 和 README_CN.md,改進文檔結構和內容
feat: tags 標籤插件夜間模式調整
feat: 加按鈕懸停效果和動畫
fix: 修復右下角箭頭圖標位置沒有居中的 bug
feat: 增加右下角箭頭和滾動百分比的切換效果
improvement: 優化 tags 頁標籤雲顯示效果
improvement: 整合部分js到 init.js
improvement: 統一 CSS 變數使用,改進主題一致性
2025-08-19 14:49:39 +08:00
Jerry Wong
58818a0630 Merge pull request #1718 from Windsland52/dev
fix(gitalk): 修复 MD5 函数导致的评论聚合问题
2025-08-14 13:51:09 +08:00
Jerry Wong
a551b9277f Merge pull request #1720 from DeepChirp/structured_data
feat(structured_data): add `alternateNames` for backup
2025-08-14 13:50:33 +08:00
DeepChirp
1b5bc97431 feat(structured_data): add alternateNames for backup 2025-07-31 19:37:25 +08:00
Windsland52
0da72787fa fix(gitalk): 修复 MD5 函数导致的评论聚合问题
- 修改 url_for 调用添加 {relative: false} 参数
- 解决空字符串 MD5 导致多页面评论混合的问题
2025-07-30 08:27:46 +08:00
Jerry
dcb35181fc Merge branch 'dev' 2025-07-27 15:27:14 +08:00
Jerry
0e9b8f5b69 Merge branch 'dev' of https://github.com/jerryc127/hexo-theme-butterfly into dev 2025-07-27 15:26:37 +08:00
Jerry
89b3626c84 fix: 修復 aside 最後一個 沒有 margin-bottom 的 bug
feat: 適配 fortawesome v7
improvement: 優化 loading 加載
2025-07-27 15:26:28 +08:00
Jerry Wong
c5417d4532 Merge pull request #1699 from DeepChirp/copyright-space
在`©`后添加空格
2025-07-23 15:21:23 +08:00
DeepChirp
dd8a18e8f4 chore: clean up unnecessary comments 2025-07-15 13:41:26 +08:00
Jerry
736cd75cda Merge branch 'dev' 2025-07-09 13:25:05 +08:00
Jerry
a61e216452 fix: 修正 abcjs 報錯的 bug 2025-07-09 13:24:42 +08:00
DeepChirp
b89f165be3 chore: add a space after © 2025-07-07 13:14:38 +08:00
Jerry
1de3507843 Merge branch 'dev' 2025-07-04 23:28:11 +08:00
Jerry
d2eacd2d8a feat: 增加導航欄顯示文章標題的選項,並更新懶加載設置 2025-07-04 23:26:29 +08:00
Jerry
5bfc1da03b feat: 更新版本號並升級多個插件的依賴版本,改善功能和相容性 2025-07-04 23:12:47 +08:00
Jerry
21c238e5c1 Merge branch 'dev' of https://github.com/jerryc127/hexo-theme-butterfly into dev 2025-06-25 16:27:06 +08:00
Jerry
60fa703fd3 feat: 增加導覽欄顯示文章標題的選項 feat: 文章頁增加'返回首頁'文字 improvement: 當 per_page 為 0 時,頁面不顯示導覽列 2025-06-25 16:25:46 +08:00
Jerry Wong
7353de6c2e Merge pull request #1695 from DeepChirp/structured_data
feat(structured_data): 为网站首页添加结构化数据
2025-06-25 15:17:08 +08:00
DeepChirp
a9fe9f5332 feat(structured_data): 为网站首页添加结构化数据 2025-06-12 11:43:32 +08:00
Jerry
73de62a6e1 Merge branch 'dev' of https://github.com/jerryc127/hexo-theme-butterfly into dev 2025-05-09 17:57:40 +08:00
Jerry
cf059bd533 fix: 更新 package.json 和 plugins.yml 中的版本號
fix: 修正 truncateContent 函數, 加密文章不显示自动擷取內容
feat: 增加首頁樣式以支持單詞換行
fix: 修正 truncateContent 函數以正確處理自動擷取內容
fix: 修复 card_archives 计数 bug
fix: 修正分頁順序邏輯
2025-05-09 17:52:01 +08:00
Jerry Wong
8cb726ddaa Merge pull request #1683 from SamirLiu127/dev
feat: Add google tag manager
2025-05-09 17:44:32 +08:00
SamirLiu
baee803689 feat: move noscript to additional-js 2025-05-09 13:28:01 +08:00
SamirLiu
023d82820d feat: update merge_config.js 2025-05-09 13:23:17 +08:00
SamirLiu
b7f1610859 update: DataLayer parameter 2025-05-08 14:03:08 +08:00
SamirLiu
5bac44a284 feat: add dataLayer push event pjaxComplete 2025-05-08 13:51:09 +08:00
SamirLiu
9c5294b40c fix: update analytics.pug for google tag manager under pjax 2025-05-07 19:21:39 +08:00
SamirLiu
a7315b6bfc feat: add google tag manager config 2025-05-07 19:17:39 +08:00
SamirLiu
cb3778c686 feat: add google tag manager noscript 2025-05-07 19:17:39 +08:00
Jerry Wong
2ce969023c Merge pull request #1686 from guozhenyi/dev
chore: 补充缺少的依赖库
2025-05-07 18:28:03 +08:00
gzy
7c697c15e8 chore: 补充缺少的依赖库 2025-04-30 16:56:46 +08:00
Jerry
ca030589fb feat: 更新頁腳配置,增加導航欄和版權信息,改進樣式和結構
fix: 修改 getBgPath 函數以使用 this.url_for 獲取圖片的正確路徑
feat: 增加右側配置按鈕的動畫效果
2025-03-23 23:20:12 +08:00
Jerry
46cf1c4e80 Merge branch 'dev' 2025-03-04 16:19:44 +08:00
Jerry Wong
a3f6b625ed Update package.json 2025-03-04 16:14:26 +08:00
Jerry Wong
4a955f0f05 Merge pull request #1659 from LinkinStars/fix-link
fix(subtitle): fix the wrong subtitle link
2025-03-04 16:05:44 +08:00
LinkinStars
525ed7ac82 fix(subtitle): fix the wrong subtitle link 2025-03-03 15:00:27 +08:00
Jerry
b3ba4c9ac4 Merge branch 'dev' 2025-03-02 15:37:03 +08:00
Jerry
628d1bbe52 Merge branch 'dev' of https://github.com/jerryc127/hexo-theme-butterfly into dev 2025-03-02 15:27:34 +08:00
Jerry
ede4f8bfea update 2025-03-02 15:27:16 +08:00
Jerry Wong
feb3346cf6 Merge pull request #1650 from TephrocactusMYC/dev
feat(score tag): support RenderAbc options
2025-03-02 15:13:15 +08:00
Yuchen Mu
576fa5c80e fix: resolve issues from previous commit about feat(score tag) 2025-03-02 10:23:40 +08:00
Yuchen Mu
6019cc8b7c Merge branch 'jerryc127:dev' into dev 2025-03-02 10:12:00 +08:00
Jerry Wong
7f9409553a Merge pull request #1649 from icemyst/patch-1
Update pjax.pug
2025-03-02 01:06:20 +08:00
Yuchen Mu
2de7d34b2b feat(score tag): support RenderAbc options
Modified the score tag to pass custom options to the abcjs RenderAbc interface.
This enhancement enables proper rendering of guitar tablature with custom
settings such as instrument tuning, labels, and formatting options.
2025-02-24 19:03:46 +08:00
冰梦
4b8a610c08 Update pjax.pug
自定义404.html跳转更平滑,使用 window.location.href = e.request.responseURL 加载,自定义404.html加载速度缓慢,可能会出现loading二次加载
2025-02-20 14:10:13 +08:00
Jerry
0dd4645ece Merge branch 'dev' 2025-02-16 21:27:12 +08:00
Jerry
13720bd94d .DS_Store banished! 2025-02-16 21:26:08 +08:00
Jerry
3028b08526 update 2025-02-16 20:53:10 +08:00
Jerry
f605e6dc89 update 2025-02-16 20:38:17 +08:00
Jerry Wong
8ddc25753e Merge pull request #1639 from jerryc127/dev 2025-01-28 18:59:06 +08:00
Jerry Wong
92913a6193 Merge pull request #1635 from cxyfer/dev 2025-01-17 12:16:06 +08:00
cxyfer
1e20234d74 fix: update addtoany item reference for correct sharing functionality 2025-01-17 02:19:21 +08:00
myw
1f3ea55890 fix: 文章頁分頁不顯示的 bug
improvement: 優化部分插件導致文章頁分頁樣式錯亂的 bug
2025-01-13 00:21:47 +08:00
myw
8a0e14b9b8 Merge branch 'dev' 2025-01-12 15:33:52 +08:00
myw
3d4bf30948 fix: 修復隨機封面死循環的問題 2025-01-12 15:32:55 +08:00
myw
0dc6aede35 Merge branch 'dev' 2025-01-11 01:26:37 +08:00
myw
172a8ee846 更新依赖 2025-01-11 01:26:06 +08:00
myw
5331f4ef9e Merge branch 'dev' of https://github.com/jerryc127/hexo-theme-butterfly into dev 2024-12-10 20:41:14 +08:00
myw
0d0001c808 feat: 更新 plugins.yml 中的依賴版本至最新
feat: 優化 aside_archives ,改進性能和可讀性
feat: 改善 inlineImg 和 timeline 標籤的文檔,優化時間線邏輯
feat: 更新 gallery 標籤以支持額外參數,優化圖片顯示邏輯
improvement: 優化隨機封面過濾器邏輯, 避免連續重複
feat: 最新評論限制顯示 1-10 條之間
fix: artalk 的最新評論顯示待定或者封禁的評論的 bug
2024-12-10 20:35:58 +08:00
Jerry Wong
13db106172 Merge pull request #1619 from Henry-ZHR/dev
fix: 代码字体先 fallback 到 monospace 再到中文和 sans-serif
2024-12-08 00:53:59 +08:00
Henry-ZHR
f42048792e fix: 代码字体先 fallback 到 monospace 再到中文和 sans-serif 2024-12-07 20:43:16 +08:00
myw
a1caed17c7 Merge branch 'master' of https://github.com/jerryc127/hexo-theme-butterfly 2024-12-05 15:19:06 +08:00
myw
247c1b664d feat: 更新 lazyload 配置,支持原生 lazyload 功能
feat: 代碼優化
feat: 優化 pageType 邏輯
fix: 修復解密文章後, chartjs 沒有加載的 bug
2024-11-30 13:38:39 +08:00
myw
f7483d59b5 Merge branch 'dev' of https://github.com/jerryc127/hexo-theme-butterfly into dev 2024-11-15 14:56:56 +08:00
myw
b7771e5938 feat: 移除 pangu.js
feat: js 加载完成才显示聊天按钮
2024-11-15 14:53:40 +08:00
myw
a69cb0543f update 2024-11-15 14:52:52 +08:00
Jerry Wong
74c555fb37 Merge pull request #1602 from DeepChirp/structured_data
feat: 添加结构化数据支持
2024-11-11 18:46:57 +08:00
DeepChirp
648ca6eb4f chore: 简化结构化数据配置,移除不必要的嵌套 2024-11-11 18:42:46 +08:00
DeepChirp
e5a52d5621 fix: 移除结构化数据中不合规范的微数据 2024-11-11 16:29:28 +08:00
Jerry Wong
1045f66a51 Merge pull request #1603 from DeepChirp/image
chore: add `avif` to the list of supported image formats
2024-11-11 15:13:02 +08:00
DeepChirp
5338a2be99 chore: add avif to the list of supported image formats 2024-11-07 18:09:01 +08:00
DeepChirp
3ea138178d feat: 添加结构化数据支持 2024-11-07 17:55:33 +08:00
Jerry Wong
c8609e8433 Update package.json 2024-11-05 19:12:36 +08:00
myw
997b76b967 fix conflicts 2024-11-05 18:03:47 +08:00
myw
91c8c5cd4b feat: 過期通知優化,可單獨文章關閉
fix: 修復説説評論 css 受主題影響的 bug
2024-11-05 18:01:36 +08:00
214 changed files with 18080 additions and 15980 deletions

2
.github/FUNDING.yml vendored
View File

@@ -10,4 +10,4 @@ liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username otechie: # Replace with a single Otechie username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
custom: ['https://buy.stripe.com/3cs6rP6YA91sbbG5kk','https://jsd.012700.xyz/gh/jerryc127/CDN/Photo/wechat.jpg'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] custom: ['https://buy.stripe.com/3cs6rP6YA91sbbG5kk'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
.DS_Store
node_modules/

226
README.md
View File

@@ -1,117 +1,193 @@
<div align="right"> <div align="right">
<a title="Chinese" href="/README_CN.md">中文</a> <a title="中文" href="/README_CN.md">中文</a>
</div> </div>
<div align="center"> <div align="center">
<img src="./source/img/butterfly-icon.png" width="150" height="150" /> <img src="./source/img/butterfly-icon.png" width="150" height="150" alt="Butterfly Logo" />
# hexo-theme-butterfly # hexo-theme-butterfly
A modern, elegant and feature-rich theme for Hexo
![master version](https://img.shields.io/github/package-json/v/jerryc127/hexo-theme-butterfly/master?color=%231ab1ad&label=master) ![master version](https://img.shields.io/github/package-json/v/jerryc127/hexo-theme-butterfly/master?color=%231ab1ad&label=master)
![master version](https://img.shields.io/github/package-json/v/jerryc127/hexo-theme-butterfly/dev?label=dev) ![dev version](https://img.shields.io/github/package-json/v/jerryc127/hexo-theme-butterfly/dev?label=dev)
![https://img.shields.io/npm/v/hexo-theme-butterfly?color=%09%23bf00ff](https://img.shields.io/npm/v/hexo-theme-butterfly?color=%09%23bf00ff) ![npm version](https://img.shields.io/npm/v/hexo-theme-butterfly?color=%09%23bf00ff)
![hexo version](https://img.shields.io/badge/hexo-5.3.0+-0e83c) ![hexo version](https://img.shields.io/badge/hexo-5.3.0+-0e83cd)
![license](https://img.shields.io/github/license/jerryc127/hexo-theme-butterfly?color=FF5531) ![license](https://img.shields.io/github/license/jerryc127/hexo-theme-butterfly?color=FF5531)
![GitHub stars](https://img.shields.io/github/stars/jerryc127/hexo-theme-butterfly?style=social)
📢 Demo: [Butterfly](https://butterfly.js.org/) / [CrazyWong](https://blog.crazywong.com/) 📢 **Demo**: [Butterfly Official](https://butterfly.js.org/) | [CrazyWong's Blog](https://blog.crazywong.com/)
📖 Docs: [English](https://butterfly.js.org/en/posts/butterfly-docs-en-get-started/) / [Chinese](https://butterfly.js.org/posts/21cfbf15/) 📖 **Documentation**: [English Docs](https://butterfly.js.org/en/posts/butterfly-docs-en-get-started/) | [中文文档](https://butterfly.js.org/posts/21cfbf15/)
![](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/theme-butterfly-readme.png) ![Butterfly Theme Preview](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/theme-butterfly-readme.png)
</div> </div>
--- ---
## 💻 Installation ## 🚀 Quick Start
### GIT ### 💾 Installation
> If you are in Mainland China, you can download in [Gitee](https://gitee.com/immyw/hexo-theme-butterfly.git) #### Method 1: Git Installation (Recommended)
Stable branch [recommend]: > 💡 **Tip**: If GitHub access is slow in mainland China, you can use the [Gitee Mirror](https://gitee.com/immyw/hexo-theme-butterfly.git)
``` Execute in your Hexo blog root directory:
```bash
# Install stable version (recommended)
git clone -b master https://github.com/jerryc127/hexo-theme-butterfly.git themes/butterfly git clone -b master https://github.com/jerryc127/hexo-theme-butterfly.git themes/butterfly
``` ```
Dev branch: ```bash
# Install development version (early access to new features)
```
git clone -b dev https://github.com/jerryc127/hexo-theme-butterfly.git themes/butterfly git clone -b dev https://github.com/jerryc127/hexo-theme-butterfly.git themes/butterfly
``` ```
### NPM #### Method 2: NPM Installation
> It supports Hexo 5.0.0 or later > ⚠️ **Note**: NPM installation only supports Hexo 5.0.0 and above
In Hexo site root directory ```bash
npm install hexo-theme-butterfly
```powershell
npm i hexo-theme-butterfly
``` ```
## ⚙ Configuration ### Theme Configuration
Set theme in the hexo work folder's root config file `_config.yml`: 1. **Enable Theme**: Modify your Hexo configuration file `_config.yml`:
> theme: butterfly ```yaml
theme: butterfly
```
If you don't have pug & stylus renderer, try this: 2. **Install Dependencies**: If you haven't installed pug and stylus renderers, please run:
> npm install hexo-renderer-pug hexo-renderer-stylus ```bash
npm install hexo-renderer-pug hexo-renderer-stylus --save
```
## 🎉 Features ## ✨ Theme Features
- [x] Card UI Design ### 🎨 Design Style
- [x] Rounded Design/Squared Design - [x] **Card-based Design** - Modern card-style layout
- [X] Support sub-menu - [x] **Rounded/Square Design** - Customizable border styles
- [x] Two-column layout - [x] **Responsive Design** - Perfect adaptation to all screen sizes
- [x] Responsive Web Design - [x] **Two-column Layout** - Optimized reading experience
- [x] Dark Mode - [x] **Dark Mode** - Eye-friendly night mode
- [x] Pjax
- [x] Read Mode
- [x] Conversion between Traditional and Simplified Chinese
- [X] TOC catalog is available for both computers and mobile phones
- [X] Built-in Syntax Highlighting Themes (darker/pale night/light/ocean), also support customization
- [X] Code Blocks (Display code language/close or expand Code Blocks/Copy Button/word wrap)
- [X] Disable copy/Add a Copyright Notice to the Copied Text
- [X] Search (Algolia Search/Local Search)
- [x] Mathjax and Katex
- [x] Built-in 404 page
- [x] WordCount
- [x] Related articles
- [x] Displays outdated notice for a post
- [x] Share (Sharejs/Addtoany)
- [X] Comment (Disqus/Disqusjs/Livere/Gitalk/Valine/Waline/Utterances/Facebook Comments/Twikoo/Giscus/Remark42/artalk)
- [x] Multiple Comment System Support
- [x] Online Chats (Chatra/Tidio/Crisp)
- [x] Web analytics
- [x] Google AdSense
- [x] Webmaster Verification
- [x] Change website colour scheme
- [x] Typewriter Effect: activate_power_mode
- [x] Background effects (Canvas ribbon/canvas_ribbon_piao/canvas_nest)
- [x] Mouse click effects (Fireworks/Heart/Text)
- [x] Preloader/Loading Animation/pace.js
- [x] Busuanzi visitor counter
- [x] Medium Zoom/Fancybox
- [x] Mermaid
- [x] Chart.js
- [x] Justified Gallery
- [x] Lazyload images
- [x] Instantpage/Pangu/Snackbar notification toast/PWA......
## Contributors ### 📝 Content Features
- [x] **Multi-level Menu** - Support for secondary navigation menus
- [x] **Reading Mode** - Focused article reading experience
- [x] **TOC Navigation** - Desktop and mobile TOC support
- [x] **Word Count** - Display article word count and reading time
- [x] **Related Articles** - Smart recommendation of related content
- [x] **Outdated Reminder** - Automatic article update status alerts
- [x] **Traditional/Simplified Chinese** - Support for Traditional and Simplified Chinese switching
- [x] **Tag Plugins** - Rich tag plugin support
<a href="https://github.com/jerryc127/hexo-theme-butterfly/graphs/contributors"> ### 🔍 Search & Navigation
<img src="https://contrib.rocks/image?repo=jerryc127/hexo-theme-butterfly" /> - [x] **Multiple Search Options** - Algolia Search / Local Search / Docsearch
</a> - [x] **Built-in 404** - Beautiful 404 error page
- [x] **Pjax Support** - Smooth page transition experience
## 📷 Screenshots ### 🎨 Code Display
- [x] **Syntax Highlighting** - Built-in multiple themes (darker/pale night/light/ocean)
- [x] **Code Features** - Language display/fold expand/copy button/auto-wrap
- [x] **Math Formulas** - Support for Mathjax and Katex
![](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-1.jpg) ### 💬 Social Interaction
![](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-2.jpg) - [x] **Multiple Comment Systems** - Disqus/Gitalk/Valine/Waline/Twikoo/Giscus/Artalk etc.
![](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-3.jpg) - [x] **Dual Comments Support** - Enable two comment systems simultaneously
![](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-4.jpg) - [x] **Share Features** - Sharejs/Addtoany sharing components
- [x] **Live Chat** - Chatra/Tidio/Crisp instant messaging
### 📊 Analytics & Statistics
- [x] **Visit Statistics** - Busuanzi counter
- [x] **Site Analytics** - Google Analytics/Baidu Analytics/Cloudflare Analytics/Microsoft Clarity/Umami
- [x] **Webmaster Verification** - Major search engine verification
- [x] **Ad Support** - Google AdSense/custom ad slots
### 🎪 Visual Effects
- [x] **Typing Effects** - activate_power_mode animations
- [x] **Background Effects** - Static ribbons/dynamic ribbons/floating ribbons/Canvas Nest
- [x] **Mouse Effects** - Fireworks/hearts/text click effects
- [x] **Loading Animations** - Preloader and pace.js progress bars
- [x] **Image Effects** - Medium Zoom/Fancybox image lightbox
- [x] **Lazy Loading** - Image lazy loading optimization
### 🛠️ Advanced Features
- [x] **PWA Support** - Progressive Web App
- [x] **Copy Protection** - Disable text copying/copyright info append
- [x] **Theme Customization** - Custom site color schemes
- [x] **Chart Support** - Mermaid flowcharts/Chart.js data charts
- [x] **Music Notation** - ABCJS music notation support
- [x] **Music Player** - APlayer/Meting music playback
- [x] **Article Series** - Series article organization
- [x] **Instantpage** - Page preloading acceleration
- [x] **Snackbar** - Elegant notification messages
## 🤝 Contributors
Thanks to all the developers who have contributed to the Butterfly theme!
[![Contributors](https://contrib.rocks/image?repo=jerryc127/hexo-theme-butterfly)](https://github.com/jerryc127/hexo-theme-butterfly/graphs/contributors)
## 📸 Screenshots
<div align="center">
![Theme Demo](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-1.jpg)
![Theme Demo](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-2.jpg)
![Theme Demo](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-3.jpg)
![Theme Demo](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-4.jpg)
</div>
## ⭐ Star History
[![Star History Chart](https://api.star-history.com/svg?repos=jerryc127/hexo-theme-butterfly&type=Date)](https://star-history.com/#jerryc127/hexo-theme-butterfly&Date)
## 🤝 Building a Better Theme Together
We believe **the power of open source comes from everyone's participation**! Whether you're a developer, designer, or user, you can contribute to the development of the Butterfly theme.
### 💬 Get Help & Support
- 🐛 **Found a bug?** → [GitHub Issues](https://github.com/jerryc127/hexo-theme-butterfly/issues) - Let's solve it together!
- 💡 **Have ideas?** → [GitHub Discussions](https://github.com/jerryc127/hexo-theme-butterfly/discussions) - Share your creative ideas!
- 📚 **Learning to use?** → [Official Documentation](https://butterfly.js.org/) - Detailed usage guide
- 💬 **Real-time discussion?** → [Telegram Group](https://t.me/bu2fly) - Chat with community members
### 🎯 Contributing
Want to make Butterfly better? We welcome any form of contribution:
- **🔧 Code Contributions** - Fix bugs, add new features, optimize performance
- **📝 Documentation** - Improve docs, translate content, write tutorials
- **🎨 Design Suggestions** - UI/UX improvements, theme colors, icon design
- **🧪 Testing & Feedback** - Test new features, report issues, provide user experience
- **💰 Financial Support** - [Sponsor the Project](https://buy.stripe.com/3cs6rP6YA91sbbG5kk) - Support long-term development
## 📄 License
This project is licensed under the [Apache 2.0](LICENSE) License.
## 🙏 Acknowledgments
This theme is developed based on [hexo-theme-melody](https://github.com/Molunerfinn/hexo-theme-melody). Thanks to the original author for their excellent work that provided inspiration and foundation!
Thanks to all friends who have contributed to the development of the Butterfly theme. Your support has made this theme continuously improve and progress.
---
<div align="center">
**✨ If this theme helps you, please give us a ⭐ Star! ✨**
</div>

View File

@@ -4,114 +4,190 @@
<div align="center"> <div align="center">
<img src="./source/img/butterfly-icon.png" width="150" height="150" /> <img src="./source/img/butterfly-icon.png" width="150" height="150" alt="Butterfly Logo" />
# hexo-theme-butterfly # hexo-theme-butterfly
一個適用於 Hexo 的現代化、美觀且功能豐富的主題
![master version](https://img.shields.io/github/package-json/v/jerryc127/hexo-theme-butterfly/master?color=%231ab1ad&label=master) ![master version](https://img.shields.io/github/package-json/v/jerryc127/hexo-theme-butterfly/master?color=%231ab1ad&label=master)
![master version](https://img.shields.io/github/package-json/v/jerryc127/hexo-theme-butterfly/dev?label=dev) ![dev version](https://img.shields.io/github/package-json/v/jerryc127/hexo-theme-butterfly/dev?label=dev)
![https://img.shields.io/npm/v/hexo-theme-butterfly?color=%09%23bf00ff](https://img.shields.io/npm/v/hexo-theme-butterfly?color=%09%23bf00ff) ![npm version](https://img.shields.io/npm/v/hexo-theme-butterfly?color=%09%23bf00ff)
![hexo version](https://img.shields.io/badge/hexo-5.3.0+-0e83c) ![hexo version](https://img.shields.io/badge/hexo-5.3.0+-0e83cd)
![license](https://img.shields.io/github/license/jerryc127/hexo-theme-butterfly?color=FF5531) ![license](https://img.shields.io/github/license/jerryc127/hexo-theme-butterfly?color=FF5531)
![GitHub stars](https://img.shields.io/github/stars/jerryc127/hexo-theme-butterfly?style=social)
📢 預覽: [Butterfly](https://butterfly.js.org/) / [CrazyWong](https://blog.crazywong.com/) 📢 **在線預覽**: [Butterfly 官方](https://butterfly.js.org/) | [CrazyWong 博客](https://blog.crazywong.com/)
📖 文檔: [中文](https://butterfly.js.org/posts/21cfbf15/) / [English](https://butterfly.js.org/en/posts/butterfly-docs-en-get-started/) 📖 **完整文檔**: [中文文檔](https://butterfly.js.org/posts/21cfbf15/) | [English Docs](https://butterfly.js.org/en/posts/butterfly-docs-en-get-started/)
![](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/theme-butterfly-readme.png) ![Butterfly 主題預覽](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/theme-butterfly-readme.png)
</div> </div>
--- ---
## 💻 安裝 ## 🚀 快速開始
### Git 安裝 ### 💾 安裝方式
> 本倉庫同時上傳到 [Gitee](https://gitee.com/immyw/hexo-theme-butterfly.git),如果你訪問 Github 緩慢,可從 Gitee 中下載。 #### 方式一Git 安裝(推薦)
在博客根目錄裡安裝穩定版【推薦】 > 💡 **提示**: 如果您在中國大陸訪問 GitHub 速度較慢,可以使用 [Gitee 鏡像](https://gitee.com/immyw/hexo-theme-butterfly.git)
```powershell 在您的 Hexo 博客根目錄下執行:
```bash
# 安裝穩定版本(推薦)
git clone -b master https://github.com/jerryc127/hexo-theme-butterfly.git themes/butterfly git clone -b master https://github.com/jerryc127/hexo-theme-butterfly.git themes/butterfly
``` ```
如果想要安裝比較新的dev分支可以 ```bash
# 安裝開發版本(搶先體驗新功能)
```powershell
git clone -b dev https://github.com/jerryc127/hexo-theme-butterfly.git themes/butterfly git clone -b dev https://github.com/jerryc127/hexo-theme-butterfly.git themes/butterfly
``` ```
### npm 安裝 #### 方式二NPM 安裝
> 此方法只支持Hexo 5.0.0以上版本 > ⚠️ **注意**: NPM 安裝方式僅支援 Hexo 5.0.0以上版本
在博客根目錄裡 ```bash
npm install hexo-theme-butterfly
```powershell
npm i hexo-theme-butterfly
``` ```
## ⚙ 應用主題 ### 主題配置
修改hexo配置文件`_config.yml`,把主題改為`Butterfly` 1. **啟用主題**: 修改您的 Hexo 配置檔案 `_config.yml`
``` ```yaml
theme: butterfly theme: butterfly
``` ```
>如果你沒有pug以及stylus渲染器,請下載安裝: npm install hexo-renderer-pug hexo-renderer-stylus --save 2. **安裝依賴**: 如果您尚未安裝 pug 和 stylus 渲染器,請執行:
## 🎉 特色 ```bash
npm install hexo-renderer-pug hexo-renderer-stylus --save
```
- [x] 卡片化設計 ## ✨ 主題特色
- [x] 圓角化設計/直角化設計
- [X] 支持二級目錄
- [x] 雙欄設計
- [x] 響應式主題
- [x] 夜間模式
- [x] Pjax
- [x] 文章閲讀模式
- [x] 簡體和繁體轉換
- [X] 電腦和手機都可查看TOC目錄
- [X] 內置多種代碼配色darker/pale night/light/ocean可自定義代碼配色
- [X] 代碼塊顯示代碼語言/關閉或展開代碼塊/代碼複製/代碼自動換行
- [X] 可關閉文字複製/可開啟內容複製增加版權信息)
- [X] 兩種搜索( Algolia 搜索和本地搜索)
- [x] Mathjax 和 Katex
- [x] 內置404頁面
- [x] 顯示字數統計
- [x] 顯示相關文章
- [x] 過期文章提醒
- [x] 多種分享系統Sharejs/Addtoany
- [X] 多種評論系統Disqus/Disqusjs/Livere/Gitalk/Valine/Waline/Utterances/Facebook Comments/Twikoo/Giscus/Remark42/artalk
- [x] 支持雙評論部署
- [x] 多種在線聊天Chatra/Tidio/Crisp
- [x] 多種分析系統
- [x] 谷歌廣告/手動廣告位置
- [x] 各種站長驗證
- [x] 修改網站配色
- [x] 打字特效 activate_power_mode
- [x] 多種背景特效(靜止彩帶/動態彩帶/Canvas Nest
- [x] 多種鼠標點擊特效(煙花/文字/愛心)
- [x] 內置一種 Preloader 加載動畫和 pace.js 加載動畫條
- [x] 不蒜子訪問統計
- [x] 兩種大圖模式Medium Zoom/Fancybox
- [x] Mermaid 圖表顯示
- [x] Chart.js 圖表顯示
- [x] 照片牆
- [x] 圖片懶加載
- [x] Instantpage/Pangu/Snackbar彈窗/PWA......
## ✨ 貢獻者 ### 🎨 設計風格
- [x] **卡片化設計** - 現代化的卡片式佈局
- [x] **圓角/直角設計** - 支援自訂邊框樣式
- [x] **響應式設計** - 完美適配各種螢幕尺寸
- [x] **雙欄佈局** - 優化的閱讀體驗
- [x] **深色模式** - 護眼的夜間模式
<a href="https://github.com/jerryc127/hexo-theme-butterfly/graphs/contributors"> ### 📝 內容功能
<img src="https://contrib.rocks/image?repo=jerryc127/hexo-theme-butterfly" /> - [x] **多級選單** - 支援二級導航選單
</a> - [x] **閱讀模式** - 專注的文章閱讀體驗
- [x] **目錄導航** - 電腦和手機雙端支援 TOC
- [x] **字數統計** - 顯示文章字數和閱讀時間
- [x] **相關文章** - 智能推薦相關內容
- [x] **過期提醒** - 自動提示文章更新狀態
- [x] **簡繁轉換** - 支援繁體中文和簡體中文切換
- [x] **標籤外掛** - 豐富的標籤外掛支持
## 📷 截圖 ### 🔍 搜尋與導航
- [x] **多種搜尋** - Algolia 搜尋 / 本地搜尋 / Docsearch
- [x] **內建 404** - 美觀的 404 錯誤頁面
- [x] **Pjax 支援** - 流暢的頁面切換體驗
![](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-1.jpg) ### 🎨 程式碼展示
![](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-2.jpg) - [x] **語法高亮** - 內建多種主題darker/pale night/light/ocean
![](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-3.jpg) - [x] **程式碼功能** - 語言顯示/摺疊展開/複製按鈕/自動換行
![](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-4.jpg) - [x] **數學公式** - 支援 Mathjax 和 Katex
### 💬 社交互動
- [x] **多元評論系統** - Disqus/Gitalk/Valine/Waline/Twikoo/Giscus/Artalk 等
- [x] **雙評論支援** - 可同時啟用兩套評論系統
- [x] **分享功能** - Sharejs/Addtoany 分享套件
- [x] **線上客服** - Chatra/Tidio/Crisp 即時聊天
### 📊 數據分析
- [x] **訪問統計** - 不蒜子計數器
- [x] **網站分析** - Google Analytics/百度統計/Cloudflare Analytics/Microsoft Clarity/Umami
- [x] **站長驗證** - 各大搜尋引擎驗證
- [x] **廣告支援** - Google AdSense/自訂廣告位
### 🎪 視覺效果
- [x] **打字特效** - activate_power_mode 動畫
- [x] **背景特效** - 靜態彩帶/動態彩帶/飄帶效果/Canvas Nest
- [x] **滑鼠特效** - 煙花/愛心/文字點擊效果
- [x] **載入動畫** - Preloader 和 pace.js 進度條
- [x] **圖片效果** - Medium Zoom/Fancybox 圖片燈箱
- [x] **懶載入** - 圖片延遲載入優化
### 🛠️ 進階功能
- [x] **PWA 支援** - 漸進式網頁應用
- [x] **複製保護** - 可關閉文字複製/版權資訊追加
- [x] **主題定製** - 自訂網站配色方案
- [x] **圖表支援** - Mermaid 流程圖/Chart.js 數據圖表
- [x] **音樂符號** - ABCJS 音樂記譜法支援
- [x] **音樂播放器** - APlayer/Meting 音樂播放功能
- [x] **系列文章** - 系列文章組織功能
- [x] **Instantpage** - 頁面預載入加速
- [x] **Snackbar** - 優雅的提示訊息
## 🤝 貢獻者
感謝所有為 Butterfly 主題做出貢獻的開發者們!
[![Contributors](https://contrib.rocks/image?repo=jerryc127/hexo-theme-butterfly)](https://github.com/jerryc127/hexo-theme-butterfly/graphs/contributors)
## 📸 主題截圖
<div align="center">
![主題展示](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-1.jpg)
![主題展示](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-2.jpg)
![主題展示](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-3.jpg)
![主題展示](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-4.jpg)
</div>
## ⭐ Star 趨勢
[![Star History Chart](https://api.star-history.com/svg?repos=jerryc127/hexo-theme-butterfly&type=Date)](https://star-history.com/#jerryc127/hexo-theme-butterfly&Date)
## 🤝 一起構建更美好的主題
我們相信,**開源的力量來自於每一個人的參與**!無論您是開發者、設計師還是用戶,都可以為 Butterfly 主題的發展貢獻力量。
### 💬 獲取幫助與支援
- 🐛 **發現問題?** → [GitHub Issues](https://github.com/jerryc127/hexo-theme-butterfly/issues) - 讓我們一起解決!
- 💡 **有好想法?** → [GitHub Discussions](https://github.com/jerryc127/hexo-theme-butterfly/discussions) - 分享您的創意想法!
- 📚 **學習使用?** → [官方文檔](https://butterfly.js.org/) - 詳細的使用指南
- 💬 **即時討論?** → [Telegram 群組](https://t.me/bu2fly) - 與社群成員實時交流
### 🎯 參與貢獻
想要讓 Butterfly 變得更好嗎?我們歡迎您的任何形式的貢獻:
- **🔧 代碼貢獻** - 修復 Bug、添加新功能、優化性能
- **📝 文檔完善** - 改進文檔、翻譯內容、撰寫教程
- **🎨 設計建議** - UI/UX 改進、主題配色、圖示設計
- **🧪 測試反饋** - 測試新功能、回報問題、提供使用體驗
- **💰 資金支援** - [贊助項目](https://buy.stripe.com/3cs6rP6YA91sbbG5kk) - 支持長期發展
## 📄 授權條款
本專案採用 [Apache 2.0](LICENSE) 授權條款。
## 🙏 致敬與感謝
本主題基於 [hexo-theme-melody](https://github.com/Molunerfinn/hexo-theme-melody) 進行開發,感謝原作者的精彩創作為我們提供了靈感與基礎!
感謝所有為 Butterfly 主題發展做出貢獻的朋友們,是你們的支持讓這個主題能夠不斷完善與進步。
---
<div align="center">
**✨ 如果這個主題對您有幫助,請給我們一個 ⭐ Star✨**
</div>

View File

@@ -13,6 +13,7 @@ nav:
# Navigation bar logo image # Navigation bar logo image
logo: logo:
display_title: true display_title: true
display_post_title: true
# Whether to fix navigation bar # Whether to fix navigation bar
fixed: false fixed: false
@@ -158,7 +159,7 @@ subtitle:
# Choose: false/1/2/3 # Choose: false/1/2/3
# false - disable the function # false - disable the function
# 1 - hitokoto.cn # 1 - hitokoto.cn
# 2 - yijuzhan.com # 2 - https://api.aa1.cn/doc/yiyan.html
# 3 - jinrishici.com # 3 - jinrishici.com
source: false source: false
# If you close the typewriter effect, the subtitle will only show the first line of sub # If you close the typewriter effect, the subtitle will only show the first line of sub
@@ -254,12 +255,15 @@ noticeOutdate:
# Footer Settings # Footer Settings
# -------------------------------------- # --------------------------------------
footer: footer:
nav:
owner: owner:
enable: true enable: true
since: 2019 since: 2025
custom_text:
# Copyright of theme and framework # Copyright of theme and framework
copyright: true copyright:
enable: true
version: true
custom_text:
# -------------------------------------- # --------------------------------------
# Aside Settings # Aside Settings
@@ -399,6 +403,9 @@ rightside_item_order:
# Default: toc,chat,comment # Default: toc,chat,comment
show: show:
# Animation for the bottom right config button
rightside_config_animation: true
# -------------------------------------- # --------------------------------------
# Global Settings # Global Settings
# -------------------------------------- # --------------------------------------
@@ -481,6 +488,11 @@ search:
top_n_per_article: 1 top_n_per_article: 1
# Unescape html strings to the readable one. # Unescape html strings to the readable one.
unescape: false unescape: false
# Enable pagination for search results
pagination:
enable: false
# Number of search results per page
hitsPerPage: 8
CDN: CDN:
# Docsearch # Docsearch
@@ -503,12 +515,12 @@ share:
# Share.js # Share.js
# https://github.com/overtrue/share.js # https://github.com/overtrue/share.js
sharejs: sharejs:
sites: facebook,twitter,wechat,weibo,qq sites: facebook,x,wechat,weibo,qq
# AddToAny # AddToAny
# https://www.addtoany.com/ # https://www.addtoany.com/
addtoany: addtoany:
item: facebook,twitter,wechat,sina_weibo,facebook_messenger,email,copy_link item: facebook,x,wechat,sina_weibo,facebook_messenger,email,copy_link
# -------------------------------------- # --------------------------------------
# Comments System # Comments System
@@ -687,6 +699,7 @@ umami_analytics:
enable: false enable: false
# For self-hosted setups, configure the hostname of the Umami instance # For self-hosted setups, configure the hostname of the Umami instance
serverURL: serverURL:
script_name: script.js
website_id: website_id:
option: option:
UV_PV: UV_PV:
@@ -696,6 +709,12 @@ umami_analytics:
# Umami Cloud (API key) / self-hosted Umami (token) # Umami Cloud (API key) / self-hosted Umami (token)
token: token:
# https://www.googletagmanager.com/
google_tag_manager:
tag_id:
# optional
domain:
# -------------------------------------- # --------------------------------------
# Advertisement # Advertisement
# -------------------------------------- # --------------------------------------
@@ -983,17 +1002,12 @@ snackbar:
# https://instant.page/ # https://instant.page/
instantpage: false instantpage: false
# Pangu - Insert a space between Chinese character and English character
# https://github.com/vinta/pangu.js
pangu:
enable: false
# Specify the field to use pangu (site or post)
field: site
# Lazyload # Lazyload
# https://github.com/verlok/vanilla-lazyload # https://github.com/verlok/vanilla-lazyload
lazyload: lazyload:
enable: false enable: false
# Use browser's native lazyload instead of vanilla-lazyload
native: false
# Specify the field to use lazyload (site or post) # Specify the field to use lazyload (site or post)
field: site field: site
placeholder: placeholder:
@@ -1023,6 +1037,14 @@ Open_Graph_meta:
# fb_admins: # fb_admins:
# fb_app_id: # fb_app_id:
# Structured Data
# https://developers.google.com/search/docs/guides/intro-structured-data
structured_data:
enable: false
# Alternate name for the site, used in structured data
# Format: ['name1', 'name2']
alternate_name:
# Add the vendor prefixes to ensure compatibility # Add the vendor prefixes to ensure compatibility
css_prefix: true css_prefix: true
@@ -1045,7 +1067,7 @@ CDN:
third_party_provider: jsdelivr third_party_provider: jsdelivr
# Add version number to url, true or false # Add version number to url, true or false
version: false version: true
# Custom format # Custom format
# For example: https://cdn.staticfile.org/${cdnjs_name}/${version}/${min_cdnjs_file} # For example: https://cdn.staticfile.org/${cdnjs_name}/${version}/${min_cdnjs_file}
@@ -1081,7 +1103,6 @@ CDN:
# gitalk_css: # gitalk_css:
# giscus: # giscus:
# instantpage: # instantpage:
# instantsearch:
# katex: # katex:
# katex_copytex: # katex_copytex:
# lazyload: # lazyload:
@@ -1092,7 +1113,6 @@ CDN:
# medium_zoom: # medium_zoom:
# mermaid: # mermaid:
# meting_js: # meting_js:
# pangu:
# prismjs_autoloader: # prismjs_autoloader:
# prismjs_js: # prismjs_js:
# prismjs_lineNumber_js: # prismjs_lineNumber_js:

View File

@@ -32,6 +32,7 @@ post:
copyright_content: 'All articles on this blog are licensed under <a href="%s">%s</a> unless otherwise stated.' copyright_content: 'All articles on this blog are licensed under <a href="%s">%s</a> unless otherwise stated.'
recommend: Related Articles recommend: Related Articles
edit: Edit edit: Edit
back_to_home: Back to Home
search: search:
title: Search title: Search
@@ -47,6 +48,7 @@ search:
pagination: pagination:
prev: Previous prev: Previous
next: Next next: Next
page_info: 'Page ${current} of ${total}'
comment: Comments comment: Comments

View File

@@ -32,6 +32,7 @@ post:
copyright_content: 'All articles on this blog are licensed under <a href="%s">%s</a> unless otherwise stated.' copyright_content: 'All articles on this blog are licensed under <a href="%s">%s</a> unless otherwise stated.'
recommend: Related Articles recommend: Related Articles
edit: Edit edit: Edit
back_to_home: Back to Home
search: search:
title: Search title: Search
@@ -47,6 +48,7 @@ search:
pagination: pagination:
prev: Previous prev: Previous
next: Next next: Next
page_info: 'Page ${current} of ${total}'
comment: Comments comment: Comments

View File

@@ -32,6 +32,7 @@ post:
copyright_content: 'このブログのすべての記事は、<a href="%s">%s</a> ライセンスの下で提供されており、特に明記されていない限り、すべての権利を留保します。転載時には出典を明記してください: <a href="%s">%s</a>。' copyright_content: 'このブログのすべての記事は、<a href="%s">%s</a> ライセンスの下で提供されており、特に明記されていない限り、すべての権利を留保します。転載時には出典を明記してください: <a href="%s">%s</a>。'
recommend: 関連記事 recommend: 関連記事
edit: 編集 edit: 編集
back_to_home: ホームに戻る
search: search:
title: 検索 title: 検索
@@ -47,6 +48,7 @@ search:
pagination: pagination:
prev: 前へ prev: 前へ
next: 次へ next: 次へ
page_info: '${current} ページ / 合計 ${total} ページ'
comment: コメント comment: コメント

View File

@@ -32,6 +32,7 @@ post:
copyright_content: '이 블로그의 모든 글은 <a href="%s">%s</a> 라이선스를 따르며, 별도로 명시되지 않는 한 모든 권리를 보유합니다. 재배포 시 출처를 명시해 주세요: <a href="%s">%s</a>.' copyright_content: '이 블로그의 모든 글은 <a href="%s">%s</a> 라이선스를 따르며, 별도로 명시되지 않는 한 모든 권리를 보유합니다. 재배포 시 출처를 명시해 주세요: <a href="%s">%s</a>.'
recommend: 관련 글 recommend: 관련 글
edit: 편집 edit: 편집
back_to_home: 홈으로 돌아가기
search: search:
title: 검색 title: 검색
@@ -47,6 +48,7 @@ search:
pagination: pagination:
prev: 이전 prev: 이전
next: 다음 next: 다음
page_info: '${current} 페이지 / 총 ${total} 페이지'
comment: 댓글 comment: 댓글

View File

@@ -33,6 +33,7 @@ post:
<a href="%s" target="_blank">%s</a> 许可协议。转载请注明来源 <a href="%s" target="_blank">%s</a>' <a href="%s" target="_blank">%s</a> 许可协议。转载请注明来源 <a href="%s" target="_blank">%s</a>'
recommend: 相关推荐 recommend: 相关推荐
edit: 编辑 edit: 编辑
back_to_home: 返回首页
search: search:
title: 搜索 title: 搜索
@@ -48,6 +49,7 @@ search:
pagination: pagination:
prev: 上一篇 prev: 上一篇
next: 下一篇 next: 下一篇
page_info: '第 ${current} 页 / 共 ${total} 页'
comment: 评论 comment: 评论

View File

@@ -32,6 +32,7 @@ post:
copyright_content: '除特別聲明外,本博客所有文章均採用<a href="%s">%s</a> 授權協議。轉載請註明出處:<a href="%s">%s</a>。' copyright_content: '除特別聲明外,本博客所有文章均採用<a href="%s">%s</a> 授權協議。轉載請註明出處:<a href="%s">%s</a>。'
recommend: 相關文章 recommend: 相關文章
edit: 編輯 edit: 編輯
back_to_home: 返回首頁
search: search:
title: 搜尋 title: 搜尋
@@ -47,6 +48,7 @@ search:
pagination: pagination:
prev: 上一頁 prev: 上一頁
next: 下一頁 next: 下一頁
page_info: '第 ${current} 頁 / 共 ${total} 頁'
comment: 評論 comment: 評論

View File

@@ -32,6 +32,7 @@ post:
copyright_content: '本部落格所有文章除特別聲明外,均採用<a href="%s" target="_blank">%s</a> 授權協議。轉載請註明來源 <a href="%s" target="_blank">%s</a>' copyright_content: '本部落格所有文章除特別聲明外,均採用<a href="%s" target="_blank">%s</a> 授權協議。轉載請註明來源 <a href="%s" target="_blank">%s</a>'
recommend: 相關推薦 recommend: 相關推薦
edit: 編輯 edit: 編輯
back_to_home: 返回首頁
search: search:
title: 搜尋 title: 搜尋
@@ -47,6 +48,7 @@ search:
pagination: pagination:
prev: 上一篇 prev: 上一篇
next: 下一篇 next: 下一篇
page_info: '第 ${current} 頁 / 共 ${total} 頁'
comment: 評論 comment: 評論

View File

@@ -11,15 +11,12 @@ div
if theme.instantpage if theme.instantpage
script(src=url_for(theme.asset.instantpage), type='module') script(src=url_for(theme.asset.instantpage), type='module')
if theme.lazyload.enable if theme.lazyload.enable && !theme.lazyload.native
script(src=url_for(theme.asset.lazyload)) script(src=url_for(theme.asset.lazyload))
if theme.snackbar.enable if theme.snackbar.enable
script(src=url_for(theme.asset.snackbar)) script(src=url_for(theme.asset.snackbar))
if theme.pangu.enable
!= partial("includes/third-party/pangu.pug", {}, { cache: true })
.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 })
@@ -36,7 +33,7 @@ div
!= partial("includes/third-party/prismjs", {}, { cache: true }) != partial("includes/third-party/prismjs", {}, { cache: true })
if theme.aside.enable && theme.aside.card_newest_comments.enable if theme.aside.enable && theme.aside.card_newest_comments.enable
if theme.pjax.enable || (!is_post() && page.aside !== false) if theme.pjax.enable || (globalPageType !== 'post' && page.aside !== false)
!= partial("includes/third-party/newest-comments/index", {}, { cache: true }) != partial("includes/third-party/newest-comments/index", {}, { cache: true })
!= fragment_cache('injectBottom', function(){return injectHtml(theme.inject.bottom)}) != fragment_cache('injectBottom', function(){return injectHtml(theme.inject.bottom)})
@@ -55,6 +52,10 @@ div
!= partial("includes/third-party/umami_analytics", {}, { cache: true }) != partial("includes/third-party/umami_analytics", {}, { cache: true })
if theme.busuanzi.site_uv || theme.busuanzi.site_pv || theme.busuanzi.page_pv if theme.busuanzi.site_uv || theme.busuanzi.site_pv || theme.busuanzi.page_pv
script(async data-pjax src= theme.asset.busuanzi || '//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js') script(async data-pjax src=url_for(theme.asset.busuanzi) || '//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js')
!= partial('includes/third-party/search/index', {}, { cache: true }) != partial('includes/third-party/search/index', {}, { cache: true })
if theme.google_tag_manager && theme.google_tag_manager.tag_id
noscript
iframe(src=`${theme.google_tag_manager.domain ? theme.google_tag_manager.domain : 'https://www.googletagmanager.com'}/ns.html?id=${theme.google_tag_manager.tag_id}` height="0" width="0" style="display:none;visibility:hidden")

View File

@@ -1,18 +1,39 @@
#footer-wrap - const { nav, owner, copyright, custom_text } = theme.footer
if theme.footer.owner.enable
if nav
.footer-flex
for block in nav
.footer-flex-items(style=`${ block.width ? 'flex-grow:' + block.width : '' }`)
for blockItem in block.content
.footer-flex-item
.footer-flex-title= blockItem.title
.footer-flex-content
for subitem in blockItem.item
if subitem.html
div!= subitem.html
else if subitem.url
a(href=url_for(subitem.url), target='_blank' title=subitem.title)= subitem.title
else if subitem.title
div!= subitem.title
.footer-other
.footer-copyright
if owner.enable
- const currentYear = new Date().getFullYear() - const currentYear = new Date().getFullYear()
- const sinceYear = theme.footer.owner.since - const sinceYear = owner.since
.copyright span.copyright
if sinceYear && sinceYear != currentYear if sinceYear && sinceYear != currentYear
!= `&copy;${sinceYear} - ${currentYear} By ${config.author}` != `&copy;&nbsp;${sinceYear} - ${currentYear} By ${config.author}`
else else
!= `&copy;${currentYear} By ${config.author}` != `&copy;&nbsp;${currentYear} By ${config.author}`
if theme.footer.copyright if copyright.enable
.framework-info - const v = copyright.version ? getVersion() : false
span.framework-info
if owner.enable && nav
span.footer-separator |
span= _p('footer.framework') + ' ' span= _p('footer.framework') + ' '
a(href='https://hexo.io')= 'Hexo' a(href='https://hexo.io')= `Hexo${ v ? ' ' + v.hexo : '' }`
span.footer-separator | span.footer-separator |
span= _p('footer.theme') + ' ' span= _p('footer.theme') + ' '
a(href='https://github.com/jerryc127/hexo-theme-butterfly')= 'Butterfly' a(href='https://github.com/jerryc127/hexo-theme-butterfly')= `Butterfly${ v ? ' ' + v.theme : '' }`
if theme.footer.custom_text if theme.footer.custom_text
.footer_custom_text!= theme.footer.custom_text .footer_custom_text!= theme.footer.custom_text

View File

@@ -1,12 +1,18 @@
- var pageTitle - var pageTitle
- is_archive() ? page.title = findArchivesTitle(page, theme.menu, date) : '' - globalPageType === 'archive' ? page.title = findArchivesTitle(page, theme.menu, date) : ''
- if (is_tag()) pageTitle = _p('page.tag') + ': ' + page.tag case globalPageType
- else if (is_category()) pageTitle = _p('page.category') + ': ' + page.category when 'tag'
- else if (is_current('/404.html', [strict])) pageTitle = _p('error404') - pageTitle = _p('page.tag') + ': ' + page.tag
- else pageTitle = page.title || config.title || '' when 'category'
- pageTitle = _p('page.category') + ': ' + page.category
when '404'
- pageTitle = _p('error404')
default
- pageTitle = page.title || config.title || ''
- var isSubtitle = config.subtitle ? ' - ' + config.subtitle : '' - var isSubtitle = config.subtitle ? ' - ' + config.subtitle : ''
- var tabTitle = is_home() || !pageTitle ? config.title + isSubtitle : pageTitle + ' | ' + config.title - var tabTitle = globalPageType === 'home' || !pageTitle ? config.title + isSubtitle : pageTitle + ' | ' + config.title
- var pageAuthor = config.email ? config.author + ',' + config.email : config.author - var pageAuthor = config.email ? config.author + ',' + config.email : config.author
- var pageCopyright = config.copyright || config.author - var pageCopyright = config.copyright || config.author
- var themeColorLight = theme.theme_color && theme.theme_color.enable && theme.theme_color.meta_theme_color_light || '#ffffff' - var themeColorLight = theme.theme_color && theme.theme_color.enable && theme.theme_color.meta_theme_color_light || '#ffffff'
@@ -25,6 +31,9 @@ meta(name="theme-color" content=themeColor)
//- Open_Graph //- Open_Graph
include ./head/Open_Graph.pug include ./head/Open_Graph.pug
//- Structured Data
include ./head/structured_data.pug
!=favicon_tag(theme.favicon || config.favicon) !=favicon_tag(theme.favicon || config.favicon)
link(rel="canonical" href=urlNoIndex(null,config.pretty_urls.trailing_index,config.pretty_urls.trailing_html)) link(rel="canonical" href=urlNoIndex(null,config.pretty_urls.trailing_index,config.pretty_urls.trailing_html))

View File

@@ -2,7 +2,7 @@ if theme.Open_Graph_meta.enable
- -
const coverVal = page.cover_type === 'img' ? page.cover : theme.avatar.img const coverVal = page.cover_type === 'img' ? page.cover : theme.avatar.img
let ogOption = Object.assign({ let ogOption = Object.assign({
type: is_post() ? 'article' : 'website', type: globalPageType === 'post' ? 'article' : 'website',
image: coverVal ? full_url_for(coverVal) : '', image: coverVal ? full_url_for(coverVal) : '',
fb_admins: theme.facebook_comments.user_id || '', fb_admins: theme.facebook_comments.user_id || '',
fb_app_id: theme.facebook_comments.app_id || '', fb_app_id: theme.facebook_comments.app_id || '',

View File

@@ -32,3 +32,14 @@ if theme.microsoft_clarity
t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i; t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i;
y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y); y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y);
})(window, document, "clarity", "script", "!{theme.microsoft_clarity}"); })(window, document, "clarity", "script", "!{theme.microsoft_clarity}");
if (theme.google_tag_manager && theme.google_tag_manager.tag_id)
script.
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
"!{theme.google_tag_manager.domain ? theme.google_tag_manager.domain : 'https://www.googletagmanager.com'}/gtm.js?id="+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','!{theme.google_tag_manager.tag_id}');
btf.addGlobalFn('pjaxComplete', () => {
dataLayer.push({'event': 'pjaxComplete', 'page_title': document.title, 'page_location': location.href, 'page_path': window.location.pathname})
}, 'google_tag_manager')

View File

@@ -10,7 +10,6 @@
hitsPerPage: theme.search.algolia_search.hitsPerPage, hitsPerPage: theme.search.algolia_search.hitsPerPage,
// search languages // search languages
languages: { languages: {
input_placeholder: theme.search.placeholder || _p("search.input_placeholder"),
hits_empty: _p("search.algolia_search.hits_empty"), hits_empty: _p("search.algolia_search.hits_empty"),
hits_stats: _p("search.algolia_search.hits_stats"), hits_stats: _p("search.algolia_search.hits_stats"),
} }
@@ -19,12 +18,16 @@
let localSearch = 'undefined' let localSearch = 'undefined'
if (theme.search.use === 'local_search') { if (theme.search.use === 'local_search') {
const { CDN, preload, top_n_per_article, unescape } = theme.search.local_search const { CDN, preload, top_n_per_article, pagination, unescape } = theme.search.local_search
localSearch = JSON.stringify({ localSearch = JSON.stringify({
path: CDN || config.root + config.search.path, path: CDN || config.root + config.search.path,
preload, preload,
top_n_per_article, top_n_per_article,
unescape, unescape,
pagination: {
enable: pagination.enable,
hitsPerPage: pagination.hitsPerPage
},
languages: { languages: {
// search languages // search languages
hits_empty: _p("search.local_search.hits_empty"), hits_empty: _p("search.local_search.hits_empty"),
@@ -69,16 +72,6 @@
}) })
} }
let noticeOutdate = 'undefined'
if (theme.noticeOutdate && theme.noticeOutdate.enable) {
noticeOutdate = JSON.stringify({
limitDay: theme.noticeOutdate.limit_day,
position: theme.noticeOutdate.position,
messagePrev: theme.noticeOutdate.message_prev,
messageNext: theme.noticeOutdate.message_next,
})
}
let highlight = 'undefined' let highlight = 'undefined'
let syntaxHighlighter = config.syntax_highlighter let syntaxHighlighter = config.syntax_highlighter
let highlightEnable = syntaxHighlighter ? ['highlight.js', 'prismjs'].includes(syntaxHighlighter) : (config.highlight.enable || config.prismjs.enable) let highlightEnable = syntaxHighlighter ? ['highlight.js', 'prismjs'].includes(syntaxHighlighter) : (config.highlight.enable || config.prismjs.enable)
@@ -100,7 +93,6 @@ script.
algolia: !{algolia}, algolia: !{algolia},
localSearch: !{localSearch}, localSearch: !{localSearch},
translate: !{translate}, translate: !{translate},
noticeOutdate: !{noticeOutdate},
highlight: !{highlight}, highlight: !{highlight},
copy: { copy: {
success: '!{_p("copy.success")}', success: '!{_p("copy.success")}',
@@ -127,7 +119,7 @@ script.
buttonText: '!{_p("load_more")}' buttonText: '!{_p("load_more")}'
}, },
isPhotoFigcaption: !{theme.photofigcaption}, isPhotoFigcaption: !{theme.photofigcaption},
islazyload: !{theme.lazyload.enable}, islazyloadPlugin: !{theme.lazyload.enable && !theme.lazyload.native},
isAnchor: !{theme.anchor.auto_update || false}, isAnchor: !{theme.anchor.auto_update || false},
percent: { percent: {
toc: !{theme.toc.scroll_percent}, toc: !{theme.toc.scroll_percent},

View File

@@ -9,8 +9,8 @@
var showToc = false var showToc = false
if (theme.aside.enable && page.aside !== false) { if (theme.aside.enable && page.aside !== false) {
let tocEnable = false let tocEnable = false
if (is_post() && theme.toc.post) tocEnable = true if (globalPageType === 'post' && theme.toc.post) tocEnable = true
else if (is_page() && theme.toc.page) tocEnable = true else if (globalPageType === 'page' && theme.toc.page) tocEnable = true
const pageToc = typeof page.toc === 'boolean' ? page.toc : tocEnable const pageToc = typeof page.toc === 'boolean' ? page.toc : tocEnable
showToc = pageToc && (toc(page.content) !== '' || page.encrypt === true) showToc = pageToc && (toc(page.content) !== '' || page.encrypt === true)
} }
@@ -19,10 +19,7 @@
script#config-diff. script#config-diff.
var GLOBAL_CONFIG_SITE = { var GLOBAL_CONFIG_SITE = {
title: '!{titleVal}', title: '!{titleVal}',
isPost: !{is_post()},
isHome: !{is_home()},
isHighlightShrink: !{isHighlightShrink}, isHighlightShrink: !{isHighlightShrink},
isToc: !{showToc}, isToc: !{showToc},
postUpdate: '!{full_date(page.updated)}', pageType: '!{page.type == 'shuoshuo' ? 'shuoshuo' : globalPageType}'
isShuoshuo: !{page.type == 'shuoshuo'}
} }

View File

@@ -0,0 +1,67 @@
if theme.structured_data
if page.layout === 'post'
-
// https://developers.google.com/search/docs/appearance/structured-data/article
const title = page.title
const url = page.permalink
const imageVal = page.cover_type === 'img' ? page.cover : theme.avatar.img
const image = imageVal ? full_url_for(imageVal) : ''
const datePublished = page.date.toISOString()
const dateModified = (page.updated || page.date).toISOString()
const author = page.copyright_author || config.author
const authorHrefVal = page.copyright_author_href || theme.post_copyright.author_href || config.url
const authorHref = full_url_for(authorHrefVal)
const jsonLd = {
"@context": "https://schema.org",
"@type": "BlogPosting",
"headline": title,
"url": url,
"image": image,
"datePublished": datePublished,
"dateModified": dateModified,
"author": [{
"@type": "Person",
"name": author,
"url": authorHref
}]
}
jsonLdScript = JSON.stringify(jsonLd, null, 2)
-
else if is_home() && (!page.current || page.current === 1)
-
// https://developers.google.com/search/docs/appearance/site-names#website
const baseUrl = config.url;
const currentPath = url_for('/');
const isRootOrSubdomain = currentPath.split('/').filter(Boolean).length === 0;
if (isRootOrSubdomain) {
const domain = new URL(config.url).hostname;
const alternateNames = theme.structured_data.alternate_name || [];
if (config.subtitle) {
alternateNames.push(config.subtitle);
}
if (domain) {
alternateNames.push(domain);
}
const jsonLd = {
"@context": "https://schema.org",
"@type": "WebSite",
"name": config.title,
"alternateName": alternateNames,
"url": full_url_for('/'),
}
jsonLdScript = JSON.stringify(jsonLd, null, 2)
}
-
script(type="application/ld+json").
!{jsonLdScript}

View File

@@ -6,33 +6,32 @@
var bg_img = '' var bg_img = ''
if !theme.disable_top_img && page.top_img !== false if !theme.disable_top_img && page.top_img !== false
if is_post() case globalPageType
when 'post'
- top_img = page.top_img || page.cover || theme.default_top_img - top_img = page.top_img || page.cover || theme.default_top_img
else if is_page() when 'page'
- top_img = page.top_img || theme.default_top_img - top_img = page.top_img || theme.default_top_img
else if is_tag() when 'tag'
- top_img = theme.tag_per_img && theme.tag_per_img[page.tag] - top_img = theme.tag_per_img && theme.tag_per_img[page.tag] || returnTopImg(theme.tag_img)
- top_img = top_img || returnTopImg(theme.tag_img) when 'category'
else if is_category() - top_img = theme.category_per_img && theme.category_per_img[page.category] || returnTopImg(theme.category_img)
- top_img = theme.category_per_img && theme.category_per_img[page.category] when 'home'
- top_img = top_img || returnTopImg(theme.category_img)
else if is_home()
- top_img = returnTopImg(theme.index_img) - top_img = returnTopImg(theme.index_img)
else if is_archive() when 'archive'
- top_img = returnTopImg(theme.archive_img) - top_img = returnTopImg(theme.archive_img)
else default
- top_img = page.top_img || theme.default_top_img - top_img = page.top_img || theme.default_top_img
if top_img !== false if top_img !== false
- bg_img = getBgPath(top_img) - bg_img = getBgPath(top_img)
- headerClassName = is_home() ? 'full_page' : is_post() ? 'post-bg' : 'not-home-page' - headerClassName = globalPageType === 'home' ? 'full_page' : globalPageType === 'post' ? 'post-bg' : 'not-home-page'
header#page-header(class=`${headerClassName + isFixedClass}` style=bg_img) header#page-header(class=`${headerClassName + isFixedClass}` style=bg_img)
include ./nav.pug include ./nav.pug
if top_img !== false if top_img !== false
if is_post() if globalPageType === 'post'
include ./post-info.pug include ./post-info.pug
else if is_home() else if globalPageType === 'home'
#site-info #site-info
h1#site-title=config.title h1#site-title=config.title
if theme.subtitle.enable if theme.subtitle.enable
@@ -48,6 +47,6 @@ header#page-header(class=`${headerClassName + isFixedClass}` style=bg_img)
#page-site-info #page-site-info
h1#site-title=page.title || page.tag || page.category h1#site-title=page.title || page.tag || page.category
else else
//- improvement seo //- improve seo
if !is_post() if globalPageType !== 'post'
h1.title-seo=page.title || page.tag || page.category || config.title h1.title-seo=page.title || page.tag || page.category || config.title

View File

@@ -5,9 +5,13 @@ nav#nav
img.site-icon(src=url_for(theme.nav.logo) alt='Logo') img.site-icon(src=url_for(theme.nav.logo) alt='Logo')
if theme.nav.display_title if theme.nav.display_title
span.site-name=config.title span.site-name=config.title
if is_post() if globalPageType === 'post' && theme.nav.display_post_title
a.nav-page-title(href=url_for('/')) a.nav-page-title(href=url_for('/'))
span.site-name=(page.title || config.title) span.site-name=(page.title || config.title)
span.site-name
i.fa-solid.fa-circle-arrow-left
span= ' ' + _p('post.back_to_home')
#menus #menus
if theme.search.use if theme.search.use
#search-button #search-button

View File

@@ -1,7 +1,8 @@
- var globalPageType = getPageType(page, is_home)
- var htmlClassHideAside = theme.aside.enable && theme.aside.hide ? 'hide-aside' : '' - var htmlClassHideAside = theme.aside.enable && theme.aside.hide ? 'hide-aside' : ''
- page.aside = is_archive() ? theme.aside.display.archive: is_category() ? theme.aside.display.category : is_tag() ? theme.aside.display.tag : page.aside - page.aside = globalPageType === 'archive' ? theme.aside.display.archive: globalPageType === 'category' ? theme.aside.display.category : globalPageType === 'tag' ? theme.aside.display.tag : page.aside
- var hideAside = !theme.aside.enable || page.aside === false ? 'hide-aside' : '' - var hideAside = !theme.aside.enable || page.aside === false ? 'hide-aside' : ''
- var pageType = is_post() ? 'post' : 'page' - var pageType = globalPageType === 'post' ? 'post' : 'page'
- pageType = page.type ? pageType + ' type-' + page.type : pageType - pageType = page.type ? pageType + ' type-' + page.type : pageType
doctype html doctype html

View File

@@ -14,6 +14,7 @@ script.
const $body = document.body const $body = document.body
const preloader = { const preloader = {
endLoading: () => { endLoading: () => {
if ($loadingBox.classList.contains('loaded')) return
$body.style.overflow = '' $body.style.overflow = ''
$loadingBox.classList.add('loaded') $loadingBox.classList.add('loaded')
}, },
@@ -24,7 +25,15 @@ script.
} }
preloader.initLoading() preloader.initLoading()
if (document.readyState === 'complete') {
preloader.endLoading()
} else {
window.addEventListener('load', preloader.endLoading) window.addEventListener('load', preloader.endLoading)
document.addEventListener('DOMContentLoaded', preloader.endLoading)
// Add timeout protection: force end after 7 seconds
setTimeout(preloader.endLoading, 7000)
}
if (!{theme.pjax && theme.pjax.enable}) { if (!{theme.pjax && theme.pjax.enable}) {
btf.addGlobalFn('pjaxSend', preloader.initLoading, 'preloader_init') btf.addGlobalFn('pjaxSend', preloader.initLoading, 'preloader_init')

View File

@@ -20,7 +20,7 @@ mixin indexPostUI()
div.post-bg(style=`background: ${post_cover}`) div.post-bg(style=`background: ${post_cover}`)
.recent-post-info(class=no_cover) .recent-post-info(class=no_cover)
a.article-title(href=url_for(link) title=title) a.article-title(href=url_for(link) title=title)
if is_home() && (article.top || article.sticky > 0) if globalPageType === 'home' && (article.top || article.sticky > 0)
i.fas.fa-thumbtack.sticky i.fas.fa-thumbtack.sticky
= title = title
.article-meta-wrap .article-meta-wrap

View File

@@ -1,2 +1,2 @@
#article-container #article-container.container
!= page.content != page.content

View File

@@ -1,4 +1,4 @@
#article-container #article-container.container
.flink .flink
- let { content, random, flink_url } = page - let { content, random, flink_url } = page
- let pageContent = content - let pageContent = content

View File

@@ -9,6 +9,7 @@
- page.toc = false - page.toc = false
#article-container #article-container
if page.shuoshuo_url || (site.data.shuoshuo && site.data.shuoshuo.length)
if page.comments !== false && theme.comments.use if page.comments !== false && theme.comments.use
- commentsJsLoad = true - commentsJsLoad = true
@@ -28,7 +29,7 @@
const btn = e.target.closest('.shuoshuo-comment-btn') const btn = e.target.closest('.shuoshuo-comment-btn')
if (!btn) return if (!btn) return
const ele = btn.parentNode.nextElementSibling const ele = btn.closest('.container').nextElementSibling
const { shuoshuoComment } = window const { shuoshuoComment } = window
const isInclude = ele.classList.contains('no-comment') const isInclude = ele.classList.contains('no-comment')
runDestroy(shuoshuoComment) runDestroy(shuoshuoComment)
@@ -42,7 +43,13 @@
} }
})() })()
if page.shuoshuo_url
- const localDate = page.shuoshuo_url ? [] : shuoshuoFN(site.data.shuoshuo, page)
if !page.shuoshuo_url
script(type='application/json' id='shuoshuo-data')!= safeJSON(localDate)
- const { enable, native, placeholder, field } = theme.lazyload
script. script.
(() => { (() => {
const limitConfig = !{ JSON.stringify(page.limit || {}) } const limitConfig = !{ JSON.stringify(page.limit || {}) }
@@ -78,15 +85,41 @@
return `${year}-${month}-${day} ${hour}:${minute}:${second}` return `${year}-${month}-${day} ${hour}:${minute}:${second}`
} }
const loadShuoshuo = async () => { const addLazyload = str => {
try { const config = {
const response = await fetch('!{url_for(page.shuoshuo_url)}') enable: !{Boolean(enable)},
let data = await response.json() native: !{Boolean(native)},
field: '!{field}',
placeholder: '!{url_for(placeholder)}',
}
data = filterDataByLimit(sortDataByDate(data), limitConfig) if (!config.enable || config.field !== 'site') return str
const parser = new DOMParser()
const doc = parser.parseFromString(str, 'text/html')
const images = doc.querySelectorAll('img')
const container = document.getElementById('article-container') images.forEach(img => {
let start = 0 if (config.native) {
img.setAttribute('loading', 'lazy')
} else {
const src = img.getAttribute('src')
img.setAttribute('data-lazy-src', src)
if (config.placeholder) {
img.setAttribute('src', config.placeholder)
} else {
img.removeAttribute('src')
}
}
})
return doc.body.innerHTML
}
let currentPage = 1
const itemsPerPage = 8
let totalPages = 0
let data = []
let inputEventsAttached = false // Flag to mark if input event listeners have been added
const renderData = (dataSlice) => { const renderData = (dataSlice) => {
const content = dataSlice.map(item => { const content = dataSlice.map(item => {
@@ -103,6 +136,7 @@
return ` return `
<div class="shuoshuo-item"> <div class="shuoshuo-item">
<div class="container">
<div class="shuoshuo-item-header"> <div class="shuoshuo-item-header">
<div class="shuoshuo-avatar"> <div class="shuoshuo-avatar">
<img class="no-lightbox" src="${item.avatar || '!{url_for(theme.avatar.img)}'}"> <img class="no-lightbox" src="${item.avatar || '!{url_for(theme.avatar.img)}'}">
@@ -114,46 +148,181 @@
</time> </time>
</div> </div>
</div> </div>
<div class="shuoshuo-content">${item.content}</div> <div class="shuoshuo-content">${addLazyload(item.content)}</div>
<div class="shuoshuo-footer ${tags ? 'flex-between' : 'flex-end'}"> <div class="shuoshuo-footer ${tags ? 'flex-between' : 'flex-end'}">
${tags ? `<div class="shuoshuo-tags">${tags}</div>` : ''} ${tags ? `<div class="shuoshuo-tags">${tags}</div>` : ''}
${commentButton} ${commentButton}
</div> </div>
</div>
${commentContainer} ${commentContainer}
</div>` </div>`
}).join('') }).join('')
container.insertAdjacentHTML('beforeend', content) const container = document.getElementById('article-container')
container.innerHTML = content
window.lazyLoadInstance.update() window.lazyLoadInstance && window.lazyLoadInstance.update()
btf.loadLightbox(document.querySelectorAll('#article-container img:not(.no-lightbox)')) btf.loadLightbox(document.querySelectorAll('#article-container img:not(.no-lightbox)'))
} }
const handleIntersection = (entries) => { const renderNavigation = () => {
if (!entries[0].isIntersecting) return const container = document.getElementById('article-container')
observer.unobserve(entries[0].target) const existingNav = container.nextElementSibling
if (existingNav && existingNav.classList.contains('shuoshuo-navigation')) {
const slice = data.slice(start, start + 10) existingNav.remove()
renderData(slice)
start += 10
if (start < data.length) {
setTimeout(() => observer.observe(container.lastElementChild), 100)
} else {
observer.disconnect()
} }
};
const observer = new IntersectionObserver(handleIntersection, { const pageInfoTemplate = '#{__('pagination.page_info')}'
root: null, const pageInfoText = pageInfoTemplate
rootMargin: '0px', .replace(/\$\{current}/g, currentPage)
threshold: 1.0 .replace(/\$\{total}/g, totalPages)
const navHtml = `
<div class="shuoshuo-navigation">
<button onclick="window.shuoshuoPrevPage()" ${currentPage === 1 ? 'disabled' : ''}><i class="fa-solid fa-chevron-left"></i></button>
<span class="shuoshuo-page-info">${pageInfoText}</span>
<input type="number" class="shuoshuo-page-input" min="1" max="${totalPages}" placeholder="${currentPage}" onkeydown="window.shuoshuoHandleKeyDown(event)">
<button onclick="window.shuoshuoNextPage()" ${currentPage === totalPages ? 'disabled' : ''}><i class="fa-solid fa-chevron-right"></i></button>
</div>
`
container.insertAdjacentHTML('afterend', navHtml)
// Add input validation event listeners (only once)
if (!inputEventsAttached) {
setTimeout(() => {
const input = document.querySelector('.shuoshuo-page-input')
if (input) {
// Clear placeholder when clicking the input box
input.addEventListener('focus', (event) => {
event.target.placeholder = ''
}) })
renderData(data.slice(start, 10)) // Restore placeholder if no content when losing focus
start += 10 input.addEventListener('blur', (event) => {
if (!event.target.value.trim()) {
event.target.placeholder = currentPage
}
})
if (container.lastElementChild) observer.observe(container.lastElementChild) input.addEventListener('input', (event) => {
const value = parseInt(event.target.value) || 0
let wasInvalid = false
if (value > totalPages) {
event.target.value = totalPages
wasInvalid = true
} else if (value < 1 && event.target.value !== '') {
event.target.value = 1
wasInvalid = true
}
// If value is corrected, show red and shake effect
if (wasInvalid) {
event.target.classList.add('invalid')
setTimeout(() => {
event.target.classList.remove('invalid')
}, 500)
}
})
inputEventsAttached = true // Mark that event listeners have been added
}
}, 0)
}
}
const renderPage = (page) => {
const start = (page - 1) * itemsPerPage
const end = start + itemsPerPage
const pageData = data.slice(start, end)
renderData(pageData)
renderNavigation()
}
window.shuoshuoPrevPage = () => {
if (currentPage > 1) {
currentPage--
renderPage(currentPage)
}
}
window.shuoshuoNextPage = () => {
if (currentPage < totalPages) {
currentPage++
renderPage(currentPage)
}
}
window.shuoshuoGoToPage = (page) => {
if (typeof page === 'number') {
// Directly jump to the specified page
if (page >= 1 && page <= totalPages && page !== currentPage) {
currentPage = page
renderPage(currentPage)
}
} else {
// Get page from input box
const input = document.querySelector('.shuoshuo-page-input')
const inputValue = input.value.trim()
const inputPage = inputValue === '' ? currentPage : parseInt(inputValue)
if (inputPage >= 1 && inputPage <= totalPages && inputPage !== currentPage) {
currentPage = inputPage
renderPage(currentPage)
} else if (inputValue === '') {
// If input box is empty, re-render current page (update placeholder)
renderPage(currentPage)
}
}
}
window.shuoshuoHandleKeyDown = (event) => {
const input = event.target
const value = input.value + event.key
// Allow delete, arrow keys, backspace, etc.
if (event.key === 'Enter' || event.key === 'Backspace' || event.key === 'Delete' ||
event.key === 'ArrowLeft' || event.key === 'ArrowRight' ||
event.key === 'Tab' || event.ctrlKey || event.metaKey) {
if (event.key === 'Enter') {
window.shuoshuoGoToPage()
}
return
}
// Only allow numbers
if (!/^\d$/.test(event.key)) {
event.preventDefault()
return
}
// Check if the value after input exceeds the range
const newValue = parseInt(value) || 0
if (newValue > totalPages || (value.length > 1 && newValue === 0)) {
event.preventDefault()
// Add red and shake effect
input.classList.add('invalid')
setTimeout(() => {
input.classList.remove('invalid')
}, 500)
}
}
const loadShuoshuo = async () => {
try {
let originData = []
if (!{Boolean(page.shuoshuo_url)}) {
const response = await fetch('!{url_for(page.shuoshuo_url)}')
originData = await response.json()
} else {
const dataElement = document.getElementById('shuoshuo-data')
originData = dataElement ? JSON.parse(dataElement.textContent) : []
}
data = filterDataByLimit(sortDataByDate(originData), limitConfig)
totalPages = Math.ceil(data.length / itemsPerPage)
renderPage(currentPage)
} catch (error) { } catch (error) {
console.error(error) console.error(error)
} }
@@ -161,25 +330,3 @@
window.pjax ? loadShuoshuo() : window.addEventListener('load', loadShuoshuo) window.pjax ? loadShuoshuo() : window.addEventListener('load', loadShuoshuo)
})() })()
else
if site.data.shuoshuo
each i in shuoshuoFN(site.data.shuoshuo, page)
.shuoshuo-item
.shuoshuo-item-header
.shuoshuo-avatar
img.no-lightbox(src=i.avatar || url_for(theme.avatar.img))
.shuoshuo-info
.shuoshuo-author=i.author || config.author
time.shuoshuo-date(title=i.date)=i.date
.shuoshuo-content
!=markdown(i.content)
.shuoshuo-footer(class=i.tags && i.tags.length ? 'flex-between' : 'flex-end')
if i.tags
.shuoshuo-tags
each tag in i.tags
span.shuoshuo-tag=tag
if i.key && commentsJsLoad
.shuoshuo-comment-btn(onclick='addCommentToShuoshuo(event)')
i.fa-solid.fa-comments
if i.key && commentsJsLoad
.shuoshuo-comment.no-comment(data-key=i.key)

View File

@@ -1,3 +1,4 @@
if page.total !== 1
- -
var options = { var options = {
prev_text: '<i class="fas fa-chevron-left fa-fw"></i>', prev_text: '<i class="fas fa-chevron-left fa-fw"></i>',
@@ -6,8 +7,8 @@
escape: false escape: false
} }
if is_post() if globalPageType === 'post'
- let paginationOrder = theme.post_pagination === 1 ? { prev: page.prev, next: page.next } : { prev: page.next, next: page.prev } - let paginationOrder = theme.post_pagination === 2 ? { prev: page.prev, next: page.next } : { prev: page.next, next: page.prev }
nav#pagination.pagination-post nav#pagination.pagination-post
each direction, key in paginationOrder each direction, key in paginationOrder
@@ -32,6 +33,6 @@ if is_post()
else else
nav#pagination nav#pagination
.pagination .pagination
if is_home() if globalPageType === 'home'
- options.format = 'page/%d/#content-inner' - options.format = 'page/%d/#content-inner'
!=paginator(options) !=paginator(options)

View File

@@ -0,0 +1,8 @@
- const { limit_day, message_prev, message_next, position} = theme.noticeOutdate
- const notice_data = { limitDay: limit_day, messagePrev: message_prev, messageNext: message_next, postUpdate: full_date(page.updated)}
if position === 'top'
#post-outdate-notice(data=notice_data hidden)
!=page.content
else
!=page.content
#post-outdate-notice(data=notice_data hidden)

View File

@@ -1,9 +1,10 @@
- const { readmode, translate, darkmode, aside, chat } = theme - const { readmode, translate, darkmode, aside, chat } = theme
mixin rightsideItem(array) mixin rightsideItem(array)
each item in array each item in array
case item case item
when 'readmode' when 'readmode'
if is_post() && readmode if globalPageType === 'post' && readmode
button#readmode(type="button" title=_p('rightside.readmode_title')) button#readmode(type="button" title=_p('rightside.readmode_title'))
i.fas.fa-book-open i.fas.fa-book-open
when 'translate' when 'translate'
@@ -23,37 +24,29 @@ mixin rightsideItem(array)
i.fas.fa-list-ul i.fas.fa-list-ul
when 'chat' when 'chat'
if chat.rightside_button && chat.use if chat.rightside_button && chat.use
button#chat-btn(type="button" title=_p("rightside.chat")) button#chat-btn(type="button" title=_p("rightside.chat") style="display:none")
i.fas.fa-message i.fas.fa-message
when 'comment' when 'comment'
if commentsJsLoad if commentsJsLoad
a#to_comment(href="#post-comment" title=_p("rightside.scroll_to_comment")) a#to_comment(href="#post-comment" title=_p("rightside.scroll_to_comment"))
i.fas.fa-comments i.fas.fa-comments
#rightside
- const { enable, hide, show } = theme.rightside_item_order - const { enable, hide, show } = theme.rightside_item_order
- const hideArray = enable ? hide && hide.split(',') : ['readmode','translate','darkmode','hideAside'] - const hideArray = enable && hide ? hide.split(',') : ['readmode','translate','darkmode','hideAside']
- const showArray = enable ? show && show.split(',') : ['toc','chat','comment'] - const showArray = enable && show ? show.split(',') : ['toc','chat','comment']
- const needCogBtn = (enable && hide) || (!enable && ((globalPageType === 'post' && (readmode || translate.enable || (darkmode.enable && darkmode.button))) || (translate.enable || (darkmode.enable && darkmode.button))))
#rightside
#rightside-config-hide #rightside-config-hide
if hideArray if hideArray.length
+rightsideItem(hideArray) +rightsideItem(hideArray)
#rightside-config-show
if enable
if hide
button#rightside-config(type="button" title=_p("rightside.setting"))
i.fas.fa-cog.fa-spin
else
if is_post()
if (readmode || translate.enable || (darkmode.enable && darkmode.button))
button#rightside-config(type="button" title=_p("rightside.setting"))
i.fas.fa-cog.fa-spin
else if translate.enable || (darkmode.enable && darkmode.button)
button#rightside-config(type="button" title=_p("rightside.setting"))
i.fas.fa-cog.fa-spin
if showArray #rightside-config-show
if needCogBtn
button#rightside-config(type="button" title=_p("rightside.setting"))
i.fas.fa-cog(class=theme.rightside_config_animation ? 'fa-spin' : '')
if showArray.length
+rightsideItem(showArray) +rightsideItem(showArray)
button#go-up(type="button" title=_p("rightside.back_to_top")) button#go-up(type="button" title=_p("rightside.back_to_top"))

View File

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

View File

@@ -1,17 +1,46 @@
script. script.
(() => { (() => {
const abcjsInit = () => { const abcjsInit = () => {
const abcjsFn = () => setTimeout(() => { const abcjsFn = () => {
document.querySelectorAll(".abc-music-sheet").forEach(ele => { setTimeout(() => {
if (ele.children.length > 0) return const sheets = document.querySelectorAll(".abc-music-sheet")
ABCJS.renderAbc(ele, ele.innerHTML, {responsive: 'resize'}) for (let i = 0; i < sheets.length; i++) {
}) const ele = sheets[i]
}, 100) if (ele.children.length > 0) continue
typeof ABCJS === 'object' ? abcjsFn() // Parse parameters from data-params attribute
: btf.getScript('!{url_for(theme.asset.abcjs_basic_js)}').then(abcjsFn) let params = {}
const dp = ele.getAttribute("data-params")
if (dp) {
try {
params = JSON.parse(dp)
} catch (e) {
console.error("Failed to parse data-params:", e)
}
} }
window.pjax ? abcjsInit() : window.addEventListener('load', abcjsInit) // Merge parsed parameters with the responsive option
btf.addGlobalFn('encrypt', abcjsInit, 'abcjs') // Ensures params content appears before responsive
const options = { ...params, responsive: "resize" }
// Render the music score using ABCJS.renderAbc
ABCJS.renderAbc(ele, ele.innerHTML, options)
}
}, 100)
}
if (typeof ABCJS === "object") {
abcjsFn()
} else {
btf.getScript("!{url_for(theme.asset.abcjs_basic_js)}").then(abcjsFn)
}
}
if (window.pjax) {
abcjsInit()
} else {
window.addEventListener("load", abcjsInit)
}
btf.addGlobalFn("encrypt", abcjsInit, "abcjs")
})() })()

View File

@@ -1,6 +1,3 @@
if theme.abcjs.enable if theme.abcjs.enable
if theme.abcjs.per_page if theme.abcjs.per_page && (['post','page'].includes(globalPageType)) || page.abcjs
if is_post() || is_page()
include ./abcjs.pug
else if page.abcjs
include ./abcjs.pug include ./abcjs.pug

View File

@@ -18,7 +18,9 @@ script.
includeReply: false includeReply: false
}).then(function (res) { }).then(function (res) {
document.querySelectorAll('#recent-posts .twikoo-count').forEach((item,index) => { document.querySelectorAll('#recent-posts .twikoo-count').forEach((item,index) => {
if (res[index]) {
item.textContent = res[index].count item.textContent = res[index].count
}
}) })
}).catch(function (err) { }).catch(function (err) {
console.log(err) console.log(err)

View File

@@ -1,6 +1,12 @@
//- https://chatra.io/help/api/ //- https://chatra.io/help/api/
script. script.
(() => { (() => {
window.ChatraID = '#{theme.chatra.id}'
window.Chatra = window.Chatra || function() {
(window.Chatra.q = window.Chatra.q || []).push(arguments)
}
btf.getScript('https://call.chatra.io/chatra.js').then(() => {
const isChatBtn = !{theme.chat.rightside_button} const isChatBtn = !{theme.chat.rightside_button}
const isChatHideShow = !{theme.chat.button_hide_show} const isChatHideShow = !{theme.chat.button_hide_show}
@@ -17,26 +23,16 @@ script.
window.ChatraSetup = { startHidden: true } window.ChatraSetup = { startHidden: true }
window.chatBtnFn = () => { window.chatBtnFn = () => document.getElementById('chatra').classList.contains('chatra--expanded') ? close() : open()
document.getElementById('chatra').classList.contains('chatra--expanded') ? close() : open()
} document.getElementById('chat-btn').style.display = 'block'
} else if (isChatHideShow) { } else if (isChatHideShow) {
window.chatBtn = { window.chatBtn = {
hide: () => Chatra('hide'), hide: () => Chatra('hide'),
show: () => Chatra('show') show: () => Chatra('show')
} }
} }
})
(function(d, w, c) {
w.ChatraID = '#{theme.chatra.id}'
var s = d.createElement('script')
w[c] = w[c] || function() {
(w[c].q = w[c].q || []).push(arguments)
}
s.async = true
s.src = 'https://call.chatra.io/chatra.js'
if (d.head) d.head.appendChild(s)
})(document, window, 'Chatra')
})() })()

View File

@@ -1,16 +1,9 @@
script. script.
(() => { (() => {
window.$crisp = []; window.$crisp = ['safe', true]
window.CRISP_WEBSITE_ID = "!{theme.crisp.website_id}"; window.CRISP_WEBSITE_ID = "!{theme.crisp.website_id}"
(function () {
d = document;
s = d.createElement("script");
s.src = "https://client.crisp.chat/l.js";
s.async = 1;
d.getElementsByTagName("head")[0].appendChild(s);
})();
$crisp.push(["safe", true])
btf.getScript('https://client.crisp.chat/l.js').then(() => {
const isChatBtn = !{theme.chat.rightside_button} const isChatBtn = !{theme.chat.rightside_button}
const isChatHideShow = !{theme.chat.button_hide_show} const isChatHideShow = !{theme.chat.button_hide_show}
@@ -28,10 +21,12 @@ script.
window.chatBtnFn = () => $crisp.is("chat:visible") ? close() : open() window.chatBtnFn = () => $crisp.is("chat:visible") ? close() : open()
document.getElementById('chat-btn').style.display = 'block'
} else if (isChatHideShow) { } else if (isChatHideShow) {
window.chatBtn = { window.chatBtn = {
hide: () => $crisp.push(["do", "chat:hide"]), hide: () => $crisp.push(["do", "chat:hide"]),
show: () => $crisp.push(["do", "chat:show"]) show: () => $crisp.push(["do", "chat:show"])
} }
} }
})
})() })()

View File

@@ -1,6 +1,6 @@
script(src=`//code.tidio.co/${theme.tidio.public_key}.js` async)
script. script.
(() => { (() => {
btf.getScript('//code.tidio.co/!{theme.tidio.public_key}.js').then(() => {
const isChatBtn = !{theme.chat.rightside_button} const isChatBtn = !{theme.chat.rightside_button}
const isChatHideShow = !{theme.chat.button_hide_show} const isChatHideShow = !{theme.chat.button_hide_show}
@@ -31,11 +31,15 @@ script.
if (!window.tidioChatApi) return if (!window.tidioChatApi) return
isShow ? close() : open() isShow ? close() : open()
} }
document.getElementById('chat-btn').style.display = 'block'
} else if (isChatHideShow) { } else if (isChatHideShow) {
window.chatBtn = { window.chatBtn = {
hide: () => window.tidioChatApi && window.tidioChatApi.hide(), hide: () => window.tidioChatApi && window.tidioChatApi.hide(),
show: () => window.tidioChatApi && window.tidioChatApi.show() show: () => window.tidioChatApi && window.tidioChatApi.show()
} }
} }
})
})() })()

View File

@@ -5,7 +5,7 @@ script.
(() => { (() => {
let artalkItem = null let artalkItem = null
const option = !{JSON.stringify(option)} const option = !{JSON.stringify(option)}
const isShuoshuo = GLOBAL_CONFIG_SITE.isShuoshuo const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'
const destroyArtalk = () => { const destroyArtalk = () => {
if (artalkItem) { if (artalkItem) {
@@ -51,8 +51,8 @@ script.
const loadArtalk = async (el, pageKey) => { const loadArtalk = async (el, pageKey) => {
if (typeof Artalk === 'object') initArtalk(el, pageKey) if (typeof Artalk === 'object') initArtalk(el, pageKey)
else { else {
await btf.getCSS('!{theme.asset.artalk_css}') await btf.getCSS('!{url_for(theme.asset.artalk_css)}')
await btf.getScript('!{theme.asset.artalk_js}') await btf.getScript('!{url_for(theme.asset.artalk_js)}')
initArtalk(el, pageKey) initArtalk(el, pageKey)
} }
} }

View File

@@ -4,7 +4,7 @@
script. script.
(() => { (() => {
const isShuoshuo = GLOBAL_CONFIG_SITE.isShuoshuo const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'
const disqusReset = conf => { const disqusReset = conf => {
window.DISQUS && window.DISQUS.reset({ window.DISQUS && window.DISQUS.reset({
@@ -72,7 +72,7 @@ script.
if (!{lazyload}) btf.loadComment(document.getElementById('disqus_thread'), loadDisqus) if (!{lazyload}) btf.loadComment(document.getElementById('disqus_thread'), loadDisqus)
else { else {
loadDisqus() loadDisqus()
!{ count ? 'GLOBAL_CONFIG_SITE.isPost && getCount()' : '' } !{ count ? `GLOBAL_CONFIG_SITE.pageType === 'post' && getCount()` : '' }
} }
} else { } else {
window.loadOtherComment = loadDisqus window.loadOtherComment = loadDisqus

View File

@@ -3,7 +3,7 @@
script. script.
(() => { (() => {
const isShuoshuo = GLOBAL_CONFIG_SITE.isShuoshuo const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'== 'shuoshuo'
const dqOption = !{JSON.stringify(dqOption)} const dqOption = !{JSON.stringify(dqOption)}
const destroyDisqusjs = () => { const destroyDisqusjs = () => {
@@ -79,7 +79,7 @@ script.
if (!{theme.comments.lazyload}) btf.loadComment(document.getElementById('disqusjs-wrap'), loadDisqusjs) if (!{theme.comments.lazyload}) btf.loadComment(document.getElementById('disqusjs-wrap'), loadDisqusjs)
else { else {
loadDisqusjs() loadDisqusjs()
!{ theme.comments.count ? 'GLOBAL_CONFIG_SITE.isPost && getCount()' : '' } !{ theme.comments.count ? `GLOBAL_CONFIG_SITE.pageType === 'post' && getCount()` : '' }
} }
} else { } else {
window.loadOtherComment = loadDisqusjs window.loadOtherComment = loadDisqusjs

View File

@@ -3,7 +3,7 @@
script. script.
(()=>{ (()=>{
const isShuoshuo = GLOBAL_CONFIG_SITE.isShuoshuo const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'== 'shuoshuo'
const loadFBComment = (el = document, path) => { const loadFBComment = (el = document, path) => {
if (isShuoshuo) { if (isShuoshuo) {

View File

@@ -5,7 +5,7 @@
script. script.
(() => { (() => {
const isShuoshuo = GLOBAL_CONFIG_SITE.isShuoshuo const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'
const option = !{JSON.stringify(option)} const option = !{JSON.stringify(option)}
const getGiscusTheme = theme => theme === 'dark' ? '!{dark_theme}' : '!{light_theme}' const getGiscusTheme = theme => theme === 'dark' ? '!{dark_theme}' : '!{light_theme}'

View File

@@ -2,7 +2,7 @@
script. script.
(() => { (() => {
const isShuoshuo = GLOBAL_CONFIG_SITE.isShuoshuo const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'
const option = !{JSON.stringify(option)} const option = !{JSON.stringify(option)}
const commentCount = n => { const commentCount = n => {

View File

@@ -2,7 +2,7 @@
script. script.
(() => { (() => {
const isShuoshuo = GLOBAL_CONFIG_SITE.isShuoshuo const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'
const loadLivere = (el, path) => { const loadLivere = (el, path) => {
window.livereOptions = { window.livereOptions = {

View File

@@ -2,8 +2,8 @@
script. script.
(() => { (() => {
const isShuoshuo = GLOBAL_CONFIG_SITE.isShuoshuo const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'
const option = !{JSON.stringify(option)} const options = !{JSON.stringify(option)}
const loadScript = src => { const loadScript = src => {
const script = document.createElement('script') const script = document.createElement('script')
@@ -42,8 +42,8 @@ script.
host: '!{host}', host: '!{host}',
site_id: '!{siteId}', site_id: '!{siteId}',
theme: document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'light', theme: document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'light',
...option, ...options,
url: isShuoshuo ? window.location.origin + path : (option && option.url) || window.location.origin + window.location.pathname url: isShuoshuo ? window.location.origin + path : (options && options.url) || window.location.origin + window.location.pathname
} }
if (window.REMARK42) { if (window.REMARK42) {

View File

@@ -3,7 +3,7 @@
script. script.
(() => { (() => {
const isShuoshuo = GLOBAL_CONFIG_SITE.isShuoshuo const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'
const option = !{JSON.stringify(option)} const option = !{JSON.stringify(option)}
const getCount = () => { const getCount = () => {
@@ -33,7 +33,7 @@ script.
path: isShuoshuo ? path : (option && option.path) || path path: isShuoshuo ? path : (option && option.path) || path
}) })
!{count ? 'GLOBAL_CONFIG_SITE.isPost && getCount()' : ''} !{count ? `GLOBAL_CONFIG_SITE.pageType === 'post' && getCount()` : ''}
isShuoshuo && (window.shuoshuoComment.destroyTwikoo = () => { isShuoshuo && (window.shuoshuoComment.destroyTwikoo = () => {
if (el.children.length) { if (el.children.length) {

View File

@@ -5,7 +5,7 @@
script. script.
(() => { (() => {
const isShuoshuo = GLOBAL_CONFIG_SITE.isShuoshuo const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'
const option = !{JSON.stringify(option)} const option = !{JSON.stringify(option)}
const getUtterancesTheme = theme => theme === 'dark' ? '#{dark_theme}' : '#{light_theme}' const getUtterancesTheme = theme => theme === 'dark' ? '#{dark_theme}' : '#{light_theme}'

View File

@@ -7,7 +7,7 @@ if site.data.valine
script. script.
(() => { (() => {
const isShuoshuo = GLOBAL_CONFIG_SITE.isShuoshuo const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'
const option = !{JSON.stringify(option)} const option = !{JSON.stringify(option)}
const initValine = (el, path) => { const initValine = (el, path) => {

View File

@@ -4,7 +4,7 @@
script. script.
(() => { (() => {
let initFn = window.walineFn || null let initFn = window.walineFn || null
const isShuoshuo = GLOBAL_CONFIG_SITE.isShuoshuo const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'
const option = !{JSON.stringify(option)} const option = !{JSON.stringify(option)}
const destroyWaline = ele => ele.destroy() const destroyWaline = ele => ele.destroy()

View File

@@ -2,9 +2,6 @@
script. script.
(() => { (() => {
const $chartjs = document.querySelectorAll('#article-container .chartjs-container')
if ($chartjs.length === 0) return
const applyThemeDefaultsConfig = theme => { const applyThemeDefaultsConfig = theme => {
if (theme === 'dark-mode') { if (theme === 'dark-mode') {
Chart.defaults.color = "!{fontColor.dark}" Chart.defaults.color = "!{fontColor.dark}"
@@ -35,10 +32,10 @@ script.
}) })
} }
const runChartJS = () => { const runChartJS = ele => {
window.loadChartJS = true window.loadChartJS = true
Array.from($chartjs).forEach((item, index) => { Array.from(ele).forEach((item, index) => {
const chartSrc = item.firstElementChild const chartSrc = item.firstElementChild
const chartID = item.getAttribute('data-chartjs-id') || ('chartjs-' + index) // Use custom ID or default ID const chartID = item.getAttribute('data-chartjs-id') || ('chartjs-' + index) // Use custom ID or default ID
const width = item.getAttribute('data-width') const width = item.getAttribute('data-width')
@@ -80,12 +77,15 @@ script.
} }
const loadChartJS = () => { const loadChartJS = () => {
window.loadChartJS ? runChartJS() : btf.getScript('!{url_for(theme.asset.chartjs)}').then(runChartJS) const chartJSEle = document.querySelectorAll('#article-container .chartjs-container')
if (chartJSEle.length === 0) return
window.loadChartJS ? runChartJS(chartJSEle) : btf.getScript('!{url_for(theme.asset.chartjs)}').then(() => runChartJS(chartJSEle))
} }
// Listen for theme change events // Listen for theme change events
btf.addGlobalFn('themeChange', runChartJS, 'chartjs') btf.addGlobalFn('themeChange', loadChartJS, 'chartjs')
btf.addGlobalFn('encrypt', runChartJS, 'chartjs') btf.addGlobalFn('encrypt', loadChartJS, 'chartjs')
window.pjax ? loadChartJS() : document.addEventListener('DOMContentLoaded', loadChartJS) window.pjax ? loadChartJS() : document.addEventListener('DOMContentLoaded', loadChartJS)
})() })()

View File

@@ -1,10 +1,10 @@
case theme.math.use case theme.math.use
when 'mathjax' when 'mathjax'
if (theme.math.per_page && (is_post() || is_page())) || page.mathjax if (theme.math.per_page && (['post','page'].includes(globalPageType))) || page.mathjax
include ./mathjax.pug include ./mathjax.pug
when 'katex' when 'katex'
if (theme.math.per_page && (is_post() || is_page())) || page.katex if (theme.math.per_page && (['post','page'].includes(globalPageType))) || page.katex
include ./katex.pug include ./katex.pug
if theme.mermaid.enable if theme.mermaid.enable

View File

@@ -1,19 +1,50 @@
//- Mathjax 3 //- Mathjax 4/5
- const { tags, enableMenu } = theme.math.mathjax - const { tags, enableMenu } = theme.math.mathjax
script. script.
(() => { (() => {
const loadMathjax = () => { const loadMathjax = () => {
if (!window.MathJax) { if (!window.MathJax) {
window.MathJax = { window.MathJax = {
loader: {
load: [
// Four font extension packages (optional)
//- '[tex]/bbm',
//- '[tex]/bboldx',
//- '[tex]/dsfont',
'[tex]/mhchem'
],
paths: {
'mathjax-newcm': '[mathjax]/../@mathjax/mathjax-newcm-font',
//- // Four font extension packages (optional)
//- 'mathjax-bbm-extension': '[mathjax]/../@mathjax/mathjax-bbm-font-extension',
//- 'mathjax-bboldx-extension': '[mathjax]/../@mathjax/mathjax-bboldx-font-extension',
//- 'mathjax-dsfont-extension': '[mathjax]/../@mathjax/mathjax-dsfont-font-extension',
'mathjax-mhchem-extension': '[mathjax]/../@mathjax/mathjax-mhchem-font-extension'
}
},
output: {
font: 'mathjax-newcm',
},
tex: { tex: {
inlineMath: [['$', '$'], ['\\(', '\\)']], inlineMath: [['$', '$'], ['\\(', '\\)']],
tags: '!{tags}', tags: '!{tags}',
packages: {
'[+]': [
'mhchem'
]
}
}, },
chtml: { chtml: {
scale: 1.1 scale: 1.1
}, },
options: { options: {
enableMenu: !{enableMenu}, enableMenu: !{enableMenu},
menuOptions: {
settings: {
enrich: false // Turn off Braille and voice narration text automatic generation
}
},
renderActions: { renderActions: {
findScript: [10, doc => { findScript: [10, doc => {
for (const node of document.querySelectorAll('script[type^="math/tex"]')) { for (const node of document.querySelectorAll('script[type^="math/tex"]')) {

View File

@@ -6,7 +6,11 @@ script.
ele.forEach((item, index) => { ele.forEach((item, index) => {
const mermaidSrc = item.firstElementChild const mermaidSrc = item.firstElementChild
const mermaidThemeConfig = `%%{init:{ 'theme':'${theme}'}}%%\n` const config = mermaidSrc.dataset.config ? JSON.parse(mermaidSrc.dataset.config) : {}
if (!config.theme) {
config.theme = theme
}
const mermaidThemeConfig = `%%{init: ${JSON.stringify(config)}}%%\n`
const mermaidID = `mermaid-${index}` const mermaidID = `mermaid-${index}`
const mermaidDefinition = mermaidThemeConfig + mermaidSrc.textContent const mermaidDefinition = mermaidThemeConfig + mermaidSrc.textContent

View File

@@ -34,7 +34,7 @@ script.
const searchParams = new URLSearchParams({ const searchParams = new URLSearchParams({
'site_name': '!{site}', 'site_name': '!{site}',
'limit': '!{theme.aside.card_newest_comments.limit}', 'limit': '!{newestCommentsLimit * 2}', // Fetch more comments to filter pending comments
}) })
const getComment = async (ele) => { const getComment = async (ele) => {
@@ -42,7 +42,10 @@ script.
const res = await fetch(`!{server}/api/v2/stats/latest_comments?${searchParams}`) const res = await fetch(`!{server}/api/v2/stats/latest_comments?${searchParams}`)
const result = await res.json() const result = await res.json()
const { avatarCdn, avatarDefault } = await getAvatarValue() const { avatarCdn, avatarDefault } = await getAvatarValue()
const artalk = result.data.map(e => { const artalk = result.data
.filter(e => !e.is_pending) // Filter pending comments
.slice(0, !{newestCommentsLimit}) // Limit the number of comments
.map(e => {
const avatar = avatarCdn && e.email_encrypted ? `${avatarCdn}${e.email_encrypted}?${avatarDefault}` : '' const avatar = avatarCdn && e.email_encrypted ? `${avatarCdn}${e.email_encrypted}?${avatarDefault}` : ''
return { return {
'avatar': avatar, 'avatar': avatar,

View File

@@ -23,8 +23,9 @@ script.
result += '<div class="aside-list-item">' result += '<div class="aside-list-item">'
if (!{theme.aside.card_newest_comments.avatar} && array[i].avatar) { if (!{theme.aside.card_newest_comments.avatar} && array[i].avatar) {
const imgAttr = '!{theme.lazyload.enable ? "data-lazy-src" : "src"}' const imgAttr = '!{theme.lazyload.enable && !theme.lazyload.native ? "data-lazy-src" : "src"}'
result += `<a href="${array[i].url}" class="thumbnail"><img ${imgAttr}="${array[i].avatar}" alt="${array[i].nick}"></a>` const lazyloadNative = '!{theme.lazyload.enable && theme.lazyload.native ? "loading=\"lazy\"" : ""}'
result += `<a href="${array[i].url}" class="thumbnail"><img ${imgAttr}="${array[i].avatar}" alt="${array[i].nick}" ${lazyloadNative}></a>`
} }
result += `<div class="content"> result += `<div class="content">

View File

@@ -6,7 +6,7 @@ script.
const { changeContent, generateHtml, run } = window.newestComments const { changeContent, generateHtml, run } = window.newestComments
const getComment = ele => { const getComment = ele => {
fetch('https://disqus.com/api/3.0/forums/listPosts.json?forum=!{forum}&related=thread&limit=!{theme.aside.card_newest_comments.limit}&api_key=!{apiKey}') fetch('https://disqus.com/api/3.0/forums/listPosts.json?forum=!{forum}&related=thread&limit=!{newestCommentsLimit}&api_key=!{apiKey}')
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
const disqusArray = data.response.map(item => { const disqusArray = data.response.map(item => {

View File

@@ -32,7 +32,7 @@ script.
} }
const getComment = ele => { const getComment = ele => {
fetch('https://api.github.com/repos/!{userRepo}/issues/comments?sort=updated&direction=desc&per_page=!{theme.aside.card_newest_comments.limit}&page=1',{ fetch('https://api.github.com/repos/!{userRepo}/issues/comments?sort=updated&direction=desc&per_page=!{newestCommentsLimit}&page=1',{
"headers": { "headers": {
Accept: 'application/vnd.github.v3.html+json' Accept: 'application/vnd.github.v3.html+json'
} }

View File

@@ -1,7 +1,11 @@
- let { use } = theme.comments - let { use } = theme.comments
if use if use
- let forum,apiKey,userRepo -
let forum,apiKey,userRepo
let { limit:newestCommentsLimit } = theme.aside.card_newest_comments
if (newestCommentsLimit > 10 || newestCommentsLimit < 1) newestCommentsLimit = 6
case use[0] case use[0]
when 'Valine' when 'Valine'
include ./valine.pug include ./valine.pug

View File

@@ -7,7 +7,7 @@ script.
const { changeContent, generateHtml, run } = window.newestComments const { changeContent, generateHtml, run } = window.newestComments
const getComment = ele => { const getComment = ele => {
fetch('!{host}/api/v1/last/!{theme.aside.card_newest_comments.limit}?site=!{siteId}') fetch('!{host}/api/v1/last/!{newestCommentsLimit}?site=!{siteId}')
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
const remark42 = data.map(e => { const remark42 = data.map(e => {

View File

@@ -10,7 +10,7 @@ script.
twikoo.getRecentComments({ twikoo.getRecentComments({
envId: '!{theme.twikoo.envId}', envId: '!{theme.twikoo.envId}',
region: '!{theme.twikoo.region}', region: '!{theme.twikoo.region}',
pageSize: !{theme.aside.card_newest_comments.limit}, pageSize: !{newestCommentsLimit},
includeReply: true includeReply: true
}).then(res => { }).then(res => {
const twikooArray = res.map(e => { const twikooArray = res.map(e => {

View File

@@ -27,7 +27,7 @@ script.
}, },
} }
fetch(`${serverURL}/1.1/classes/Comment?limit=!{theme.aside.card_newest_comments.limit}&order=-createdAt`,settings) fetch(`${serverURL}/1.1/classes/Comment?limit=!{newestCommentsLimit}&order=-createdAt`,settings)
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
const valineArray = data.results.map(e => { const valineArray = data.results.map(e => {

View File

@@ -9,7 +9,7 @@ script.
const getComment = async (ele) => { const getComment = async (ele) => {
try { try {
const res = await fetch('!{serverURL}/api/comment?type=recent&count=!{theme.aside.card_newest_comments.limit}', { method: 'GET' }) const res = await fetch('!{serverURL}/api/comment?type=recent&count=!{newestCommentsLimit}')
const result = await res.json() const result = await res.json()
const walineArray = result.data.map(e => { const walineArray = result.data.map(e => {
return { return {

View File

@@ -1,23 +0,0 @@
script.
(() => {
const panguFn = () => {
if (typeof pangu === 'object') pangu.autoSpacingPage()
else {
btf.getScript('!{url_for(theme.asset.pangu)}')
.then(() => {
pangu.autoSpacingPage()
})
}
}
const panguInit = () => {
if (!{theme.pangu.field === 'post'}){
GLOBAL_CONFIG_SITE.isPost && panguFn()
} else {
panguFn()
}
}
btf.addGlobalFn('pjaxComplete', panguInit, 'pangu')
document.addEventListener('DOMContentLoaded', panguInit)
})()

View File

@@ -7,10 +7,12 @@ if theme.pjax.exclude
- let choose = theme.comments.use - let choose = theme.comments.use
if choose if choose
if theme.Open_Graph_meta.enable && (choose.includes('Livere') || choose.includes('Utterances') || choose.includes('Giscus')) if choose.includes('Livere') || choose.includes('Utterances') || choose.includes('Giscus')
- pjaxSelectors.unshift('meta[property="og:image"]', 'meta[property="og:title"]', 'meta[property="og:url"]')
if choose.includes('Utterances') || choose.includes('Giscus')
- pjaxSelectors.unshift('link[rel="canonical"]') - pjaxSelectors.unshift('link[rel="canonical"]')
if theme.Open_Graph_meta.enable
- pjaxSelectors.unshift('meta[property="og:image"]', 'meta[property="og:title"]', 'meta[property="og:url"]', 'meta[property="og:description"]')
else
- pjaxSelectors.unshift('meta[name="description"]')
script(src=url_for(theme.asset.pjax)) script(src=url_for(theme.asset.pjax))
script. script.
@@ -27,7 +29,13 @@ script.
const triggerPjaxFn = (val) => { const triggerPjaxFn = (val) => {
if (!val) return if (!val) return
Object.values(val).forEach(fn => fn()) Object.values(val).forEach(fn => {
try {
fn()
} catch (err) {
console.debug('Pjax callback failed:', err)
}
})
} }
document.addEventListener('pjax:send', () => { document.addEventListener('pjax:send', () => {
@@ -57,7 +65,10 @@ script.
document.addEventListener('pjax:error', e => { document.addEventListener('pjax:error', e => {
if (e.request.status === 404) { if (e.request.status === 404) {
pjax.loadUrl('!{url_for("/404.html")}') const usePjax = !{theme.pjax && theme.pjax.enable}
!{theme.error_404 && theme.error_404.enable}
? (usePjax ? pjax.loadUrl('!{url_for("/404.html")}') : window.location.href = '!{url_for("/404.html")}')
: window.location.href = e.request.responseURL
} }
}) })
})() })()

View File

@@ -2,21 +2,33 @@
.search-dialog .search-dialog
nav.search-nav nav.search-nav
span.search-dialog-title= _p('search.title') span.search-dialog-title= _p('search.title')
i.fas.fa-spinner.fa-pulse#loading-status(hidden)
button.search-close-button button.search-close-button
i.fas.fa-times i.fas.fa-times
.search-wrap
#algolia-search-input #algolia-search-input
.ais-SearchBox
form.ais-SearchBox-form(action="" role="search" novalidate="")
input.ais-SearchBox-input(type="search" placeholder=theme.search.placeholder || _p("search.input_placeholder") autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" maxlength="512" aria-label="Search")
button.ais-SearchBox-submit(type="submit" title="Submit the search query" style="display:none;")
svg.ais-SearchBox-submitIcon(width="10" height="10" viewBox="0 0 40 40" aria-hidden="true")
path(d="M26.804 29.01c-2.832 2.34-6.465 3.746-10.426 3.746C7.333 32.756 0 25.424 0 16.378 0 7.333 7.333 0 16.378 0c9.046 0 16.378 7.333 16.378 16.378 0 3.96-1.406 7.594-3.746 10.426l10.534 10.534c.607.607.61 1.59-.004 2.202-.61.61-1.597.61-2.202.004L26.804 29.01zm-10.426.627c7.323 0 13.26-5.936 13.26-13.26 0-7.32-5.937-13.257-13.26-13.257C9.056 3.12 3.12 9.056 3.12 16.378c0 7.323 5.936 13.26 13.258 13.26z")
hr hr
#algolia-search-results #algolia-search-results
#algolia-hits #algolia-hits
#algolia-pagination #algolia-hits-empty(style="display:none;")
.ais-Hits(style="display:none;")
ol.ais-Hits-list
#algolia-pagination.ais-Pagination(style="display:none;")
ul.ais-Pagination-list
#algolia-info #algolia-info
.algolia-stats span.ais-Stats-text
.algolia-poweredBy a.algolia-poweredBy(href="https://www.algolia.com/?utm_source=algoliasearch.js&utm_medium=website&utm_content=localhost&utm_campaign=poweredby" target="_blank" aria-label="Search by Algolia" rel="noopener noreferrer")
svg.ais-PoweredBy-logo(height="1.2em" viewBox="0 0 572 64" style="width: auto;")
path(fill="#36395A" d="M16 48.3c-3.4 0-6.3-.6-8.7-1.7A12.4 12.4 0 0 1 1.9 42C.6 40 0 38 0 35.4h6.5a6.7 6.7 0 0 0 3.9 6c1.4.7 3.3 1.1 5.6 1.1 2.2 0 4-.3 5.4-1a7 7 0 0 0 3-2.4 6 6 0 0 0 1-3.4c0-1.5-.6-2.8-1.9-3.7-1.3-1-3.3-1.6-5.9-1.8l-4-.4c-3.7-.3-6.6-1.4-8.8-3.4a10 10 0 0 1-3.3-7.9c0-2.4.6-4.6 1.8-6.4a12 12 0 0 1 5-4.3c2.2-1 4.7-1.6 7.5-1.6s5.5.5 7.6 1.6a12 12 0 0 1 5 4.4c1.2 1.8 1.8 4 1.8 6.7h-6.5a6.4 6.4 0 0 0-3.5-5.9c-1-.6-2.6-1-4.4-1s-3.2.3-4.4 1c-1.1.6-2 1.4-2.6 2.4-.5 1-.8 2-.8 3.1a5 5 0 0 0 1.5 3.6c1 1 2.6 1.7 4.7 1.9l4 .3c2.8.2 5.2.8 7.2 1.8 2.1 1 3.7 2.2 4.9 3.8a9.7 9.7 0 0 1 1.7 5.8c0 2.5-.7 4.7-2 6.6a13 13 0 0 1-5.6 4.4c-2.4 1-5.2 1.6-8.4 1.6Zm35.6 0c-2.6 0-4.8-.4-6.7-1.3a13 13 0 0 1-4.7-3.5 17.1 17.1 0 0 1-3.6-10.4v-1c0-2 .3-3.8 1-5.6a13 13 0 0 1 7.3-8.3 15 15 0 0 1 6.3-1.4A13.2 13.2 0 0 1 64 24.3c1 2.2 1.6 4.6 1.6 7.2V34H39.4v-4.3h21.8l-1.8 2.2c0-2-.3-3.7-.9-5.1a7.3 7.3 0 0 0-2.7-3.4c-1.2-.7-2.7-1.1-4.6-1.1s-3.4.4-4.7 1.3a8 8 0 0 0-2.9 3.6c-.6 1.5-.9 3.3-.9 5.4 0 2 .3 3.7 1 5.3a7.9 7.9 0 0 0 2.8 3.7c1.3.8 3 1.3 5 1.3s3.8-.5 5.1-1.3c1.3-1 2.1-2 2.4-3.2h6a11.8 11.8 0 0 1-7 8.7 16 16 0 0 1-6.4 1.2ZM80 48c-2.2 0-4-.3-5.7-1a8.4 8.4 0 0 1-3.7-3.3 9.7 9.7 0 0 1-1.3-5.2c0-2 .5-3.8 1.5-5.2a9 9 0 0 1 4.3-3.1c1.8-.7 4-1 6.7-1H89v4.1h-7.5c-2 0-3.4.5-4.4 1.4-1 1-1.6 2.1-1.6 3.6s.5 2.7 1.6 3.6c1 1 2.5 1.4 4.4 1.4 1.1 0 2.2-.2 3.2-.7 1-.4 1.9-1 2.6-2 .6-1 1-2.4 1-4.2l1.7 2.1c-.2 2-.7 3.8-1.5 5.2a9 9 0 0 1-3.4 3.3 12 12 0 0 1-5.3 1Zm9.5-.7v-8.8h-1v-10c0-1.8-.5-3.2-1.4-4.1-1-1-2.4-1.4-4.2-1.4a142.9 142.9 0 0 0-10.2.4v-5.6a74.8 74.8 0 0 1 8.6-.4c3 0 5.5.4 7.5 1.2s3.4 2 4.4 3.6c1 1.7 1.4 4 1.4 6.7v18.4h-5Zm12.9 0V17.8h5v12.3h-.2c0-4.2 1-7.4 2.8-9.5a11 11 0 0 1 8.3-3.1h1v5.6h-2a9 9 0 0 0-6.3 2.2c-1.5 1.5-2.2 3.6-2.2 6.4v15.6h-6.4Zm34.4 1a15 15 0 0 1-6.6-1.3c-1.9-.9-3.4-2-4.7-3.5a15.5 15.5 0 0 1-2.7-5c-.6-1.7-1-3.6-1-5.4v-1c0-2 .4-3.8 1-5.6a15 15 0 0 1 2.8-4.9c1.3-1.5 2.8-2.6 4.6-3.5a16.4 16.4 0 0 1 13.3.2c2 1 3.5 2.3 4.8 4a12 12 0 0 1 2 6H144c-.2-1.6-1-3-2.2-4.1a7.5 7.5 0 0 0-5.2-1.7 8 8 0 0 0-4.7 1.3 8 8 0 0 0-2.8 3.6 13.8 13.8 0 0 0 0 10.3c.6 1.5 1.5 2.7 2.8 3.6s2.8 1.3 4.8 1.3c1.5 0 2.7-.2 3.8-.8a7 7 0 0 0 2.6-2c.7-1 1-2 1.2-3.2h6.2a11 11 0 0 1-2 6.2 15.1 15.1 0 0 1-11.8 5.5Zm19.7-1v-40h6.4V31h-1.3c0-3 .4-5.5 1.1-7.6a9.7 9.7 0 0 1 3.5-4.8A9.9 9.9 0 0 1 172 17h.3c3.5 0 6 1.1 7.9 3.5 1.7 2.3 2.6 5.7 2.6 10v16.8h-6.4V29.6c0-2.1-.6-3.8-1.8-5a6.4 6.4 0 0 0-4.8-1.8c-2 0-3.7.7-5 2a7.8 7.8 0 0 0-1.9 5.5v17h-6.4Zm63.8 1a12.2 12.2 0 0 1-10.9-6.2 19 19 0 0 1-1.8-7.3h1.4v12.5h-5.1v-40h6.4v19.8l-2 3.5c.2-3.1.8-5.7 1.9-7.7a11 11 0 0 1 4.4-4.5c1.8-1 3.9-1.5 6.1-1.5a13.4 13.4 0 0 1 12.8 9.1c.7 1.9 1 3.8 1 6v1c0 2.2-.3 4.1-1 6a13.6 13.6 0 0 1-13.2 9.4Zm-1.2-5.5a8.4 8.4 0 0 0 7.9-5c.7-1.5 1.1-3.3 1.1-5.3s-.4-3.8-1.1-5.3a8.7 8.7 0 0 0-3.2-3.6 9.6 9.6 0 0 0-9.2-.2 8.5 8.5 0 0 0-3.3 3.2c-.8 1.4-1.3 3-1.3 5v2.3a9 9 0 0 0 1.3 4.8 9 9 0 0 0 3.4 3c1.4.7 2.8 1 4.4 1Zm27.3 3.9-10-28.9h6.5l9.5 28.9h-6Zm-7.5 12.2v-5.7h4.9c1 0 2-.1 2.9-.4a4 4 0 0 0 2-1.4c.4-.7.9-1.6 1.2-2.7l8.6-30.9h6.2l-9.3 32.4a14 14 0 0 1-2.5 5 8.9 8.9 0 0 1-4 2.8c-1.5.6-3.4.9-5.6.9h-4.4Zm9-12.2v-5.2h6.4v5.2H248Z")
path(fill="#003DFF" d="M534.4 9.1H528a.8.8 0 0 1-.7-.7V1.8c0-.4.2-.7.6-.8l6.5-1c.4 0 .8.2.9.6v7.8c0 .4-.4.7-.8.7zM428 35.2V.8c0-.5-.3-.8-.7-.8h-.2l-6.4 1c-.4 0-.7.4-.7.8v35c0 1.6 0 11.8 12.3 12.2.5 0 .8-.4.8-.8V43c0-.4-.3-.7-.6-.8-4.5-.5-4.5-6-4.5-7zm106.5-21.8H528c-.4 0-.7.4-.7.8v34c0 .4.3.8.7.8h6.5c.4 0 .8-.4.8-.8v-34c0-.5-.4-.8-.8-.8zm-17.7 21.8V.8c0-.5-.3-.8-.8-.8l-6.5 1c-.4 0-.7.4-.7.8v35c0 1.6 0 11.8 12.3 12.2.4 0 .8-.4.8-.8V43c0-.4-.3-.7-.7-.8-4.4-.5-4.4-6-4.4-7zm-22.2-20.6a16.5 16.5 0 0 1 8.6 9.3c.8 2.2 1.3 4.8 1.3 7.5a19.4 19.4 0 0 1-4.6 12.6 14.8 14.8 0 0 1-5.2 3.6c-2 .9-5.2 1.4-6.8 1.4a21 21 0 0 1-6.7-1.4 15.4 15.4 0 0 1-8.6-9.3 21.3 21.3 0 0 1 0-14.4 15.2 15.2 0 0 1 8.6-9.3c2-.8 4.3-1.2 6.7-1.2s4.6.4 6.7 1.2zm-6.7 27.6c2.7 0 4.7-1 6.2-3s2.2-4.3 2.2-7.8-.7-6.3-2.2-8.3-3.5-3-6.2-3-4.7 1-6.1 3c-1.5 2-2.2 4.8-2.2 8.3s.7 5.8 2.2 7.8 3.5 3 6.2 3zm-88.8-28.8c-6.2 0-11.7 3.3-14.8 8.2a18.6 18.6 0 0 0 4.8 25.2c1.8 1.2 4 1.8 6.2 1.7s.1 0 .1 0h.9c4.2-.7 8-4 9.1-8.1v7.4c0 .4.3.7.8.7h6.4a.7.7 0 0 0 .7-.7V14.2c0-.5-.3-.8-.7-.8h-13.5zm6.3 26.5a9.8 9.8 0 0 1-5.7 2h-.5a10 10 0 0 1-9.2-14c1.4-3.7 5-6.3 9-6.3h6.4v18.3zm152.3-26.5h13.5c.5 0 .8.3.8.7v33.7c0 .4-.3.7-.8.7h-6.4a.7.7 0 0 1-.8-.7v-7.4c-1.2 4-4.8 7.4-9 8h-.1a4.2 4.2 0 0 1-.5.1h-.9a10.3 10.3 0 0 1-7-2.6c-4-3.3-6.5-8.4-6.5-14.2 0-3.7 1-7.2 3-10 3-5 8.5-8.3 14.7-8.3zm.6 28.4c2.2-.1 4.2-.6 5.7-2V21.7h-6.3a9.8 9.8 0 0 0-9 6.4 10.2 10.2 0 0 0 9.1 13.9h.5zM452.8 13.4c-6.2 0-11.7 3.3-14.8 8.2a18.5 18.5 0 0 0 3.6 24.3 10.4 10.4 0 0 0 13 .6c2.2-1.5 3.8-3.7 4.5-6.1v7.8c0 2.8-.8 5-2.2 6.3-1.5 1.5-4 2.2-7.5 2.2l-6-.3c-.3 0-.7.2-.8.5l-1.6 5.5c-.1.4.1.8.5 1h.1c2.8.4 5.5.6 7 .6 6.3 0 11-1.4 14-4.1 2.7-2.5 4.2-6.3 4.5-11.4V14.2c0-.5-.4-.8-.8-.8h-13.5zm6.3 8.2v18.3a9.6 9.6 0 0 1-5.6 2h-1a10.3 10.3 0 0 1-8.8-14c1.4-3.7 5-6.3 9-6.3h6.4zM291 31.5A32 32 0 0 1 322.8 0h30.8c.6 0 1.2.5 1.2 1.2v61.5c0 1.1-1.3 1.7-2.2 1l-19.2-17a18 18 0 0 1-11 3.4 18.1 18.1 0 1 1 18.2-14.8c-.1.4-.5.7-.9.6-.1 0-.3 0-.4-.2l-3.8-3.4c-.4-.3-.6-.8-.7-1.4a12 12 0 1 0-2.4 8.3c.4-.4 1-.5 1.6-.2l14.7 13.1v-46H323a26 26 0 1 0 10 49.7c.8-.4 1.6-.2 2.3.3l3 2.7c.3.2.3.7 0 1l-.2.2a32 32 0 0 1-47.2-28.6z")
#search-mask #search-mask
script(src=url_for(theme.asset.algolia_search)) script(src=url_for(theme.asset.algolia_search))
script(src=url_for(theme.asset.instantsearch))
script(src=url_for(theme.asset.algolia_js)) script(src=url_for(theme.asset.algolia_js))

View File

@@ -2,7 +2,7 @@
.search-dialog .search-dialog
nav.search-nav nav.search-nav
span.search-dialog-title= _p('search.title') span.search-dialog-title= _p('search.title')
span#loading-status i.fas.fa-spinner.fa-pulse#loading-status(hidden)
button.search-close-button button.search-close-button
i.fas.fa-times i.fas.fa-times
@@ -10,13 +10,15 @@
i.fas.fa-spinner.fa-pulse i.fas.fa-spinner.fa-pulse
span= ' ' + _p("search.load_data") span= ' ' + _p("search.load_data")
.search-wrap .local-search-input
#local-search-input input(placeholder=theme.search.placeholder || _p("search.input_placeholder") type="text")
.local-search-box
input(placeholder=theme.search.placeholder || _p("search.input_placeholder") type="text").local-search-box--input
hr
#local-search-results
#local-search-stats-wrap
#search-mask
hr
#local-search-results
#local-search-pagination.ais-Pagination(style="display:none;")
ul.ais-Pagination-list
#local-search-stats
#search-mask
script(src=url_for(theme.asset.local_search)) script(src=url_for(theme.asset.local_search))

View File

@@ -1,6 +1,6 @@
.addtoany .addtoany
.a2a_kit.a2a_kit_size_32.a2a_default_style .a2a_kit.a2a_kit_size_32.a2a_default_style
- let addtoanyItem = theme.addtoany.item.split(',') - let addtoanyItem = theme.share.addtoany.item.split(',')
each name in addtoanyItem each name in addtoanyItem
a(class="a2a_button_" + name) a(class="a2a_button_" + name)

View File

@@ -22,6 +22,26 @@ script.
} else { } else {
subtitleType() subtitleType()
} }
},
processSubtitle: (content, extraContents = []) => {
if (!{effect}) {
const sub = !{JSON.stringify(subContent)}.slice()
if (extraContents.length > 0) {
sub.unshift(...extraContents)
}
if (typeof content === 'string') {
sub.unshift(content)
} else if (Array.isArray(content)) {
sub.unshift(...content)
}
sub.length > 0 && typedJSFn.init(sub)
} else {
document.getElementById('subtitle').textContent = typeof content === 'string' ? content :
(Array.isArray(content) && content.length > 0 ? content[0] : '')
}
} }
} }
btf.addGlobalFn('pjaxSendOnce', () => { typed.destroy() }, 'typedDestroy') btf.addGlobalFn('pjaxSendOnce', () => { typed.destroy() }, 'typedDestroy')
@@ -33,14 +53,12 @@ case source
fetch('https://v1.hitokoto.cn') fetch('https://v1.hitokoto.cn')
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
if (!{effect}) {
const from = '出自 ' + data.from const from = '出自 ' + data.from
const sub = !{JSON.stringify(subContent)} typedJSFn.processSubtitle(data.hitokoto, [from])
sub.unshift(data.hitokoto, from) })
typedJSFn.init(sub) .catch(err => {
} else { console.error('Failed to get the Hitokoto API:', err)
document.getElementById('subtitle').textContent = data.hitokoto typedJSFn.processSubtitle(!{JSON.stringify(subContent)})
}
}) })
} }
typedJSFn.run(subtitleType) typedJSFn.run(subtitleType)
@@ -48,46 +66,48 @@ case source
when 2 when 2
script. script.
function subtitleType () { function subtitleType () {
btf.getScript('https://yijuzhan.com/api/word.php?m=js').then(() => { fetch('https://v.api.aa1.cn/api/yiyan/index.php')
const con = str[0] .then(response => response.text())
if (!{effect}) { .then(data => {
const from = '出自 ' + str[1] const reg = /<p>(.*?)<\/p>/g
const sub = !{JSON.stringify(subContent)} const result = reg.exec(data)
sub.unshift(con, from) if (result && result[1]) {
typedJSFn.init(sub) typedJSFn.processSubtitle(result[1])
} else { } else {
document.getElementById('subtitle').textContent = con throw new Error('Failed to parse the return value of the Yiyan API')
} }
}) })
.catch(err => {
console.error('Failed to get the Yiyan API:', err)
typedJSFn.processSubtitle(!{JSON.stringify(subContent.length)})
})
} }
typedJSFn.run(subtitleType) typedJSFn.run(subtitleType)
when 3 when 3
script. script.
function subtitleType () { function subtitleType () {
btf.getScript('https://sdk.jinrishici.com/v2/browser/jinrishici.js').then(() => { btf.getScript('https://sdk.jinrishici.com/v2/browser/jinrishici.js')
.then(() => {
jinrishici.load(result => { jinrishici.load(result => {
if (!{effect}) { if (result && result.data && result.data.content) {
const sub = !{JSON.stringify(subContent)} typedJSFn.processSubtitle(result.data.content)
const content = result.data.content
sub.unshift(content)
typedJSFn.init(sub)
} else { } else {
document.getElementById('subtitle').textContent = result.data.content throw new Error('Failed to parse the return value of Jinrishici API')
} }
}) })
}) })
.catch(err => {
console.error('Failed to get the Jinrishici API:', err)
typedJSFn.processSubtitle(!{JSON.stringify(subContent.length)})
})
} }
typedJSFn.run(subtitleType) typedJSFn.run(subtitleType)
default default
- subContent = subContent.length ? subContent : new Array(config.subtitle) if subContent.length > 0
script. script.
function subtitleType () { function subtitleType () {
if (!{effect}) { typedJSFn.processSubtitle(!{JSON.stringify(subContent)})
typedJSFn.init(!{JSON.stringify(subContent)})
} else {
document.getElementById("subtitle").textContent = !{JSON.stringify(subContent[0])}
}
} }
typedJSFn.run(subtitleType) typedJSFn.run(subtitleType)

View File

@@ -1,4 +1,4 @@
- let { serverURL, website_id, option, UV_PV } = theme.umami_analytics - let { serverURL, script_name, website_id, option, UV_PV } = theme.umami_analytics
- const isServerURL = !!serverURL - const isServerURL = !!serverURL
- const baseURL = serverURL ? serverURL.replace(/\/$/, '') : 'https://cloud.umami.is' - const baseURL = serverURL ? serverURL.replace(/\/$/, '') : 'https://cloud.umami.is'
- const apiUrl = serverURL ? serverURL.replace(/\/$/, '') + '/api' : 'https://api.umami.is/v1' - const apiUrl = serverURL ? serverURL.replace(/\/$/, '') + '/api' : 'https://api.umami.is/v1'
@@ -9,57 +9,102 @@ script.
const config = !{JSON.stringify(UV_PV)} const config = !{JSON.stringify(UV_PV)}
const runTrack = () => { const runTrack = () => {
if (typeof umami !== 'undefined' && typeof umami.track === 'function') {
umami.track(props => ({ ...props, url: window.location.pathname, title: GLOBAL_CONFIG_SITE.title })) umami.track(props => ({ ...props, url: window.location.pathname, title: GLOBAL_CONFIG_SITE.title }))
} else {
console.warn('Umami Analytics: umami.track is not available')
}
} }
const loadUmamiJS = () => { const loadUmamiJS = () => {
btf.getScript('!{baseURL}/script.js', { btf.getScript('!{baseURL}/!{script_name}', {
'data-website-id': '!{website_id}', 'data-website-id': '!{website_id}',
'data-auto-track': 'false', 'data-auto-track': 'false',
...option ...option
}).then(runTrack) }).then(() => {
runTrack()
}).catch(error => {
console.error('Umami Analytics: Error loading script', error)
})
} }
const getData = async (isPost) => { const getData = async (isPost) => {
try {
const now = Date.now() const now = Date.now()
const keyUrl = isPost ? `&url=${window.location.pathname}` : '' const keyUrl = isPost ? `&url=${window.location.pathname}` : ''
const headerList = { 'Accept': 'application/json' } const headerList = { 'Accept': 'application/json' }
if (!{isServerURL}) headerList['Authorization'] = `Bearer ${config.token}`
else headerList['x-umami-api-key'] = config.token if (!{isServerURL}) {
headerList['Authorization'] = `Bearer ${config.token}`
} else {
headerList['x-umami-api-key'] = config.token
}
const res = await fetch(`!{apiUrl}/websites/!{website_id}/stats?startAt=0000000000&endAt=${now}${keyUrl}`, { const res = await fetch(`!{apiUrl}/websites/!{website_id}/stats?startAt=0000000000&endAt=${now}${keyUrl}`, {
method: "GET", method: "GET",
headers: headerList headers: headerList
}) })
if (!res.ok) {
throw new Error(`HTTP error! status: ${res.status}`)
}
return await res.json() return await res.json()
} catch (error) {
console.error('Umami Analytics: Failed to fetch data', error)
throw error
}
} }
const insertData = async () => { const insertData = async () => {
try { try {
if (GLOBAL_CONFIG_SITE.isPost && config.page_pv) { if (GLOBAL_CONFIG_SITE.pageType === 'post' && config.page_pv) {
const pagePV = document.getElementById('umamiPV') const pagePV = document.getElementById('umamiPV')
if (pagePV) { if (pagePV) {
const data = await getData(true) const data = await getData(true)
if (data && data.pageviews && typeof data.pageviews.value !== 'undefined') {
pagePV.textContent = data.pageviews.value pagePV.textContent = data.pageviews.value
}
} else { } else {
const data = (config.site_uv || config.site_pv) && await getData() console.warn('Umami Analytics: Invalid page view data received')
}
}
}
if (config.site_uv || config.site_pv) {
const data = await getData(false)
if (config.site_uv) { if (config.site_uv) {
const siteUV = document.getElementById('umami-site-uv') const siteUV = document.getElementById('umami-site-uv')
if (siteUV) siteUV.textContent = data.visitors.value if (siteUV && data && data.visitors && typeof data.visitors.value !== 'undefined') {
siteUV.textContent = data.visitors.value
} else if (siteUV) {
console.warn('Umami Analytics: Invalid site UV data received')
} }
}
if (config.site_pv) { if (config.site_pv) {
const sitePV = document.getElementById('umami-site-pv') const sitePV = document.getElementById('umami-site-pv')
if (sitePV) sitePV.textContent = data.pageviews.value if (sitePV && data && data.pageviews && typeof data.pageviews.value !== 'undefined') {
sitePV.textContent = data.pageviews.value
} else if (sitePV) {
console.warn('Umami Analytics: Invalid site PV data received')
} }
} }
} catch (e) { }
console.error('Failed to load Umami Analytics:', e) } catch (error) {
console.error('Umami Analytics: Failed to insert data', error)
} }
} }
btf.addGlobalFn('pjaxComplete', runTrack, 'umami_analytics_run_track') btf.addGlobalFn('pjaxComplete', runTrack, 'umami_analytics_run_track')
btf.addGlobalFn('pjaxComplete', insertData, 'umami_analytics_insert') btf.addGlobalFn('pjaxComplete', insertData, 'umami_analytics_insert')
loadUmamiJS() loadUmamiJS()
insertData()
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', insertData)
} else {
setTimeout(insertData, 100)
}
})() })()

View File

@@ -12,4 +12,3 @@
.toc-content.toc-div-class(class=tocExpandClass style="display:none")!=toc(page.origin, {list_number: tocNumber}) .toc-content.toc-div-class(class=tocExpandClass style="display:none")!=toc(page.origin, {list_number: tocNumber})
else else
.toc-content(class=tocExpandClass)!=toc(page.content, {list_number: tocNumber}) .toc-content(class=tocExpandClass)!=toc(page.content, {list_number: tocNumber})

View File

@@ -9,6 +9,6 @@ if theme.aside.card_tags.enable
- limit = limit === 0 ? 0 : limit || 40 - 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, orderby: orderby, order: order, minfontsize: 1.15, maxfontsize: 1.45, limit: limit, unit: 'em'}) .card-tag-cloud!= cloudTags({source: site.tags, orderby: orderby, order: order, minfontsize: 1.15, maxfontsize: 1.45, limit: limit, unit: 'em', page: 'index'})
else else
.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'}) .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

@@ -6,40 +6,39 @@ if theme.aside.card_webinfo.enable
.webinfo .webinfo
if theme.aside.card_webinfo.post_count if theme.aside.card_webinfo.post_count
.webinfo-item .webinfo-item
.item-name= _p('aside.card_webinfo.article_name') + " :" .item-name= `${_p('aside.card_webinfo.article_name')} :`
.item-count= site.posts.length .item-count= site.posts.length
if theme.aside.card_webinfo.runtime_date if theme.aside.card_webinfo.runtime_date
.webinfo-item .webinfo-item
.item-name= _p('aside.card_webinfo.runtime.name') + " :" .item-name= `${_p('aside.card_webinfo.runtime.name')} :`
.item-count#runtimeshow(data-publishDate=date_xml(theme.aside.card_webinfo.runtime_date)) .item-count#runtimeshow(data-publishDate=date_xml(theme.aside.card_webinfo.runtime_date))
i.fa-solid.fa-spinner.fa-spin i.fa-solid.fa-spinner.fa-spin
if theme.wordcount.enable && theme.wordcount.total_wordcount if theme.wordcount.enable && theme.wordcount.total_wordcount
.webinfo-item .webinfo-item
.item-name=_p('aside.card_webinfo.site_wordcount') + " :" .item-name= `${_p('aside.card_webinfo.site_wordcount')} :`
.item-count= totalcount(site) .item-count= totalcount(site)
if theme.umami_analytics.enable && theme.umami_analytics.UV_PV.site_uv if theme.umami_analytics.enable && theme.umami_analytics.UV_PV.site_uv
.webinfo-item .webinfo-item
.item-name= _p('aside.card_webinfo.site_uv_name') + " :" .item-name= `${_p('aside.card_webinfo.site_uv_name')} :`
.item-count#umami-site-uv .item-count#umami-site-uv
i.fa-solid.fa-spinner.fa-spin i.fa-solid.fa-spinner.fa-spin
else if theme.busuanzi.site_uv else if theme.busuanzi.site_uv
.webinfo-item .webinfo-item
.item-name= _p('aside.card_webinfo.site_uv_name') + " :" .item-name= `${_p('aside.card_webinfo.site_uv_name')} :`
.item-count#busuanzi_value_site_uv .item-count#busuanzi_value_site_uv
i.fa-solid.fa-spinner.fa-spin i.fa-solid.fa-spinner.fa-spin
if theme.umami_analytics.enable && theme.umami_analytics.UV_PV.site_pv if theme.umami_analytics.enable && theme.umami_analytics.UV_PV.site_pv
.webinfo-item .webinfo-item
.item-name= _p('aside.card_webinfo.site_pv_name') + " :" .item-name= `${_p('aside.card_webinfo.site_pv_name')} :`
.item-count#umami-site-pv .item-count#umami-site-pv
i.fa-solid.fa-spinner.fa-spin i.fa-solid.fa-spinner.fa-spin
else if theme.busuanzi.site_pv else if theme.busuanzi.site_pv
.webinfo-item .webinfo-item
.item-name= _p('aside.card_webinfo.site_pv_name') + " :" .item-name= `${_p('aside.card_webinfo.site_pv_name')} :`
.item-count#busuanzi_value_site_pv .item-count#busuanzi_value_site_pv
i.fa-solid.fa-spinner.fa-spin i.fa-solid.fa-spinner.fa-spin
if theme.aside.card_webinfo.last_push_date if theme.aside.card_webinfo.last_push_date
.webinfo-item .webinfo-item
.item-name= _p('aside.card_webinfo.last_push_date.name') + " :" .item-name= `${_p('aside.card_webinfo.last_push_date.name')} :`
.item-count#last-push-date(data-lastPushDate=date_xml(Date.now())) .item-count#last-push-date(data-lastPushDate=date_xml(Date.now()))
i.fa-solid.fa-spinner.fa-spin i.fa-solid.fa-spinner.fa-spin

View File

@@ -1,6 +1,6 @@
#aside-content.aside-content #aside-content.aside-content
//- post //- post
if is_post() if globalPageType === 'post'
- const tocStyle = page.toc_style_simple - const tocStyle = page.toc_style_simple
- const tocStyleVal = tocStyle === true || tocStyle === false ? tocStyle : theme.toc.style_simple - const tocStyleVal = tocStyle === true || tocStyle === false ? tocStyle : theme.toc.style_simple
if showToc && tocStyleVal if showToc && tocStyleVal

View File

@@ -5,7 +5,11 @@ block content
if top_img === false if top_img === false
include includes/header/post-info.pug include includes/header/post-info.pug
article#article-container.post-content!=page.content article#article-container.container.post-content
if theme.noticeOutdate.enable && page.noticeOutdate !== false
include includes/post/outdate-notice.pug
else
!=page.content
include includes/post/post-copyright.pug include includes/post/post-copyright.pug
.tag_share .tag_share
if (page.tags.length > 0 && theme.post_meta.post.tags) if (page.tags.length > 0 && theme.post_meta.post.tags)
@@ -29,4 +33,3 @@ block content
if page.comments !== false && theme.comments.use if page.comments !== false && theme.comments.use
- var commentsJsLoad = true - var commentsJsLoad = true
!=partial('includes/third-party/comments/index', {}, {cache: true}) !=partial('includes/third-party/comments/index', {}, {cache: true})

View File

@@ -1,6 +1,6 @@
{ {
"name": "hexo-theme-butterfly", "name": "hexo-theme-butterfly",
"version": "5.2.1", "version": "5.5.2",
"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": {
@@ -23,8 +23,10 @@
"email": "my@crazywong.com" "email": "my@crazywong.com"
}, },
"dependencies": { "dependencies": {
"hexo-renderer-pug": "^3.0.0",
"hexo-renderer-stylus": "^3.0.1", "hexo-renderer-stylus": "^3.0.1",
"hexo-renderer-pug": "^3.0.0" "hexo-util": "^4.0.0",
"moment-timezone": "^0.6.0"
}, },
"homepage": "https://butterfly.js.org/", "homepage": "https://butterfly.js.org/",
"author": "Jerry <my@crazywong.com>", "author": "Jerry <my@crazywong.com>",

View File

@@ -1,15 +1,15 @@
abcjs_basic_js: abcjs_basic_js:
name: abcjs name: abcjs
file: dist/abcjs-basic-min.js file: dist/abcjs-basic-min.js
version: 6.4.4 version: 6.5.2
activate_power_mode: activate_power_mode:
name: butterfly-extsrc name: butterfly-extsrc
file: dist/activate-power-mode.min.js file: dist/activate-power-mode.min.js
version: 1.1.4 version: 1.1.6
algolia_search: algolia_search:
name: algoliasearch name: algoliasearch
file: dist/lite/builds/browser.umd.js file: dist/lite/builds/browser.umd.js
version: 5.12.0 version: 5.43.0
aplayer_css: aplayer_css:
name: aplayer name: aplayer
file: dist/APlayer.min.css file: dist/APlayer.min.css
@@ -33,45 +33,45 @@ blueimp_md5:
canvas_fluttering_ribbon: canvas_fluttering_ribbon:
name: butterfly-extsrc name: butterfly-extsrc
file: dist/canvas-fluttering-ribbon.min.js file: dist/canvas-fluttering-ribbon.min.js
version: 1.1.4 version: 1.1.6
canvas_nest: canvas_nest:
name: butterfly-extsrc name: butterfly-extsrc
file: dist/canvas-nest.min.js file: dist/canvas-nest.min.js
version: 1.1.4 version: 1.1.6
canvas_ribbon: canvas_ribbon:
name: butterfly-extsrc name: butterfly-extsrc
file: dist/canvas-ribbon.min.js file: dist/canvas-ribbon.min.js
version: 1.1.4 version: 1.1.6
chartjs: chartjs:
name: chart.js name: chart.js
file: dist/chart.umd.js file: dist/chart.umd.js
version: 4.4.6 version: 4.5.1
clickShowText: clickShowText:
name: butterfly-extsrc name: butterfly-extsrc
file: dist/click-show-text.min.js file: dist/click-show-text.min.js
version: 1.1.4 version: 1.1.6
click_heart: click_heart:
name: butterfly-extsrc name: butterfly-extsrc
file: dist/click-heart.min.js file: dist/click-heart.min.js
version: 1.1.4 version: 1.1.6
disqusjs: disqusjs:
name: disqusjs name: disqusjs
file: dist/browser/disqusjs.es2015.umd.min.js file: dist/browser/disqusjs.es2015.umd.min.js
version: 3.0.2 version: 3.2.1
disqusjs_css: disqusjs_css:
name: disqusjs name: disqusjs
file: dist/browser/styles/disqusjs.css file: dist/browser/styles/disqusjs.css
version: 3.0.2 version: 3.2.1
docsearch_css: docsearch_css:
name: '@docsearch/css' name: '@docsearch/css'
other_name: docsearch-css other_name: docsearch-css
file: dist/style.css file: dist/style.css
version: 3.6.3 version: 4.3.1
docsearch_js: docsearch_js:
name: '@docsearch/js' name: '@docsearch/js'
other_name: docsearch-js other_name: docsearch-js
file: dist/umd/index.js file: dist/umd/index.js
version: 3.6.3 version: 4.3.1
egjs_infinitegrid: egjs_infinitegrid:
name: '@egjs/infinitegrid' name: '@egjs/infinitegrid'
other_name: egjs-infinitegrid other_name: egjs-infinitegrid
@@ -80,22 +80,22 @@ egjs_infinitegrid:
fancybox: fancybox:
name: '@fancyapps/ui' name: '@fancyapps/ui'
file: dist/fancybox/fancybox.umd.js file: dist/fancybox/fancybox.umd.js
version: 5.0.36 version: 6.1.4
other_name: fancyapps-ui other_name: fancyapps-ui
fancybox_css: fancybox_css:
name: '@fancyapps/ui' name: '@fancyapps/ui'
file: dist/fancybox/fancybox.css file: dist/fancybox/fancybox.css
version: 5.0.36 version: 6.1.4
other_name: fancyapps-ui other_name: fancyapps-ui
fireworks: fireworks:
name: butterfly-extsrc name: butterfly-extsrc
file: dist/fireworks.min.js file: dist/fireworks.min.js
version: 1.1.4 version: 1.1.6
fontawesome: fontawesome:
name: '@fortawesome/fontawesome-free' name: '@fortawesome/fontawesome-free'
file: css/all.min.css file: css/all.min.css
other_name: font-awesome other_name: font-awesome
version: 6.6.0 version: 7.1.0
gitalk: gitalk:
name: gitalk name: gitalk
file: dist/gitalk.min.js file: dist/gitalk.min.js
@@ -108,28 +108,24 @@ instantpage:
name: instant.page name: instant.page
file: instantpage.js file: instantpage.js
version: 5.2.0 version: 5.2.0
instantsearch:
name: instantsearch.js
file: dist/instantsearch.production.min.js
version: 4.75.3
katex: katex:
name: katex name: katex
file: dist/katex.min.css file: dist/katex.min.css
other_name: KaTeX other_name: KaTeX
version: 0.16.11 version: 0.16.25
katex_copytex: katex_copytex:
name: katex name: katex
file: dist/contrib/copy-tex.min.js file: dist/contrib/copy-tex.min.js
other_name: KaTeX other_name: KaTeX
version: 0.16.11 version: 0.16.25
lazyload: lazyload:
name: vanilla-lazyload name: vanilla-lazyload
file: dist/lazyload.iife.min.js file: dist/lazyload.iife.min.js
version: 19.1.3 version: 19.1.3
mathjax: mathjax:
name: mathjax name: mathjax
file: es5/tex-mml-chtml.js file: tex-mml-chtml.js
version: 3.2.2 version: 4.0.0
medium_zoom: medium_zoom:
name: medium-zoom name: medium-zoom
file: dist/medium-zoom.min.js file: dist/medium-zoom.min.js
@@ -137,11 +133,11 @@ medium_zoom:
mermaid: mermaid:
name: mermaid name: mermaid
file: dist/mermaid.min.js file: dist/mermaid.min.js
version: 11.4.0 version: 11.12.1
meting_js: meting_js:
name: butterfly-extsrc name: butterfly-extsrc
file: metingjs/dist/Meting.min.js file: metingjs/dist/Meting.min.js
version: 1.1.4 version: 1.1.6
pace_default_css: pace_default_css:
name: pace-js name: pace-js
other_name: pace other_name: pace
@@ -152,10 +148,6 @@ pace_js:
other_name: pace other_name: pace
file: pace.min.js file: pace.min.js
version: 1.2.4 version: 1.2.4
pangu:
name: pangu
file: dist/browser/pangu.min.js
version: 4.0.7
pjax: pjax:
name: pjax name: pjax
file: pjax.min.js file: pjax.min.js
@@ -164,25 +156,25 @@ prismjs_autoloader:
name: prismjs name: prismjs
file: plugins/autoloader/prism-autoloader.min.js file: plugins/autoloader/prism-autoloader.min.js
other_name: prism other_name: prism
version: 1.29.0 version: 1.30.0
prismjs_js: prismjs_js:
name: prismjs name: prismjs
file: prism.js file: prism.js
other_name: prism other_name: prism
version: 1.29.0 version: 1.30.0
prismjs_lineNumber_js: prismjs_lineNumber_js:
name: prismjs name: prismjs
file: plugins/line-numbers/prism-line-numbers.min.js file: plugins/line-numbers/prism-line-numbers.min.js
other_name: prism other_name: prism
version: 1.29.0 version: 1.30.0
sharejs: sharejs:
name: butterfly-extsrc name: butterfly-extsrc
file: sharejs/dist/js/social-share.min.js file: sharejs/dist/js/social-share.min.js
version: 1.1.4 version: 1.1.6
sharejs_css: sharejs_css:
name: butterfly-extsrc name: butterfly-extsrc
file: sharejs/dist/css/share.min.css file: sharejs/dist/css/share.min.css
version: 1.1.4 version: 1.1.6
snackbar: snackbar:
name: node-snackbar name: node-snackbar
file: dist/snackbar.min.js file: dist/snackbar.min.js
@@ -194,7 +186,7 @@ snackbar_css:
twikoo: twikoo:
name: twikoo name: twikoo
file: dist/twikoo.all.min.js file: dist/twikoo.all.min.js
version: 1.6.39 version: 1.6.44
typed: typed:
name: typed.js name: typed.js
file: dist/typed.umd.js file: dist/typed.umd.js
@@ -202,14 +194,14 @@ typed:
valine: valine:
name: valine name: valine
file: dist/Valine.min.js file: dist/Valine.min.js
version: 1.5.2 version: 1.5.3
waline_css: waline_css:
name: '@waline/client' name: '@waline/client'
file: dist/waline.css file: dist/waline.css
other_name: waline other_name: waline
version: 3.3.2 version: 3.7.1
waline_js: waline_js:
name: '@waline/client' name: '@waline/client'
file: dist/waline.js file: dist/waline.js
other_name: waline other_name: waline
version: 3.3.2 version: 3.7.1

View File

@@ -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
}
}

View File

@@ -3,13 +3,14 @@
const { stripHTML, truncate } = require('hexo-util') const { stripHTML, truncate } = require('hexo-util')
// Truncates the given content to a specified length, removing HTML tags and replacing newlines with spaces. // Truncates the given content to a specified length, removing HTML tags and replacing newlines with spaces.
const truncateContent = (content, length) => { const truncateContent = (content, length, encrypt = false) => {
return truncate(stripHTML(content), { length, separator: ' ' }).replace(/\n/g, ' ') if (!content || encrypt) return ''
return truncate(stripHTML(content).replace(/\n/g, ' '), { length })
} }
// Generates a post description based on the provided data and theme configuration. // Generates a post description based on the provided data and theme configuration.
const postDesc = (data, hexo) => { const postDesc = (data, hexo) => {
const { description, content, postDesc } = data const { description, content, postDesc, encrypt } = data
if (postDesc) return postDesc if (postDesc) return postDesc
@@ -23,10 +24,10 @@ const postDesc = (data, hexo) => {
result = description result = description
break break
case 2: case 2:
result = description || truncateContent(content, length) result = description || truncateContent(content, length, encrypt)
break break
default: default:
result = truncateContent(content, length) result = truncateContent(content, length, encrypt)
} }
data.postDesc = result data.postDesc = result

View File

@@ -1,24 +0,0 @@
/**
* 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
// Make sure use is an array
use = Array.isArray(use) ? use : use.split(',')
// Capitalize the first letter of each comment name
use = use.map(item =>
item.trim().toLowerCase().replace(/\b[a-z]/g, s => s.toUpperCase())
)
// Disqus and Disqusjs conflict, only keep the first one
if (use.includes('Disqus') && use.includes('Disqusjs')) {
use = [use[0]]
}
themeConfig.comments.use = use
})

View File

@@ -1,14 +1,26 @@
hexo.extend.filter.register('before_generate', () => { const { deepMerge } = require('hexo-util')
// Get first two digits of the Hexo version number const path = require('path')
const { version, log, locals } = hexo
const hexoVer = version.replace(/(^.*\..*)\..*/, '$1')
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('Please update Hexo to V5.3.0 or higher!')
log.error('請把 Hexo 升級到 V5.3.0 或更高的版本!') log.error('請把 Hexo 升級到 V5.3.0 或更高的版本!')
process.exit(-1) process.exit(-1)
} }
// Check for deprecated configuration file
if (locals.get) { if (locals.get) {
const data = locals.get('data') const data = locals.get('data')
if (data && data.butterfly) { if (data && data.butterfly) {
@@ -17,4 +29,58 @@ hexo.extend.filter.register('before_generate', () => {
process.exit(-1) 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)

View File

@@ -1,585 +0,0 @@
const { deepMerge } = require('hexo-util')
hexo.extend.filter.register('before_generate', () => {
const defaultConfig = {
nav: {
logo: null,
display_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: {
owner: {
enable: true,
since: 2019
},
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_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
},
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,
CDN: null
},
docsearch: {
appId: null,
apiKey: null,
indexName: null,
option: null
}
},
share: {
use: 'sharejs',
sharejs: {
sites: 'facebook,twitter,wechat,weibo,qq'
},
addtoany: {
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',
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
},
baidu_analytics: null,
google_analytics: null,
cloudflare_analytics: null,
microsoft_clarity: null,
umami_analytics: {
enable: false,
serverURL: null,
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,
pangu: {
enable: false,
field: 'site'
},
lazyload: {
enable: 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
},
css_prefix: true,
inject: {
head: null,
bottom: null
},
CDN: {
internal_provider: 'local',
third_party_provider: 'jsdelivr',
version: false,
custom_format: null,
option: null
}
}
hexo.theme.config = deepMerge(defaultConfig, hexo.theme.config)
}, 1)

View File

@@ -1,7 +1,7 @@
/** /**
* Butterfly * Butterfly
* lazyload * Lazyload filter
* replace src to data-lazy-src * Replace src with data-lazy-src for lazy loading
*/ */
'use strict' 'use strict'
@@ -9,8 +9,21 @@
const urlFor = require('hexo-util').url_for.bind(hexo) const urlFor = require('hexo-util').url_for.bind(hexo)
const lazyload = htmlContent => { const lazyload = htmlContent => {
if (hexo.theme.config.lazyload.native) {
// Use more precise replacement: only replace img tags in HTML, not content inside script tags
return htmlContent.replace(/(<img(?![^>]*?\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' 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=`)
// Handle src attributes with double quotes, single quotes, or no quotes (unified approach)
// Matches: src="..." or src='...' or src=... (e.g., after minification by hexo-minify)
return htmlContent.replace(/(<img(?![^>]*?\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 => { hexo.extend.filter.register('after_render:html', data => {

View File

@@ -1,33 +1,57 @@
/** /**
* Butterfly * Random cover for posts
* ramdom cover
*/ */
'use strict' 'use strict'
hexo.extend.filter.register('before_post_render', data => { hexo.extend.generator.register('post', locals => {
const imgTestReg = /\.(png|jpe?g|gif|svg|webp)(\?.*)?$/i const previousIndexes = []
const getRandomCover = defaultCover => {
if (!defaultCover) return false
if (!Array.isArray(defaultCover)) return defaultCover
const coverCount = defaultCover.length
if (coverCount === 1) {
return defaultCover[0]
}
const maxPreviousIndexes = coverCount === 2 ? 1 : (coverCount === 3 ? 2 : 3)
let index
do {
index = Math.floor(Math.random() * coverCount)
} while (previousIndexes.includes(index) && previousIndexes.length < coverCount)
previousIndexes.push(index)
if (previousIndexes.length > maxPreviousIndexes) {
previousIndexes.shift()
}
return defaultCover[index]
}
const handleImg = data => {
const imgTestReg = /\.(png|jpe?g|gif|svg|webp|avif)(\?.*)?$/i
let { cover: coverVal, top_img: topImg } = data let { cover: coverVal, top_img: topImg } = data
// Add path to top_img and cover if post_asset_folder is enabled // Add path to top_img and cover if post_asset_folder is enabled
if (hexo.config.post_asset_folder) { if (hexo.config.post_asset_folder) {
if (topImg && topImg.indexOf('/') === -1 && imgTestReg.test(topImg)) data.top_img = `${data.path}${topImg}` if (topImg && topImg.indexOf('/') === -1 && imgTestReg.test(topImg)) {
if (coverVal && coverVal.indexOf('/') === -1 && imgTestReg.test(coverVal)) data.cover = `${data.path}${coverVal}` 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 (coverVal === false) return data
// If cover is not set, use random cover // If cover is not set, use random cover
if (!coverVal) { if (!coverVal) {
const randomCover = randomCoverFn() const { cover: { default_cover: defaultCover } } = hexo.theme.config
const randomCover = getRandomCover(defaultCover)
data.cover = randomCover data.cover = randomCover
coverVal = randomCover // update coverVal coverVal = randomCover // update coverVal
} }
@@ -37,4 +61,22 @@ hexo.extend.filter.register('before_post_render', data => {
} }
return data return data
}
// https://github.com/hexojs/hexo/blob/master/lib%2Fplugins%2Fgenerator%2Fpost.ts
const posts = locals.posts.sort('date').toArray()
const { length } = posts
return posts.map((post, i) => {
if (i) post.prev = posts[i - 1]
if (i < length - 1) post.next = posts[i + 1]
post.__post = true
return {
data: handleImg(post),
layout: 'post',
path: post.path
}
})
}) })

View File

@@ -1,77 +1,126 @@
'use strict' 'use strict'
hexo.extend.helper.register('aside_archives', function (options = {}) { hexo.extend.helper.register('aside_archives', function (options = {}) {
const { config, page, site, url_for, _p } = this const { config, page, site, url_for: urlFor, _p } = this
const archiveDir = config.archive_dir const { archive_dir: archiveDir, timezone, language } = config
const { timezone } = config
const lang = toMomentLocale(page.lang || page.language || config.language) // Destructure and set default options with object destructuring
const type = options.type || 'monthly' const {
const format = options.format || (type === 'monthly' ? 'MMMM YYYY' : 'YYYY') type = 'monthly',
const showCount = Object.prototype.hasOwnProperty.call(options, 'show_count') ? options.show_count : true format = type === 'monthly' ? 'MMMM YYYY' : 'YYYY',
const order = options.order || -1 show_count: showCount = true,
const limit = options.limit order = -1,
const compareFunc = type === 'monthly' limit,
transform
} = options
// Optimize locale handling
const lang = toMomentLocale(page.lang || page.language || language)
// Memoize comparison function to improve performance
const compareFunc =
type === 'monthly'
? (yearA, monthA, yearB, monthB) => yearA === yearB && monthA === monthB ? (yearA, monthA, yearB, monthB) => yearA === yearB && monthA === monthB
: (yearA, monthA, yearB, monthB) => yearA === yearB : (yearA, yearB) => yearA === yearB
const posts = site.posts.sort('date', order) // Early return if no posts
if (!posts.length) return '' if (!site.posts.length) return ''
const data = [] // Use reduce for more efficient data processing
posts.forEach(post => { const data = site.posts.sort('date', order).reduce((acc, post) => {
let date = post.date.clone() let date = post.date.clone()
if (timezone) date = date.tz(timezone) if (timezone) date = date.tz(timezone)
const year = date.year() const year = date.year()
const month = date.month() + 1 const month = date.month() + 1
if (!data.length || !compareFunc(data[data.length - 1].year, data[data.length - 1].month, year, month)) {
if (lang) date = date.locale(lang) if (lang) date = date.locale(lang)
data.push({ name: date.format(format), year, month, count: 1 })
} else {
data[data.length - 1].count++
}
})
const link = item => { // Find or create archive entry
const lastEntry = acc[acc.length - 1]
if (type === 'yearly') {
const existingYearIndex = acc.findIndex(entry => entry.year === year)
if (existingYearIndex !== -1) {
acc[existingYearIndex].count++
} else {
// 否則創建新條目
acc.push({
name: date.format(format),
year,
month,
count: 1
})
}
} else {
if (!lastEntry || !compareFunc(lastEntry.year, lastEntry.month, year, month)) {
acc.push({
name: date.format(format),
year,
month,
count: 1
})
} else {
lastEntry.count++
}
}
return acc
}, [])
// Create link generator function
const createArchiveLink = item => {
let url = `${archiveDir}/${item.year}/` let url = `${archiveDir}/${item.year}/`
if (type === 'monthly') { if (type === 'monthly') {
url += item.month < 10 ? `0${item.month}/` : `${item.month}/` url += item.month < 10 ? `0${item.month}/` : `${item.month}/`
} }
return url_for(url) return urlFor(url)
} }
const len = data.length // Limit results efficiently
const limitLength = limit === 0 ? len : Math.min(len, limit) const limitedData = limit > 0 ? data.slice(0, Math.min(data.length, limit)) : data
let result = ` // Use template literal for better readability
const archiveHeader = `
<div class="item-headline"> <div class="item-headline">
<i class="fas fa-archive"></i> <i class="fas fa-archive"></i>
<span>${_p('aside.card_archives')}</span> <span>${_p('aside.card_archives')}</span>
${len > limitLength ? `<a class="card-more-btn" href="${url_for(archiveDir)}/" title="${_p('aside.more_button')}"><i class="fas fa-angle-right"></i></a>` : ''} ${
data.length > limitedData.length
? `<a class="card-more-btn" href="${urlFor(archiveDir)}/"
title="${_p('aside.more_button')}">
<i class="fas fa-angle-right"></i>
</a>`
: ''
}
</div> </div>
<ul class="card-archive-list">
` `
for (let i = 0; i < limitLength; i++) { // Use map for generating list items, join for performance
const item = data[i] const archiveList = `
result += ` <ul class="card-archive-list">
${limitedData
.map(
item => `
<li class="card-archive-list-item"> <li class="card-archive-list-item">
<a class="card-archive-list-link" href="${link(item)}"> <a class="card-archive-list-link" href="${createArchiveLink(item)}">
<span class="card-archive-list-date">${options.transform ? options.transform(item.name) : item.name}</span> <span class="card-archive-list-date">
${transform ? transform(item.name) : item.name}
</span>
${showCount ? `<span class="card-archive-list-count">${item.count}</span>` : ''} ${showCount ? `<span class="card-archive-list-count">${item.count}</span>` : ''}
</a> </a>
</li> </li>
` `
} )
.join('')}
</ul>
`
result += '</ul>' return archiveHeader + archiveList
return result
}) })
const toMomentLocale = function (lang) { // Improved locale conversion function
if (!lang || lang === 'en' || lang === 'default') { const toMomentLocale = lang => {
return 'en' if (!lang || ['en', 'default'].includes(lang)) return 'en'
}
return lang.toLowerCase().replace('_', '-') return lang.toLowerCase().replace('_', '-')
} }

View File

@@ -4,9 +4,9 @@ hexo.extend.helper.register('inject_head_js', function () {
const { darkmode, aside, pjax } = this.theme const { darkmode, aside, pjax } = this.theme
const start = darkmode.start || 6 const start = darkmode.start || 6
const end = darkmode.end || 18 const end = darkmode.end || 18
const { theme_color } = hexo.theme.config const { theme_color: themeColor } = hexo.theme.config
const themeColorLight = theme_color && theme_color.enable ? theme_color.meta_theme_color_light : '#ffffff' const themeColorLight = themeColor && themeColor.enable ? themeColor.meta_theme_color_light : '#ffffff'
const themeColorDark = theme_color && theme_color.enable ? theme_color.meta_theme_color_dark : '#0d0d0d' const themeColorDark = themeColor && themeColor.enable ? themeColor.meta_theme_color_dark : '#0d0d0d'
const createCustomJs = () => ` const createCustomJs = () => `
const saveToLocal = { const saveToLocal = {

View File

@@ -13,7 +13,7 @@ hexo.extend.helper.register('postDesc', data => {
hexo.extend.helper.register('cloudTags', function (options = {}) { hexo.extend.helper.register('cloudTags', function (options = {}) {
const env = this 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) { if (limit > 0) {
source = source.limit(limit) 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)})` return `rgb(${Math.max(r, 50)}, ${Math.max(g, 50)}, ${Math.max(b, 50)})`
} }
const generateStyle = (size, unit) => const generateStyle = (size, unit, page) => {
`font-size: ${parseFloat(size.toFixed(2)) + unit}; color: ${getRandomColor()};` 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 length = sizes.length - 1
const result = source.sort(orderby, order).map(tag => { const result = source.sort(orderby, order).map(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)
const style = generateStyle(size, unit) const style = generateStyle(size, unit, page)
return `<a href="${env.url_for(tag.path)}" style="${style}">${tag.name}</a>` return `<a href="${env.url_for(tag.path)}" style="${style}">${tag.name}</a>`
}).join('') }).join('')
@@ -48,7 +53,7 @@ hexo.extend.helper.register('urlNoIndex', function (url = null, trailingIndex =
}) })
hexo.extend.helper.register('md5', function (path) { 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 => { 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') const defaultTitle = this._p('page.archives')
if (!menu) return defaultTitle if (!menu) return defaultTitle
const loop = (m) => { const loop = m => {
for (const key in m) { for (const key in m) {
if (typeof m[key] === 'object') { if (typeof m[key] === 'object') {
const result = loop(m[key]) const result = loop(m[key])
@@ -81,7 +86,7 @@ hexo.extend.helper.register('findArchivesTitle', function (page, menu, date) {
return loop(menu) || defaultTitle return loop(menu) || defaultTitle
}) })
hexo.extend.helper.register('getBgPath', path => { hexo.extend.helper.register('getBgPath', function (path) {
if (!path) return '' if (!path) return ''
const absoluteUrlPattern = /^(?:[a-z][a-z\d+.-]*:)?\/\//i const absoluteUrlPattern = /^(?:[a-z][a-z\d+.-]*:)?\/\//i
@@ -91,7 +96,7 @@ hexo.extend.helper.register('getBgPath', path => {
if (colorPattern.test(path)) { if (colorPattern.test(path)) {
return `background-color: ${path};` return `background-color: ${path};`
} else if (absoluteUrlPattern.test(path) || relativeUrlPattern.test(path)) { } else if (absoluteUrlPattern.test(path) || relativeUrlPattern.test(path)) {
return `background-image: url(${path});` return `background-image: url(${this.url_for(path)});`
} else { } else {
return `background: ${path};` return `background: ${path};`
} }
@@ -127,7 +132,37 @@ hexo.extend.helper.register('shuoshuoFN', (data, page) => {
finalResult.forEach(item => { finalResult.forEach(item => {
const utcDate = moment.utc(item.date).format('YYYY-MM-DD HH:mm:ss') 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') 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 return finalResult
}) })
hexo.extend.helper.register('getPageType', (page, isHome) => {
const { layout, tag, category, type, archive } = page
if (layout) return layout
if (tag) return 'tag'
if (category) return 'category'
if (archive) return 'archive'
if (type) {
if (type === 'tags' || type === 'categories') return type
else return 'page'
}
if (isHome) return 'home'
return 'post'
})
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 <script> tags
return JSON.stringify(data)
.replace(/</g, '\\u003c')
.replace(/>/g, '\\u003e')
.replace(/\u2028/g, '\\u2028')
.replace(/\u2029/g, '\\u2029')
})

View File

@@ -3,7 +3,7 @@
* galleryGroup and gallery * galleryGroup and gallery
* {% galleryGroup [name] [descr] [url] [img] %} * {% galleryGroup [name] [descr] [url] [img] %}
* *
* {% gallery [button],%} * {% gallery [button],[limit],[firstLimit] %}
* {% gallery url,[url],[button] %} * {% gallery url,[url],[button] %}
*/ */
@@ -11,54 +11,66 @@
const urlFor = require('hexo-util').url_for.bind(hexo) const urlFor = require('hexo-util').url_for.bind(hexo)
const gallery = (args, content) => { const DEFAULT_LIMIT = 10
args = args.join(' ').split(',') const DEFAULT_FIRST_LIMIT = 10
let button = false const IMAGE_REGEX = /!\[(.*?)\]\(([^\s]*)\s*(?:["'](.*?)["']?)?\s*\)/g
let type = 'data'
let dataStr = ''
if (args[0] === 'url') { // Helper functions
[type, dataStr, button] = args // url,[link],[lazyload] const parseGalleryArgs = args => {
dataStr = urlFor(dataStr) const [type, ...rest] = args.join(' ').split(',').map(arg => arg.trim())
} else { return {
[button] = args // [lazyload] isUrl: type === 'url',
const regex = /!\[(.*?)\]\(([^\s]*)\s*(?:["'](.*?)["']?)?\s*\)/g params: type === 'url' ? rest : [type, ...rest]
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], const parseImageContent = content => {
title: m[3] const images = []
let match
while ((match = IMAGE_REGEX.exec(content)) !== null) {
images.push({
url: match[2],
alt: match[1] || '',
title: match[3] || ''
}) })
} }
dataStr = JSON.stringify(arr) return images
} }
return `<div class="gallery-container" data-type="${type}" data-button="${button}"> const createGalleryHTML = (type, dataStr, button, limit, firstLimit) => {
return `<div class="gallery-container" data-type="${type}" data-button="${button}" data-limit="${limit}" data-first="${firstLimit}">
<div class="gallery-items">${dataStr}</div> <div class="gallery-items">${dataStr}</div>
</div>` </div>`
} }
const gallery = (args, content) => {
const { isUrl, params } = parseGalleryArgs(args)
if (isUrl) {
const [dataStr, button = false, limit = DEFAULT_LIMIT, firstLimit = DEFAULT_FIRST_LIMIT] = params
return createGalleryHTML('url', urlFor(dataStr), button, limit, firstLimit)
}
const [button = false, limit = DEFAULT_LIMIT, firstLimit = DEFAULT_FIRST_LIMIT] = params
const images = parseImageContent(content)
return createGalleryHTML('data', JSON.stringify(images), button, limit, firstLimit)
}
const galleryGroup = args => { const galleryGroup = args => {
const [name, descr, url, img] = args const [name = '', descr = '', url = '', img = ''] = args.map(arg => arg.trim())
const imgUrl = urlFor(img)
const urlLink = urlFor(url)
return `<figure class="gallery-group"> return `<figure class="gallery-group">
<img class="gallery-group-img no-lightbox" src='${imgUrl}' alt="Group Image Gallery"> <img class="gallery-group-img no-lightbox" src='${urlFor(img)}' alt="Group Image Gallery">
<figcaption> <figcaption>
<div class="gallery-group-name">${name}</div> <div class="gallery-group-name">${name}</div>
<p>${descr}</p> <p>${descr}</p>
<a href='${urlLink}'></a> <a href='${urlFor(url)}'></a>
</figcaption> </figcaption>
</figure> </figure>`
`
} }
// Register tags
hexo.extend.tag.register('gallery', gallery, { ends: true }) hexo.extend.tag.register('gallery', gallery, { ends: true })
hexo.extend.tag.register('galleryGroup', galleryGroup) hexo.extend.tag.register('galleryGroup', galleryGroup)

View File

@@ -16,48 +16,35 @@
'use strict' 'use strict'
const parseArgs = args => { const parseArgs = args => args.join(' ').split(',')
return args.join(' ').split(',')
}
const generateStyle = (bg, color) => { const generateStyle = (bg, color) => {
let style = 'style="' let style = 'style="'
if (bg) { if (bg) style += `background-color: ${bg};`
style += `background-color: ${bg};` if (color) style += `color: ${color}`
}
if (color) {
style += `color: ${color}`
}
style += '"' style += '"'
return style return style
} }
const hideInline = args => { const hideInline = args => {
const [content, display = 'Click', bg = false, color = false] = parseArgs(args) const [content, display = 'Click', bg = false, color = false] = parseArgs(args)
const group = generateStyle(bg, color) const style = generateStyle(bg, color)
return `<span class="hide-inline"><button type="button" class="hide-button" ${style}>${display}</button><span class="hide-content">${content}</span></span>`
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 hideBlock = (args, content) => {
const [display = 'Click', bg = false, color = false] = parseArgs(args) const [display = 'Click', bg = false, color = false] = parseArgs(args)
const group = generateStyle(bg, color) const style = generateStyle(bg, color)
const renderedContent = hexo.render.renderSync({ text: content, engine: 'markdown' })
return `<div class="hide-block"><button type="button" class="hide-button" ${group}>${display} return `<div class="hide-block"><button type="button" class="hide-button" ${style}>${display}</button><div class="hide-content">${renderedContent}</div></div>`
</button><div class="hide-content">${hexo.render.renderSync({ text: content, engine: 'markdown' })}</div></div>`
} }
const hideToggle = (args, content) => { const hideToggle = (args, content) => {
const [display, bg = false, color = false] = parseArgs(args) const [display, bg = false, color = false] = parseArgs(args)
const group = generateStyle(bg, color) const style = generateStyle(bg, color)
let border = '' const border = bg ? `style="border: 1px solid ${bg}"` : ''
const renderedContent = hexo.render.renderSync({ text: content, engine: 'markdown' })
if (bg) { return `<details class="toggle" ${border}><summary class="toggle-button" ${style}>${display}</summary><div class="toggle-content">${renderedContent}</div></details>`
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('hideInline', hideInline)

View File

@@ -1,9 +1,9 @@
/** /**
* inlineImg 圖片 * inlineImg
* @param {Array} args 圖片名稱和高度 * @param {Array} args - Image name and height
* @param {string} args[0] 圖片名稱 * @param {string} args[0] - Image name
* @param {number} args[1] 圖片高度 * @param {number} args[1] - Image height
* @returns {string} 圖片標籤 * @returns {string} - Image tag
*/ */
'use strict' 'use strict'

View File

@@ -9,7 +9,8 @@
const { escapeHTML } = require('hexo-util') const { escapeHTML } = require('hexo-util')
const mermaid = (args, content) => { const mermaid = (args, content) => {
return `<div class="mermaid-wrap"><pre class="mermaid-src" hidden> const config = args[0] || '{}'
return `<div class="mermaid-wrap"><pre class="mermaid-src" data-config="${escapeHTML(config)}" hidden>
${escapeHTML(content)} ${escapeHTML(content)}
</pre></div>` </pre></div>`
} }

View File

@@ -6,17 +6,45 @@
'use strict' 'use strict'
const score = (args, content) => { const score = (args, content) => {
// Escape HTML tags and some special characters, including curly braces
const escapeHtmlTags = s => { const escapeHtmlTags = s => {
const lookup = { const lookup = {
'&': '&amp;', '&': '&amp;',
'"': '&quot;', '"': '&quot;',
'\'': '&apos;', "'": '&apos;',
'<': '&lt;', '<': '&lt;',
'>': '&gt;' '>': '&gt;',
'{': '&#123;',
'}': '&#125;'
} }
return s.replace(/[&"'<>]/g, c => lookup[c]) return s.replace(/[&"'<>{}]/g, c => lookup[c])
} }
return `<div class="abc-music-sheet">${escapeHtmlTags(content)}</div>`
const trimmed = content.trim()
// Split content using six dashes as a delimiter
const parts = trimmed.split('------')
if (parts.length < 2) {
// If no delimiter is found, treat the entire content as the score
return `<div class="abc-music-sheet">${escapeHtmlTags(trimmed)}</div>`
}
// First part is parameters (JSON string), the rest is the score content
const paramPart = parts[0].trim()
const scorePart = parts.slice(1).join('------').trim()
let paramsObj = {}
try {
paramsObj = JSON.parse(paramPart)
} catch (e) {
console.error('Failed to parse JSON in score tag:', e)
}
// Use double quotes for data-params attribute value,
// ensuring JSON internal double quotes are escaped
return `<div class="abc-music-sheet" data-params="${escapeHtmlTags(JSON.stringify(paramsObj))}">
${escapeHtmlTags(scorePart)}
</div>`
} }
hexo.extend.tag.register('score', score, { ends: true }) hexo.extend.tag.register('score', score, { ends: true })

View File

@@ -1,41 +1,50 @@
/** /**
* timeline * Timeline tag for Hexo
* by Jerry * Syntax:
* {% timeline [headline],[color] %}
* <!-- timeline [title] -->
* [content]
* <!-- endtimeline -->
* <!-- timeline [title] -->
* [content]
* <!-- endtimeline -->
* {% endtimeline %}
*/ */
'use strict' 'use strict'
const timeLineFn = (args, content) => { const timeLineFn = (args, content) => {
const tlBlock = /<!--\s*timeline (.*?)\s*-->\n([\w\W\s\S]*?)<!--\s*endtimeline\s*-->/g // Use named capture groups for better readability
const tlBlock = /<!--\s*timeline\s*(?<title>.*?)\s*-->\n(?<content>[\s\S]*?)<!--\s*endtimeline\s*-->/g
let result = '' // Pre-compile markdown render function
let color = '' const renderMd = text => hexo.render.renderSync({ text, engine: 'markdown' })
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 = [] // Parse arguments more efficiently
let match const [text, color = ''] = args.length ? args.join(' ').split(',') : []
while ((match = tlBlock.exec(content)) !== null) { // Build initial headline if text exists
matches.push(match[1]) const headline = text
matches.push(match[2]) ? `<div class='timeline-item headline'>
} <div class='timeline-item-title'>
<div class='item-circle'>${renderMd(text)}</div>
</div>
</div>`
: ''
for (let i = 0; i < matches.length; i += 2) { // Match all timeline blocks in one pass and transform
const tlChildTitle = hexo.render.renderSync({ text: matches[i], engine: 'markdown' }) const items = Array.from(content.matchAll(tlBlock))
const tlChildContent = hexo.render.renderSync({ text: matches[i + 1], engine: 'markdown' }) .map(({ groups: { title, content } }) =>
`<div class='timeline-item'>
<div class='timeline-item-title'>
<div class='item-circle'>${renderMd(title)}</div>
</div>
<div class='timeline-item-content'>${renderMd(content)}</div>
</div>`
)
.join('')
const tlTitleHtml = `<div class='timeline-item-title'><div class='item-circle'>${tlChildTitle}</div></div>` return `<div class="timeline ${color}">${headline}${items}</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 }) hexo.extend.tag.register('timeline', timeLineFn, { ends: true })

View File

@@ -11,7 +11,7 @@
.fontawesomeIcon .fontawesomeIcon
display: inline-block display: inline-block
font-weight: 600 font-weight: 600
font-family: 'Font Awesome 6 Free' font-family: 'Font Awesome 7 Free', 'Font Awesome 6 Free'
text-rendering: auto text-rendering: auto
-webkit-font-smoothing: antialiased -webkit-font-smoothing: antialiased
@@ -181,6 +181,60 @@ if hexo-config('avatar.effect') == true
.reward-main .reward-main
animation: donate_effcet .3s .1s ease both animation: donate_effcet .3s .1s ease both
.btn-effects
position: relative
overflow: hidden
transition: all .3s cubic-bezier(.4, 0, .2, 1)
transform: translateZ(0)
&:hover
box-shadow: 0 4px 12px rgba(0, 0, 0, .15)
text-decoration: none
transform: translateY(-1px) scale(1.02)
&:active
transition-duration: .1s
transform: translateY(0) scale(.98)
i
display: inline-block
vertical-align: middle
transition: all .3s cubic-bezier(.4, 0, .2, 1)
&:hover i
animation: buttonIconBounce .6s ease-in-out
i + span
margin-left: 6px
vertical-align: middle
transition: margin-left .3s cubic-bezier(.4, 0, .2, 1)
&:hover i + span
margin-left: 8px
&::before
position: absolute
top: 0
left: -100%
z-index: 1
width: 100%
height: 100%
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .2), transparent)
content: ''
transition: left .5s cubic-bezier(.4, 0, .2, 1)
&:hover::before
left: 100%
& > *
position: relative
z-index: 2
.btn-effects-large
&:hover
box-shadow: 0 6px 16px rgba(0, 0, 0, .2)
transform: translateY(-2px) scale(1.03)
@keyframes scroll-down-effect @keyframes scroll-down-effect
0% 0%
opacity: .4 opacity: .4
@@ -278,3 +332,17 @@ if hexo-config('avatar.effect') == true
100% 100%
transform: translateX(0) transform: translateX(0)
@keyframes buttonIconBounce
0%,
100%
transform: translateY(0) scale(1)
25%
transform: translateY(-3px) scale(1.1) rotateZ(-5deg)
50%
transform: translateY(0) scale(1.05) rotateZ(0)
75%
transform: translateY(-1px) scale(1.02) rotateZ(2deg)

View File

@@ -10,8 +10,8 @@
--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
--tab-botton-bg: $tab-botton-bg --tab-button-bg: $tab-button-bg
--tab-botton-color: $tab-botton-color --tab-button-color: $tab-button-color
--tab-button-hover-bg: $tab-button-hover-bg --tab-button-hover-bg: $tab-button-hover-bg
--tab-button-active-bg: $tab-button-active-bg --tab-button-active-bg: $tab-button-active-bg
--card-bg: $card-bg --card-bg: $card-bg
@@ -39,6 +39,65 @@
--zoom-bg: #fff --zoom-bg: #fff
--mark-bg: alpha($dark-black, .3) --mark-bg: alpha($dark-black, .3)
// tags plugin
:root
--btn-color: $btn-color
--btn-default-color: $btn-default-color
--tags-blue-color: $tagsP-blue-color
--tags-blue-color-lighten: lighten($tagsP-blue-color, 85%)
--tags-pink-color: $tagsP-pink-color
--tags-pink-color-lighten: lighten($tagsP-pink-color, 85%)
--tags-red-color: $tagsP-red-color
--tags-red-color-lighten: lighten($tagsP-red-color, 85%)
--tags-orange-color: $tagsP-orange-color
--tags-orange-color-lighten: lighten($tagsP-orange-color, 85%)
--tags-purple-color: $tagsP-purple-color
--tags-purple-color-lighten: lighten($tagsP-purple-color, 85%)
--tags-green-color: $tagsP-green-color
--tags-green-color-lighten: lighten($tagsP-green-color, 85%)
--note-default-border: $note-default-border
--note-default-bg: $note-default-bg
--note-default-text: $note-default-text
--note-modern-default-border: $note-modern-default-border
--note-modern-default-bg: $note-modern-default-bg
--note-modern-default-text: $note-modern-default-text
--note-modern-default-hover: $note-modern-default-hover
--note-primary-border: $note-primary-border
--note-primary-bg: $note-primary-bg
--note-primary-text: $note-primary-text
--note-modern-primary-border: $note-modern-primary-border
--note-modern-primary-bg: $note-modern-primary-bg
--note-modern-primary-text: $note-modern-primary-text
--note-modern-primary-hover: $note-modern-primary-hover
--note-info-border: $note-info-border
--note-info-bg: $note-info-bg
--note-info-text: $note-info-text
--note-modern-info-border: $note-modern-info-border
--note-modern-info-bg: $note-modern-info-bg
--note-modern-info-text: $note-modern-info-text
--note-modern-info-hover: $note-modern-info-hover
--note-success-border: $note-success-border
--note-success-bg: $note-success-bg
--note-success-text: $note-success-text
--note-modern-success-border: $note-modern-success-border
--note-modern-success-bg: $note-modern-success-bg
--note-modern-success-text: $note-modern-success-text
--note-modern-success-hover: $note-modern-success-hover
--note-warning-border: $note-warning-border
--note-warning-bg: $note-warning-bg
--note-warning-text: $note-warning-text
--note-modern-warning-border: $note-modern-warning-border
--note-modern-warning-bg: $note-modern-warning-bg
--note-modern-warning-text: $note-modern-warning-text
--note-modern-warning-hover: $note-modern-warning-hover
--note-danger-border: $note-danger-border
--note-danger-bg: $note-danger-bg
--note-danger-text: $note-danger-text
--note-modern-danger-border: $note-modern-danger-border
--note-modern-danger-bg: $note-modern-danger-bg
--note-modern-danger-text: $note-modern-danger-text
--note-modern-danger-hover: $note-modern-danger-hover
body body
position: relative position: relative
overflow-y: scroll overflow-y: scroll
@@ -222,3 +281,7 @@ blockquote
& > :last-child & > :last-child
margin-bottom: 0 !important margin-bottom: 0 !important
.fa-fw
width: 1.25em
text-align: center

View File

@@ -42,7 +42,7 @@ $code-block
counter-reset: line counter-reset: line
white-space: pre-wrap white-space: pre-wrap
#article-container .container
pre, pre,
code code
font-size: $code-font-size font-size: $code-font-size
@@ -100,16 +100,17 @@ $code-block
.highlight-tools .highlight-tools
display: flex display: flex
align-items: center align-items: center
overflow: hidden
padding: 0 8px padding: 0 8px
min-height: 24px min-height: 24px
height: 2.15em height: 2.15em
background: var(--hltools-bg) background: var(--hltools-bg)
color: var(--hltools-color) color: var(--hltools-color)
font-size: $code-font-size font-size: $code-font-size
overflow: hidden
& > * & > *
padding: 5px flex: 0 0 auto
margin: 2px
i i
cursor: pointer cursor: pointer
@@ -130,28 +131,25 @@ $code-block
padding: 0 padding: 0
.code-lang .code-lang
flex: 1 flex: 1 1 auto
overflow: hidden
padding-right: 10px
text-transform: uppercase text-transform: uppercase
text-overflow: ellipsis
white-space: nowrap
font-weight: bold font-weight: bold
font-size: 1.15em font-size: 1.15em
user-select: none user-select: none
-webkit-user-select: none -webkit-user-select: none
padding 2px
.copy-notice
padding-right: 2px
opacity: 0
transition: opacity .4s
if hexo-config('code_blocks.language') if hexo-config('code_blocks.language')
.code-lang margin-right: auto
flex: 1 else if !$highlight_macstyle && hexo-config('code_blocks.shrink') != 'none'
else if (!$highlight_macstyle && hexo-config('code_blocks.shrink') != 'none') & > :nth-child(2)
& > div:nth-child(2) margin-right: auto
flex: 1
else else
.macStyle & > :nth-child(1)
flex: 1 margin-right: auto
.gutter .gutter
user-select: none user-select: none
@@ -163,11 +161,21 @@ $code-block
td td
border: none border: none
.copy-notice
position: absolute
z-index: 99999
padding: 2px 6px
border-radius: 3px
background: var(--hltools-bg)
white-space: nowrap
font-size: 12px
pointer-events: none
if $highlight_macstyle if $highlight_macstyle
#article-container .container
figure.highlight figure.highlight
margin: 0 0 24px margin: 0 0 24px
border-radius: 7px border-radius: 8px
box-shadow: 0 5px 10px 0 $highlight-mac-border box-shadow: 0 5px 10px 0 $highlight-mac-border
-webkit-transform: translateZ(0) -webkit-transform: translateZ(0)
@@ -202,7 +210,7 @@ if $highlight_macstyle
transform: rotate(90deg) transform: rotate(90deg)
if hexo-config('code_blocks.height_limit') if hexo-config('code_blocks.height_limit')
#article-container .container
.code-expand-btn .code-expand-btn
position: absolute position: absolute
bottom: 0 bottom: 0
@@ -243,7 +251,7 @@ if hexo-config('code_blocks.height_limit')
opacity: .6 opacity: .6
if hexo-config('code_blocks.fullpage') if hexo-config('code_blocks.fullpage')
#article-container .container
figure.highlight.code-fullpage figure.highlight.code-fullpage
position: fixed position: fixed
top: 0 top: 0
@@ -264,8 +272,8 @@ if hexo-config('code_blocks.fullpage')
& ~ table & ~ table
display: block display: block
overflow: auto overflow: auto
height: calc(100vh - 2.15em)
margin-bottom: 0 margin-bottom: 0
height: calc(100vh - 2.15em)
@keyframes code-fullpage @keyframes code-fullpage
0%, 0%,

View File

@@ -1,7 +1,7 @@
if $highlight_theme != false if $highlight_theme != false
@require 'diff' @require 'diff'
#article-container .container
figure.highlight figure.highlight
.line .line
if wordWrap if wordWrap

View File

@@ -4,7 +4,7 @@ if $prismjs_line_number
if $highlight_theme != false if $highlight_theme != false
@require 'diff' @require 'diff'
#article-container .container
pre[class*='language-'] pre[class*='language-']
// scrollbar - firefox // scrollbar - firefox
@-moz-document url-prefix() @-moz-document url-prefix()

View File

@@ -1,4 +1,4 @@
#article-container .container
pre[class*='language-'] pre[class*='language-']
&.line-numbers &.line-numbers
position: relative position: relative

View File

@@ -23,8 +23,8 @@
&:not(#card-toc) &:not(#card-toc)
display: none display: none
&:last-child // &:last-child
margin-bottom: 0 // margin-bottom: 0
.card-info .card-info
.author-info .author-info
@@ -60,6 +60,7 @@
text-align: center text-align: center
line-height: 2.4 line-height: 2.4
addBorderRadius(7) addBorderRadius(7)
@extend .btn-effects
&:hover &:hover
background-color: var(--btn-hover-color) background-color: var(--btn-hover-color)
@@ -180,6 +181,11 @@
.card-category-list .card-category-list
&.child &.child
padding: 0 0 0 16px padding: 0 0 0 16px
overflow: hidden
max-height: 0
opacity: 0
visibility: hidden
transition: max-height .3s ease, opacity .3s ease
> .parent > .parent
> a > a
@@ -188,7 +194,9 @@
transform: rotate(-90deg) transform: rotate(-90deg)
& + .child & + .child
display: block max-height: 1000px
opacity: 1
visibility: visible
.card-category-list .card-category-list
&-name &-name
@@ -207,7 +215,9 @@
if hexo-config('aside.card_categories.expand') == false if hexo-config('aside.card_categories.expand') == false
> .child > .child
display: none max-height: 0
opacity: 0
visibility: hidden
.card-webinfo .card-webinfo
.webinfo .webinfo

Some files were not shown because too many files have changed in this diff Show More