mirror of
https://github.com/jerryc127/hexo-theme-butterfly.git
synced 2026-04-12 14:07:06 +08:00
Compare commits
130 Commits
5.0.0
...
copilot/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bffdd3ea22 | ||
|
|
13315f6710 | ||
|
|
e3e1d9e6ce | ||
|
|
954598b45c | ||
|
|
b6afb6edad | ||
|
|
b06e704281 | ||
|
|
e4597deded | ||
|
|
b034637bc1 | ||
|
|
bdc97cc22b | ||
|
|
5878e3b7ee | ||
|
|
d15620198c | ||
|
|
3b4b143c27 | ||
|
|
2bab33d312 | ||
|
|
b0951197a9 | ||
|
|
572321a00f | ||
|
|
e7f796e110 | ||
|
|
02c8e8e3b5 | ||
|
|
edbd4d10c3 | ||
|
|
2cdf93c58c | ||
|
|
2c5e8c7cbb | ||
|
|
80da110698 | ||
|
|
0cc950a111 | ||
|
|
dd2d051b3d | ||
|
|
3f3102d10b | ||
|
|
c771efa9bc | ||
|
|
1ebad6b1b7 | ||
|
|
10c967957b | ||
|
|
f1397da086 | ||
|
|
bb7b206369 | ||
|
|
586805ceb2 | ||
|
|
7dc7942230 | ||
|
|
8ae57ac9bf | ||
|
|
57a7db7a52 | ||
|
|
4225d23cb6 | ||
|
|
c882e84cd2 | ||
|
|
29a6fa455a | ||
|
|
7985bdda9e | ||
|
|
bdf77e328d | ||
|
|
65a077a00d | ||
|
|
17fc32b59d | ||
|
|
83574c75ad | ||
|
|
eb263085cf | ||
|
|
15b7a018c6 | ||
|
|
439014bbb6 | ||
|
|
2d4765202d | ||
|
|
5e766ac40a | ||
|
|
4c3a782610 | ||
|
|
8645a4355d | ||
|
|
4226c95818 | ||
|
|
67c95cee0c | ||
|
|
0a72f43fae | ||
|
|
006414da98 | ||
|
|
c61f55a773 | ||
|
|
6feac51de7 | ||
|
|
fcd760011a | ||
|
|
ec1a226774 | ||
|
|
5ee24defc3 | ||
|
|
58818a0630 | ||
|
|
a551b9277f | ||
|
|
1b5bc97431 | ||
|
|
0da72787fa | ||
|
|
0e9b8f5b69 | ||
|
|
89b3626c84 | ||
|
|
c5417d4532 | ||
|
|
dd8a18e8f4 | ||
|
|
a61e216452 | ||
|
|
b89f165be3 | ||
|
|
d2eacd2d8a | ||
|
|
5bfc1da03b | ||
|
|
21c238e5c1 | ||
|
|
60fa703fd3 | ||
|
|
7353de6c2e | ||
|
|
a9fe9f5332 | ||
|
|
73de62a6e1 | ||
|
|
cf059bd533 | ||
|
|
8cb726ddaa | ||
|
|
baee803689 | ||
|
|
023d82820d | ||
|
|
b7f1610859 | ||
|
|
5bac44a284 | ||
|
|
9c5294b40c | ||
|
|
a7315b6bfc | ||
|
|
cb3778c686 | ||
|
|
2ce969023c | ||
|
|
7c697c15e8 | ||
|
|
ca030589fb | ||
|
|
a3f6b625ed | ||
|
|
628d1bbe52 | ||
|
|
ede4f8bfea | ||
|
|
feb3346cf6 | ||
|
|
576fa5c80e | ||
|
|
6019cc8b7c | ||
|
|
7f9409553a | ||
|
|
2de7d34b2b | ||
|
|
4b8a610c08 | ||
|
|
13720bd94d | ||
|
|
3028b08526 | ||
|
|
f605e6dc89 | ||
|
|
92913a6193 | ||
|
|
1e20234d74 | ||
|
|
1f3ea55890 | ||
|
|
3d4bf30948 | ||
|
|
172a8ee846 | ||
|
|
5331f4ef9e | ||
|
|
0d0001c808 | ||
|
|
13db106172 | ||
|
|
f42048792e | ||
|
|
247c1b664d | ||
|
|
f7483d59b5 | ||
|
|
b7771e5938 | ||
|
|
74c555fb37 | ||
|
|
648ca6eb4f | ||
|
|
e5a52d5621 | ||
|
|
1045f66a51 | ||
|
|
5338a2be99 | ||
|
|
3ea138178d | ||
|
|
91c8c5cd4b | ||
|
|
fb4ab20169 | ||
|
|
7b6a386a14 | ||
|
|
f91ce41a66 | ||
|
|
d7bfcf36c9 | ||
|
|
c72f8c41ec | ||
|
|
95ca6878f0 | ||
|
|
3d7a033721 | ||
|
|
90b4511193 | ||
|
|
e65481ecbd | ||
|
|
9398cf5190 | ||
|
|
0f87df3b3a | ||
|
|
f62d76046d | ||
|
|
975134fb14 |
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -10,4 +10,4 @@ liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
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
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
.DS_Store
|
||||
node_modules/
|
||||
225
README.md
225
README.md
@@ -1,116 +1,193 @@
|
||||
<div align="right">
|
||||
<a title="Chinese" href="/README_CN.md">中文</a>
|
||||
<a title="中文" href="/README_CN.md">中文</a>
|
||||
</div>
|
||||
|
||||
<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
|
||||
|
||||
A modern, elegant and feature-rich theme for Hexo
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
📢 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/)
|
||||
|
||||

|
||||

|
||||
|
||||
</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
|
||||
```
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
### 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
|
||||
|
||||
```powershell
|
||||
npm i hexo-theme-butterfly
|
||||
```bash
|
||||
npm install 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
|
||||
- [x] Rounded Design/Squared Design
|
||||
- [X] Support sub-menu
|
||||
- [x] Two-column layout
|
||||
- [x] Responsive Web Design
|
||||
- [x] Dark 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/Daovoice/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] Justified Gallery
|
||||
- [x] Lazyload images
|
||||
- [x] Instantpage/Pangu/Snackbar notification toast/PWA......
|
||||
### 🎨 Design Style
|
||||
- [x] **Card-based Design** - Modern card-style layout
|
||||
- [x] **Rounded/Square Design** - Customizable border styles
|
||||
- [x] **Responsive Design** - Perfect adaptation to all screen sizes
|
||||
- [x] **Two-column Layout** - Optimized reading experience
|
||||
- [x] **Dark Mode** - Eye-friendly night mode
|
||||
|
||||
## ✨ 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">
|
||||
<img src="https://contrib.rocks/image?repo=jerryc127/hexo-theme-butterfly" />
|
||||
</a>
|
||||
### 🔍 Search & Navigation
|
||||
- [x] **Multiple Search Options** - Algolia Search / Local Search / Docsearch
|
||||
- [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
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
### 💬 Social Interaction
|
||||
- [x] **Multiple Comment Systems** - Disqus/Gitalk/Valine/Waline/Twikoo/Giscus/Artalk etc.
|
||||
- [x] **Dual Comments Support** - Enable two comment systems simultaneously
|
||||
- [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!
|
||||
|
||||
[](https://github.com/jerryc127/hexo-theme-butterfly/graphs/contributors)
|
||||
|
||||
## 📸 Screenshots
|
||||
|
||||
<div align="center">
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
|
||||
## ⭐ Star History
|
||||
|
||||
[](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>
|
||||
|
||||
221
README_CN.md
221
README_CN.md
@@ -4,113 +4,190 @@
|
||||
|
||||
<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 的現代化、美觀且功能豐富的主題
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
📢 預覽: [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/)
|
||||
|
||||

|
||||

|
||||
|
||||
</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
|
||||
```
|
||||
|
||||
如果想要安裝比較新的dev分支,可以
|
||||
|
||||
```powershell
|
||||
```bash
|
||||
# 安裝開發版本(搶先體驗新功能)
|
||||
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 及以上版本
|
||||
|
||||
在博客根目錄裡
|
||||
|
||||
```powershell
|
||||
npm i hexo-theme-butterfly
|
||||
```bash
|
||||
npm install hexo-theme-butterfly
|
||||
```
|
||||
|
||||
## ⚙ 應用主題
|
||||
### ⚙️ 主題配置
|
||||
|
||||
修改hexo配置文件`_config.yml`,把主題改為`Butterfly`
|
||||
1. **啟用主題**: 修改您的 Hexo 配置檔案 `_config.yml`:
|
||||
|
||||
```
|
||||
```yaml
|
||||
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/Daovoice/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] 照片牆
|
||||
- [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" />
|
||||
</a>
|
||||
### 📝 內容功能
|
||||
- [x] **多級選單** - 支援二級導航選單
|
||||
- [x] **閱讀模式** - 專注的文章閱讀體驗
|
||||
- [x] **目錄導航** - 電腦和手機雙端支援 TOC
|
||||
- [x] **字數統計** - 顯示文章字數和閱讀時間
|
||||
- [x] **相關文章** - 智能推薦相關內容
|
||||
- [x] **過期提醒** - 自動提示文章更新狀態
|
||||
- [x] **簡繁轉換** - 支援繁體中文和簡體中文切換
|
||||
- [x] **標籤外掛** - 豐富的標籤外掛支持
|
||||
|
||||
## 📷 截圖
|
||||
### 🔍 搜尋與導航
|
||||
- [x] **多種搜尋** - Algolia 搜尋 / 本地搜尋 / Docsearch
|
||||
- [x] **內建 404** - 美觀的 404 錯誤頁面
|
||||
- [x] **Pjax 支援** - 流暢的頁面切換體驗
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
### 🎨 程式碼展示
|
||||
- [x] **語法高亮** - 內建多種主題(darker/pale night/light/ocean)
|
||||
- [x] **程式碼功能** - 語言顯示/摺疊展開/複製按鈕/自動換行
|
||||
- [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 主題做出貢獻的開發者們!
|
||||
|
||||
[](https://github.com/jerryc127/hexo-theme-butterfly/graphs/contributors)
|
||||
|
||||
## 📸 主題截圖
|
||||
|
||||
<div align="center">
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
|
||||
## ⭐ Star 趨勢
|
||||
|
||||
[](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>
|
||||
|
||||
88
_config.yml
88
_config.yml
@@ -13,6 +13,7 @@ nav:
|
||||
# Navigation bar logo image
|
||||
logo:
|
||||
display_title: true
|
||||
display_post_title: true
|
||||
# Whether to fix navigation bar
|
||||
fixed: false
|
||||
|
||||
@@ -90,7 +91,8 @@ category_per_img:
|
||||
footer_img: false
|
||||
|
||||
# Website Background
|
||||
# Can set it to color or image url
|
||||
# Can set it to color, image URL or an array containing colors and/or image URLs
|
||||
# If an array is provided, a random background will be selected from the array on each load
|
||||
background:
|
||||
|
||||
cover:
|
||||
@@ -158,7 +160,7 @@ subtitle:
|
||||
# Choose: false/1/2/3
|
||||
# false - disable the function
|
||||
# 1 - hitokoto.cn
|
||||
# 2 - yijuzhan.com
|
||||
# 2 - https://api.aa1.cn/doc/yiyan.html
|
||||
# 3 - jinrishici.com
|
||||
source: false
|
||||
# If you close the typewriter effect, the subtitle will only show the first line of sub
|
||||
@@ -254,12 +256,15 @@ noticeOutdate:
|
||||
# Footer Settings
|
||||
# --------------------------------------
|
||||
footer:
|
||||
nav:
|
||||
owner:
|
||||
enable: true
|
||||
since: 2019
|
||||
custom_text:
|
||||
since: 2025
|
||||
# Copyright of theme and framework
|
||||
copyright: true
|
||||
copyright:
|
||||
enable: true
|
||||
version: true
|
||||
custom_text:
|
||||
|
||||
# --------------------------------------
|
||||
# Aside Settings
|
||||
@@ -314,6 +319,7 @@ aside:
|
||||
# If set 0 will show all
|
||||
limit: 40
|
||||
color: false
|
||||
custom_colors:
|
||||
# Order of tags, random/name/length
|
||||
orderby: random
|
||||
# Sort of order. 1, asc for ascending; -1, desc for descending
|
||||
@@ -399,6 +405,9 @@ rightside_item_order:
|
||||
# Default: toc,chat,comment
|
||||
show:
|
||||
|
||||
# Animation for the bottom right config button
|
||||
rightside_config_animation: true
|
||||
|
||||
# --------------------------------------
|
||||
# Global Settings
|
||||
# --------------------------------------
|
||||
@@ -481,6 +490,11 @@ search:
|
||||
top_n_per_article: 1
|
||||
# Unescape html strings to the readable one.
|
||||
unescape: false
|
||||
# Enable pagination for search results
|
||||
pagination:
|
||||
enable: false
|
||||
# Number of search results per page
|
||||
hitsPerPage: 8
|
||||
CDN:
|
||||
|
||||
# Docsearch
|
||||
@@ -503,12 +517,12 @@ share:
|
||||
# Share.js
|
||||
# https://github.com/overtrue/share.js
|
||||
sharejs:
|
||||
sites: facebook,twitter,wechat,weibo,qq
|
||||
sites: facebook,x,wechat,weibo,qq
|
||||
|
||||
# AddToAny
|
||||
# https://www.addtoany.com/
|
||||
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
|
||||
@@ -602,7 +616,7 @@ facebook_comments:
|
||||
pageSize: 10
|
||||
# Choose: social / time / reverse_time
|
||||
order_by: social
|
||||
lang: zh_TW
|
||||
lang: en_US
|
||||
|
||||
# Twikoo
|
||||
# https://github.com/imaegoo/twikoo
|
||||
@@ -645,7 +659,7 @@ artalk:
|
||||
# --------------------------------------
|
||||
|
||||
chat:
|
||||
# Choose: chatra/tidio/daovoice/crisp
|
||||
# Choose: chatra/tidio/crisp
|
||||
# Leave it empty if you don't need chat
|
||||
use:
|
||||
# Chat Button [recommend]
|
||||
@@ -662,10 +676,6 @@ chatra:
|
||||
tidio:
|
||||
public_key:
|
||||
|
||||
# http://dashboard.daovoice.io/app
|
||||
daovoice:
|
||||
app_id:
|
||||
|
||||
# https://crisp.chat/en/
|
||||
crisp:
|
||||
website_id:
|
||||
@@ -691,6 +701,7 @@ umami_analytics:
|
||||
enable: false
|
||||
# For self-hosted setups, configure the hostname of the Umami instance
|
||||
serverURL:
|
||||
script_name: script.js
|
||||
website_id:
|
||||
option:
|
||||
UV_PV:
|
||||
@@ -700,6 +711,12 @@ umami_analytics:
|
||||
# Umami Cloud (API key) / self-hosted Umami (token)
|
||||
token:
|
||||
|
||||
# https://www.googletagmanager.com/
|
||||
google_tag_manager:
|
||||
tag_id:
|
||||
# optional
|
||||
domain:
|
||||
|
||||
# --------------------------------------
|
||||
# Advertisement
|
||||
# --------------------------------------
|
||||
@@ -923,6 +940,29 @@ mermaid:
|
||||
theme:
|
||||
light: default
|
||||
dark: dark
|
||||
# Enable "Open in New Tab" button to view diagram in a separate window
|
||||
open_in_new_tab: true
|
||||
# Enable zoom and pan interactions on diagrams
|
||||
zoom_pan: true
|
||||
|
||||
# chartjs
|
||||
# see https://www.chartjs.org/docs/latest/
|
||||
chartjs:
|
||||
enable: false
|
||||
# Do not modify unless you understand how they work.
|
||||
# The default settings are only used when the MD syntax is not specified.
|
||||
# General font color for the chart
|
||||
fontColor:
|
||||
light: 'rgba(0, 0, 0, 0.8)'
|
||||
dark: 'rgba(255, 255, 255, 0.8)'
|
||||
# General border color for the chart
|
||||
borderColor:
|
||||
light: 'rgba(0, 0, 0, 0.1)'
|
||||
dark: 'rgba(255, 255, 255, 0.2)'
|
||||
# Background color for scale labels on radar and polar area charts
|
||||
scale_ticks_backdropColor:
|
||||
light: 'transparent'
|
||||
dark: 'transparent'
|
||||
|
||||
# Note - Bootstrap Callout
|
||||
note:
|
||||
@@ -968,17 +1008,12 @@ snackbar:
|
||||
# https://instant.page/
|
||||
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
|
||||
# https://github.com/verlok/vanilla-lazyload
|
||||
lazyload:
|
||||
enable: false
|
||||
# Use browser's native lazyload instead of vanilla-lazyload
|
||||
native: false
|
||||
# Specify the field to use lazyload (site or post)
|
||||
field: site
|
||||
placeholder:
|
||||
@@ -1008,6 +1043,14 @@ Open_Graph_meta:
|
||||
# fb_admins:
|
||||
# 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
|
||||
css_prefix: true
|
||||
|
||||
@@ -1030,7 +1073,7 @@ CDN:
|
||||
third_party_provider: jsdelivr
|
||||
|
||||
# Add version number to url, true or false
|
||||
version: false
|
||||
version: true
|
||||
|
||||
# Custom format
|
||||
# For example: https://cdn.staticfile.org/${cdnjs_name}/${version}/${min_cdnjs_file}
|
||||
@@ -1050,6 +1093,7 @@ CDN:
|
||||
# canvas_fluttering_ribbon:
|
||||
# canvas_nest:
|
||||
# canvas_ribbon:
|
||||
# chartjs:
|
||||
# click_heart:
|
||||
# clickShowText:
|
||||
# disqusjs:
|
||||
@@ -1065,7 +1109,6 @@ CDN:
|
||||
# gitalk_css:
|
||||
# giscus:
|
||||
# instantpage:
|
||||
# instantsearch:
|
||||
# katex:
|
||||
# katex_copytex:
|
||||
# lazyload:
|
||||
@@ -1076,7 +1119,6 @@ CDN:
|
||||
# medium_zoom:
|
||||
# mermaid:
|
||||
# meting_js:
|
||||
# pangu:
|
||||
# prismjs_autoloader:
|
||||
# prismjs_js:
|
||||
# prismjs_lineNumber_js:
|
||||
|
||||
@@ -32,6 +32,7 @@ post:
|
||||
copyright_content: 'All articles on this blog are licensed under <a href="%s">%s</a> unless otherwise stated.'
|
||||
recommend: Related Articles
|
||||
edit: Edit
|
||||
back_to_home: Back to Home
|
||||
|
||||
search:
|
||||
title: Search
|
||||
@@ -47,6 +48,7 @@ search:
|
||||
pagination:
|
||||
prev: Previous
|
||||
next: Next
|
||||
page_info: 'Page ${current} of ${total}'
|
||||
|
||||
comment: Comments
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ post:
|
||||
copyright_content: 'All articles on this blog are licensed under <a href="%s">%s</a> unless otherwise stated.'
|
||||
recommend: Related Articles
|
||||
edit: Edit
|
||||
back_to_home: Back to Home
|
||||
|
||||
search:
|
||||
title: Search
|
||||
@@ -47,6 +48,7 @@ search:
|
||||
pagination:
|
||||
prev: Previous
|
||||
next: Next
|
||||
page_info: 'Page ${current} of ${total}'
|
||||
|
||||
comment: Comments
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ post:
|
||||
copyright_content: 'このブログのすべての記事は、<a href="%s">%s</a> ライセンスの下で提供されており、特に明記されていない限り、すべての権利を留保します。転載時には出典を明記してください: <a href="%s">%s</a>。'
|
||||
recommend: 関連記事
|
||||
edit: 編集
|
||||
back_to_home: ホームに戻る
|
||||
|
||||
search:
|
||||
title: 検索
|
||||
@@ -47,6 +48,7 @@ search:
|
||||
pagination:
|
||||
prev: 前へ
|
||||
next: 次へ
|
||||
page_info: '${current} ページ / 合計 ${total} ページ'
|
||||
|
||||
comment: コメント
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ post:
|
||||
copyright_content: '이 블로그의 모든 글은 <a href="%s">%s</a> 라이선스를 따르며, 별도로 명시되지 않는 한 모든 권리를 보유합니다. 재배포 시 출처를 명시해 주세요: <a href="%s">%s</a>.'
|
||||
recommend: 관련 글
|
||||
edit: 편집
|
||||
back_to_home: 홈으로 돌아가기
|
||||
|
||||
search:
|
||||
title: 검색
|
||||
@@ -47,6 +48,7 @@ search:
|
||||
pagination:
|
||||
prev: 이전
|
||||
next: 다음
|
||||
page_info: '${current} 페이지 / 총 ${total} 페이지'
|
||||
|
||||
comment: 댓글
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ post:
|
||||
<a href="%s" target="_blank">%s</a> 许可协议。转载请注明来源 <a href="%s" target="_blank">%s</a>!'
|
||||
recommend: 相关推荐
|
||||
edit: 编辑
|
||||
back_to_home: 返回首页
|
||||
|
||||
search:
|
||||
title: 搜索
|
||||
@@ -48,6 +49,7 @@ search:
|
||||
pagination:
|
||||
prev: 上一篇
|
||||
next: 下一篇
|
||||
page_info: '第 ${current} 页 / 共 ${total} 页'
|
||||
|
||||
comment: 评论
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ post:
|
||||
copyright_content: '除特別聲明外,本博客所有文章均採用<a href="%s">%s</a> 授權協議。轉載請註明出處:<a href="%s">%s</a>。'
|
||||
recommend: 相關文章
|
||||
edit: 編輯
|
||||
back_to_home: 返回首頁
|
||||
|
||||
search:
|
||||
title: 搜尋
|
||||
@@ -47,6 +48,7 @@ search:
|
||||
pagination:
|
||||
prev: 上一頁
|
||||
next: 下一頁
|
||||
page_info: '第 ${current} 頁 / 共 ${total} 頁'
|
||||
|
||||
comment: 評論
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ post:
|
||||
copyright_content: '本部落格所有文章除特別聲明外,均採用<a href="%s" target="_blank">%s</a> 授權協議。轉載請註明來源 <a href="%s" target="_blank">%s</a>!'
|
||||
recommend: 相關推薦
|
||||
edit: 編輯
|
||||
back_to_home: 返回首頁
|
||||
|
||||
search:
|
||||
title: 搜尋
|
||||
@@ -47,6 +48,7 @@ search:
|
||||
pagination:
|
||||
prev: 上一篇
|
||||
next: 下一篇
|
||||
page_info: '第 ${current} 頁 / 共 ${total} 頁'
|
||||
|
||||
comment: 評論
|
||||
|
||||
|
||||
@@ -11,15 +11,12 @@ div
|
||||
if theme.instantpage
|
||||
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))
|
||||
|
||||
if theme.snackbar.enable
|
||||
script(src=url_for(theme.asset.snackbar))
|
||||
|
||||
if theme.pangu.enable
|
||||
!= partial("includes/third-party/pangu.pug", {}, { cache: true })
|
||||
|
||||
.js-pjax
|
||||
if needLoadCountJs
|
||||
!= partial("includes/third-party/card-post-count/index", {}, { cache: true })
|
||||
@@ -36,7 +33,7 @@ div
|
||||
!= partial("includes/third-party/prismjs", {}, { cache: true })
|
||||
|
||||
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 })
|
||||
|
||||
!= fragment_cache('injectBottom', function(){return injectHtml(theme.inject.bottom)})
|
||||
@@ -55,6 +52,10 @@ div
|
||||
!= partial("includes/third-party/umami_analytics", {}, { cache: true })
|
||||
|
||||
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=theme.asset.busuanzi ? url_for(theme.asset.busuanzi) : '//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js')
|
||||
|
||||
!= 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")
|
||||
@@ -1,18 +1,39 @@
|
||||
#footer-wrap
|
||||
if theme.footer.owner.enable
|
||||
- const { nav, owner, copyright, custom_text } = theme.footer
|
||||
|
||||
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 sinceYear = theme.footer.owner.since
|
||||
.copyright
|
||||
- const sinceYear = owner.since
|
||||
span.copyright
|
||||
if sinceYear && sinceYear != currentYear
|
||||
!= `©${sinceYear} - ${currentYear} By ${config.author}`
|
||||
!= `© ${sinceYear} - ${currentYear} By ${config.author}`
|
||||
else
|
||||
!= `©${currentYear} By ${config.author}`
|
||||
if theme.footer.copyright
|
||||
.framework-info
|
||||
!= `© ${currentYear} By ${config.author}`
|
||||
if copyright.enable
|
||||
- const v = copyright.version ? getVersion() : false
|
||||
span.framework-info
|
||||
if owner.enable && nav
|
||||
span.footer-separator |
|
||||
span= _p('footer.framework') + ' '
|
||||
a(href='https://hexo.io')= 'Hexo'
|
||||
a(href='https://hexo.io')= `Hexo${ v ? ' ' + v.hexo : '' }`
|
||||
span.footer-separator |
|
||||
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
|
||||
.footer_custom_text!= theme.footer.custom_text
|
||||
@@ -1,12 +1,18 @@
|
||||
- var pageTitle
|
||||
- is_archive() ? page.title = findArchivesTitle(page, theme.menu, date) : ''
|
||||
- if (is_tag()) pageTitle = _p('page.tag') + ': ' + page.tag
|
||||
- else if (is_category()) pageTitle = _p('page.category') + ': ' + page.category
|
||||
- else if (is_current('/404.html', [strict])) pageTitle = _p('error404')
|
||||
- else pageTitle = page.title || config.title || ''
|
||||
- globalPageType === 'archive' ? page.title = findArchivesTitle(page, theme.menu, date) : ''
|
||||
case globalPageType
|
||||
when 'tag'
|
||||
- pageTitle = _p('page.tag') + ': ' + page.tag
|
||||
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 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 pageCopyright = config.copyright || config.author
|
||||
- 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
|
||||
include ./head/Open_Graph.pug
|
||||
|
||||
//- Structured Data
|
||||
include ./head/structured_data.pug
|
||||
|
||||
!=favicon_tag(theme.favicon || config.favicon)
|
||||
link(rel="canonical" href=urlNoIndex(null,config.pretty_urls.trailing_index,config.pretty_urls.trailing_html))
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ if theme.Open_Graph_meta.enable
|
||||
-
|
||||
const coverVal = page.cover_type === 'img' ? page.cover : theme.avatar.img
|
||||
let ogOption = Object.assign({
|
||||
type: is_post() ? 'article' : 'website',
|
||||
type: globalPageType === 'post' ? 'article' : 'website',
|
||||
image: coverVal ? full_url_for(coverVal) : '',
|
||||
fb_admins: theme.facebook_comments.user_id || '',
|
||||
fb_app_id: theme.facebook_comments.app_id || '',
|
||||
|
||||
@@ -32,3 +32,14 @@ if theme.microsoft_clarity
|
||||
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);
|
||||
})(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')
|
||||
@@ -10,7 +10,6 @@
|
||||
hitsPerPage: theme.search.algolia_search.hitsPerPage,
|
||||
// search languages
|
||||
languages: {
|
||||
input_placeholder: theme.search.placeholder || _p("search.input_placeholder"),
|
||||
hits_empty: _p("search.algolia_search.hits_empty"),
|
||||
hits_stats: _p("search.algolia_search.hits_stats"),
|
||||
}
|
||||
@@ -19,12 +18,16 @@
|
||||
|
||||
let localSearch = 'undefined'
|
||||
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({
|
||||
path: CDN || config.root + config.search.path,
|
||||
preload,
|
||||
top_n_per_article,
|
||||
unescape,
|
||||
pagination: {
|
||||
enable: pagination.enable,
|
||||
hitsPerPage: pagination.hitsPerPage
|
||||
},
|
||||
languages: {
|
||||
// search languages
|
||||
hits_empty: _p("search.local_search.hits_empty"),
|
||||
@@ -69,30 +72,16 @@
|
||||
})
|
||||
}
|
||||
|
||||
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 syntaxHighlighter = config.syntax_highlighter
|
||||
let highlightEnable = syntaxHighlighter ? ['highlight.js', 'prismjs'].includes(syntaxHighlighter) : (config.highlight.enable || config.prismjs.enable)
|
||||
if (highlightEnable) {
|
||||
const { copy, language, height_limit, fullpage, macStyle } = theme.code_blocks
|
||||
highlight = JSON.stringify({
|
||||
plugin: syntaxHighlighter ? syntaxHighlighter : config.highlight.enable ? 'highlight.js' : 'prismjs',
|
||||
let highlightProvider = config.syntax_highlighter || (config.highlight.enable ? 'highlight.js' : config.prismjs.enable ? 'prismjs' : null)
|
||||
const { copy, language, height_limit, fullpage, macStyle, shrink } = theme.code_blocks
|
||||
let highlight = JSON.stringify({
|
||||
plugin: highlightProvider,
|
||||
highlightCopy: copy,
|
||||
highlightLang: language,
|
||||
highlightHeightLimit: height_limit,
|
||||
highlightFullpage: fullpage,
|
||||
highlightMacStyle: macStyle
|
||||
})
|
||||
}
|
||||
|
||||
script.
|
||||
const GLOBAL_CONFIG = {
|
||||
@@ -100,7 +89,6 @@ script.
|
||||
algolia: !{algolia},
|
||||
localSearch: !{localSearch},
|
||||
translate: !{translate},
|
||||
noticeOutdate: !{noticeOutdate},
|
||||
highlight: !{highlight},
|
||||
copy: {
|
||||
success: '!{_p("copy.success")}',
|
||||
@@ -127,7 +115,7 @@ script.
|
||||
buttonText: '!{_p("load_more")}'
|
||||
},
|
||||
isPhotoFigcaption: !{theme.photofigcaption},
|
||||
islazyload: !{theme.lazyload.enable},
|
||||
islazyloadPlugin: !{theme.lazyload.enable && !theme.lazyload.native},
|
||||
isAnchor: !{theme.anchor.auto_update || false},
|
||||
percent: {
|
||||
toc: !{theme.toc.scroll_percent},
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
var showToc = false
|
||||
if (theme.aside.enable && page.aside !== false) {
|
||||
let tocEnable = false
|
||||
if (is_post() && theme.toc.post) tocEnable = true
|
||||
else if (is_page() && theme.toc.page) tocEnable = true
|
||||
if (globalPageType === 'post' && theme.toc.post) tocEnable = true
|
||||
else if (globalPageType === 'page' && theme.toc.page) tocEnable = true
|
||||
const pageToc = typeof page.toc === 'boolean' ? page.toc : tocEnable
|
||||
showToc = pageToc && (toc(page.content) !== '' || page.encrypt === true)
|
||||
}
|
||||
@@ -19,9 +19,7 @@
|
||||
script#config-diff.
|
||||
var GLOBAL_CONFIG_SITE = {
|
||||
title: '!{titleVal}',
|
||||
isPost: !{is_post()},
|
||||
isHome: !{is_home()},
|
||||
isHighlightShrink: !{isHighlightShrink},
|
||||
isToc: !{showToc},
|
||||
postUpdate: '!{full_date(page.updated)}'
|
||||
pageType: '!{page.type == 'shuoshuo' ? 'shuoshuo' : globalPageType}'
|
||||
}
|
||||
|
||||
67
layout/includes/head/structured_data.pug
Normal file
67
layout/includes/head/structured_data.pug
Normal 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}
|
||||
@@ -6,33 +6,32 @@
|
||||
var bg_img = ''
|
||||
|
||||
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
|
||||
else if is_page()
|
||||
when 'page'
|
||||
- top_img = page.top_img || theme.default_top_img
|
||||
else if is_tag()
|
||||
- top_img = theme.tag_per_img && theme.tag_per_img[page.tag]
|
||||
- top_img = top_img || returnTopImg(theme.tag_img)
|
||||
else if is_category()
|
||||
- top_img = theme.category_per_img && theme.category_per_img[page.category]
|
||||
- top_img = top_img || returnTopImg(theme.category_img)
|
||||
else if is_home()
|
||||
when 'tag'
|
||||
- top_img = theme.tag_per_img && theme.tag_per_img[page.tag] || returnTopImg(theme.tag_img)
|
||||
when 'category'
|
||||
- top_img = theme.category_per_img && theme.category_per_img[page.category] || returnTopImg(theme.category_img)
|
||||
when 'home'
|
||||
- top_img = returnTopImg(theme.index_img)
|
||||
else if is_archive()
|
||||
when 'archive'
|
||||
- top_img = returnTopImg(theme.archive_img)
|
||||
else
|
||||
default
|
||||
- top_img = page.top_img || theme.default_top_img
|
||||
|
||||
if top_img !== false
|
||||
- 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)
|
||||
include ./nav.pug
|
||||
if top_img !== false
|
||||
if is_post()
|
||||
if globalPageType === 'post'
|
||||
include ./post-info.pug
|
||||
else if is_home()
|
||||
else if globalPageType === 'home'
|
||||
#site-info
|
||||
h1#site-title=config.title
|
||||
if theme.subtitle.enable
|
||||
@@ -48,6 +47,6 @@ header#page-header(class=`${headerClassName + isFixedClass}` style=bg_img)
|
||||
#page-site-info
|
||||
h1#site-title=page.title || page.tag || page.category
|
||||
else
|
||||
//- improvement seo
|
||||
if !is_post()
|
||||
//- improve seo
|
||||
if globalPageType !== 'post'
|
||||
h1.title-seo=page.title || page.tag || page.category || config.title
|
||||
@@ -5,9 +5,13 @@ nav#nav
|
||||
img.site-icon(src=url_for(theme.nav.logo) alt='Logo')
|
||||
if theme.nav.display_title
|
||||
span.site-name=config.title
|
||||
if is_post()
|
||||
if globalPageType === 'post' && theme.nav.display_post_title
|
||||
a.nav-page-title(href=url_for('/'))
|
||||
span.site-name=(page.title || config.title)
|
||||
span.site-name
|
||||
i.fa-solid.fa-circle-arrow-left
|
||||
span= ' ' + _p('post.back_to_home')
|
||||
|
||||
#menus
|
||||
if theme.search.use
|
||||
#search-button
|
||||
|
||||
@@ -60,47 +60,38 @@
|
||||
if block
|
||||
block
|
||||
|
||||
mixin otherPV()
|
||||
if theme.umami_analytics.enable && theme.umami_analytics.UV_PV.page_pv
|
||||
+pvBlock('', '', '')
|
||||
span#umamiPV(data-path=url_for(page.path))
|
||||
i.fa-solid.fa-spinner.fa-spin
|
||||
else if theme.busuanzi.page_pv
|
||||
+pvBlock('', 'post-meta-pv-cv', '')
|
||||
span#busuanzi_value_page_pv
|
||||
i.fa-solid.fa-spinner.fa-spin
|
||||
|
||||
- const commentUse = comments.use && comments.use[0]
|
||||
if page.comments !== false && commentUse && !comments.lazyload
|
||||
case commentUse
|
||||
when 'Valine'
|
||||
if theme.valine.visitor
|
||||
if commentUse === 'Valine' && theme.valine.visitor
|
||||
+pvBlock(url_for(page.path), 'leancloud_visitors', page.title)
|
||||
span.leancloud-visitors-count
|
||||
i.fa-solid.fa-spinner.fa-spin
|
||||
when 'Waline'
|
||||
if theme.waline.pageview
|
||||
else if commentUse === 'Waline' && theme.waline.pageview
|
||||
+pvBlock('', '', '')
|
||||
span.waline-pageview-count(data-path=url_for(page.path))
|
||||
i.fa-solid.fa-spinner.fa-spin
|
||||
when 'Twikoo'
|
||||
if theme.twikoo.visitor
|
||||
else if commentUse === 'Twikoo' && theme.twikoo.visitor
|
||||
+pvBlock('', '', '')
|
||||
span#twikoo_visitors
|
||||
i.fa-solid.fa-spinner.fa-spin
|
||||
when 'Artalk'
|
||||
if theme.artalk.visitor
|
||||
else if commentUse === 'Artalk' && theme.artalk.visitor
|
||||
+pvBlock('', '', '')
|
||||
span#ArtalkPV
|
||||
i.fa-solid.fa-spinner.fa-spin
|
||||
default
|
||||
if theme.umami_analytics.enable && theme.umami_analytics.UV_PV.page_pv
|
||||
+pvBlock('', '', '')
|
||||
span#umamiPV(data-path=url_for(page.path))
|
||||
i.fa-solid.fa-spinner.fa-spin
|
||||
else if theme.busuanzi.page_pv
|
||||
+pvBlock('', 'post-meta-pv-cv', '')
|
||||
span#busuanzi_value_page_pv
|
||||
i.fa-solid.fa-spinner.fa-spin
|
||||
else
|
||||
if theme.umami_analytics.enable && theme.umami_analytics.UV_PV.page_pv
|
||||
+pvBlock('', '', '')
|
||||
span#umamiPV(data-path=url_for(page.path))
|
||||
i.fa-solid.fa-spinner.fa-spin
|
||||
else if theme.busuanzi.page_pv
|
||||
+pvBlock('', 'post-meta-pv-cv', '')
|
||||
span#busuanzi_value_page_pv
|
||||
i.fa-solid.fa-spinner.fa-spin
|
||||
+otherPV()
|
||||
else
|
||||
+otherPV()
|
||||
|
||||
if comments.count && !comments.lazyload && page.comments !== false && comments.use
|
||||
- var whichCount = comments.use[0]
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
- var globalPageType = getPageType(page, is_home)
|
||||
- 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 pageType = is_post() ? 'post' : 'page'
|
||||
- var pageType = globalPageType === 'post' ? 'post' : 'page'
|
||||
- pageType = page.type ? pageType + ' type-' + page.type : pageType
|
||||
|
||||
doctype html
|
||||
@@ -12,7 +13,29 @@ html(lang=config.language data-theme=theme.display_mode class=htmlClassHideAside
|
||||
!=partial('includes/loading/index', {}, {cache: true})
|
||||
|
||||
if theme.background
|
||||
#web_bg(style=getBgPath(theme.background))
|
||||
if !Array.isArray(theme.background)
|
||||
#web_bg.bg-animation(style=getBgPath(theme.background))
|
||||
else
|
||||
#web_bg.bg-animation
|
||||
- const bgStyleArr = theme.background.map(getBgPath)
|
||||
script.
|
||||
(() => {
|
||||
const arr = !{JSON.stringify(bgStyleArr)}
|
||||
const webBgDiv = document.getElementById('web_bg')
|
||||
|
||||
const setRandomBg = () => {
|
||||
webBgDiv.style = arr[Math.floor(Math.random() * arr.length)]
|
||||
requestAnimationFrame(() => webBgDiv.classList.add('bg-animation'))
|
||||
}
|
||||
|
||||
document.addEventListener('pjax:send', () => {
|
||||
webBgDiv.style = ''
|
||||
webBgDiv.classList.remove('bg-animation')
|
||||
})
|
||||
|
||||
document.addEventListener('pjax:complete', setRandomBg)
|
||||
document.addEventListener('DOMContentLoaded', setRandomBg)
|
||||
})()
|
||||
|
||||
!=partial('includes/sidebar', {}, {cache: true})
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ script.
|
||||
const $body = document.body
|
||||
const preloader = {
|
||||
endLoading: () => {
|
||||
if ($loadingBox.classList.contains('loaded')) return
|
||||
$body.style.overflow = ''
|
||||
$loadingBox.classList.add('loaded')
|
||||
},
|
||||
@@ -24,7 +25,15 @@ script.
|
||||
}
|
||||
|
||||
preloader.initLoading()
|
||||
|
||||
if (document.readyState === 'complete') {
|
||||
preloader.endLoading()
|
||||
} else {
|
||||
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}) {
|
||||
btf.addGlobalFn('pjaxSend', preloader.initLoading, 'preloader_init')
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
mixin indexPostUI()
|
||||
- const indexLayout = theme.index_layout
|
||||
- const masonryLayoutClass = (indexLayout === 6 || indexLayout === 7) ? 'masonry' : ''
|
||||
- const masonryLayoutClass = [6, 7].includes(indexLayout) ? 'masonry' : ''
|
||||
#recent-posts.recent-posts.nc(class=masonryLayoutClass)
|
||||
.recent-post-items
|
||||
each article, index in page.posts.data
|
||||
@@ -8,19 +8,19 @@ mixin indexPostUI()
|
||||
- const link = article.link || article.path
|
||||
- const title = article.title || _p('no_title')
|
||||
- const leftOrRight = indexLayout === 3 ? (index % 2 === 0 ? 'left' : 'right') : (indexLayout === 2 ? 'right' : '')
|
||||
- const post_cover = article.cover
|
||||
- const no_cover = article.cover === false || !theme.cover.index_enable ? 'no-cover' : ''
|
||||
- const postCover = article.cover
|
||||
- const noCover = article.cover === false || !theme.cover.index_enable ? 'no-cover' : ''
|
||||
|
||||
if post_cover && theme.cover.index_enable
|
||||
if postCover && theme.cover.index_enable
|
||||
.post_cover(class=leftOrRight)
|
||||
a(href=url_for(link) title=title)
|
||||
if article.cover_type === 'img'
|
||||
img.post-bg(src=url_for(post_cover) onerror=`this.onerror=null;this.src='${url_for(theme.error_img.post_page)}'` alt=title)
|
||||
img.post-bg(src=url_for(postCover) onerror=`this.onerror=null;this.src='${url_for(theme.error_img.post_page)}'` alt=title)
|
||||
else
|
||||
div.post-bg(style=`background: ${post_cover}`)
|
||||
.recent-post-info(class=no_cover)
|
||||
div.post-bg(style=`background: ${postCover}`)
|
||||
.recent-post-info(class=noCover)
|
||||
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
|
||||
= title
|
||||
.article-meta-wrap
|
||||
@@ -35,13 +35,13 @@ mixin indexPostUI()
|
||||
span.article-meta-label=_p('post.updated')
|
||||
time.post-meta-date-updated(datetime=date_xml(article.updated) title=_p('post.updated') + ' ' + full_date(article.updated))= date(article.updated, config.date_format)
|
||||
else
|
||||
- const data_type_updated = theme.post_meta.page.date_type === 'updated'
|
||||
- const date_type = data_type_updated ? 'updated' : 'date'
|
||||
- const date_icon = data_type_updated ? 'fas fa-history' : 'far fa-calendar-alt'
|
||||
- const date_title = data_type_updated ? _p('post.updated') : _p('post.created')
|
||||
i(class=date_icon)
|
||||
span.article-meta-label= date_title
|
||||
time(datetime=date_xml(article[date_type]) title=date_title + ' ' + full_date(article[date_type]))= date(article[date_type], config.date_format)
|
||||
- const isUpdatedType = theme.post_meta.page.date_type === 'updated'
|
||||
- const dateType = isUpdatedType ? 'updated' : 'date'
|
||||
- const dateIcon = isUpdatedType ? 'fas fa-history' : 'far fa-calendar-alt'
|
||||
- const dateTitle = isUpdatedType ? _p('post.updated') : _p('post.created')
|
||||
i(class=dateIcon)
|
||||
span.article-meta-label= dateTitle
|
||||
time(datetime=date_xml(article[dateType]) title=dateTitle + ' ' + full_date(article[dateType]))= date(article[dateType], config.date_format)
|
||||
if theme.post_meta.page.categories && article.categories.data.length > 0
|
||||
span.article-meta
|
||||
span.article-meta-separator |
|
||||
@@ -69,7 +69,10 @@ mixin indexPostUI()
|
||||
span.article-meta-label= ' ' + _p('card_post_count')
|
||||
|
||||
if theme.comments.card_post_count && theme.comments.use
|
||||
case theme.comments.use[0]
|
||||
- const commentSystem = theme.comments.use[0]
|
||||
- const commentLink = url_for(link) + '#post-comment'
|
||||
|
||||
case commentSystem
|
||||
when 'Disqus'
|
||||
when 'Disqusjs'
|
||||
+countBlockInIndex
|
||||
@@ -77,43 +80,37 @@ mixin indexPostUI()
|
||||
i.fa-solid.fa-spinner.fa-spin
|
||||
when 'Valine'
|
||||
+countBlockInIndex
|
||||
a(href=url_for(link) + '#post-comment')
|
||||
a(href=commentLink)
|
||||
span.valine-comment-count(data-xid=url_for(link))
|
||||
i.fa-solid.fa-spinner.fa-spin
|
||||
when 'Waline'
|
||||
+countBlockInIndex
|
||||
a(href=url_for(link) + '#post-comment')
|
||||
a(href=commentLink)
|
||||
span.waline-comment-count(data-path=url_for(link))
|
||||
i.fa-solid.fa-spinner.fa-spin
|
||||
when 'Twikoo'
|
||||
+countBlockInIndex
|
||||
a.twikoo-count(href=url_for(link) + '#post-comment')
|
||||
a.twikoo-count(href=commentLink)
|
||||
i.fa-solid.fa-spinner.fa-spin
|
||||
when 'Facebook Comments'
|
||||
+countBlockInIndex
|
||||
a(href=url_for(link) + '#post-comment')
|
||||
a(href=commentLink)
|
||||
span.fb-comments-count(data-href=urlNoIndex(article.permalink))
|
||||
when 'Remark42'
|
||||
+countBlockInIndex
|
||||
a(href=url_for(link) + '#post-comment')
|
||||
a(href=commentLink)
|
||||
span.remark42__counter(data-url=urlNoIndex(article.permalink))
|
||||
i.fa-solid.fa-spinner.fa-spin
|
||||
when 'Artalk'
|
||||
+countBlockInIndex
|
||||
a(href=url_for(link) + '#post-comment')
|
||||
a(href=commentLink)
|
||||
span.artalk-count(data-page-key=url_for(link))
|
||||
i.fa-solid.fa-spinner.fa-spin
|
||||
|
||||
//- Display the article introduction on homepage
|
||||
case theme.index_post_content.method
|
||||
when false
|
||||
- break
|
||||
when 1
|
||||
.content!= article.description
|
||||
when 2
|
||||
.content!= article.description || truncate(article.content, theme.index_post_content.length)
|
||||
default
|
||||
.content!= truncate(article.content, theme.index_post_content.length)
|
||||
- const content = postDesc(article)
|
||||
if content
|
||||
.content!=content
|
||||
|
||||
if theme.ad && theme.ad.index
|
||||
if (index + 1) % 3 === 0
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
#article-container
|
||||
#article-container.container
|
||||
!= page.content
|
||||
@@ -1,4 +1,4 @@
|
||||
#article-container
|
||||
#article-container.container
|
||||
.flink
|
||||
- let { content, random, flink_url } = page
|
||||
- let pageContent = content
|
||||
|
||||
@@ -6,98 +6,327 @@
|
||||
//- - tag1
|
||||
//- - tag2
|
||||
|
||||
|
||||
- page.comments = false
|
||||
- page.toc = false
|
||||
|
||||
#article-container
|
||||
if page.shuoshuo_url
|
||||
if page.shuoshuo_url || (site.data.shuoshuo && site.data.shuoshuo.length)
|
||||
if page.comments !== false && theme.comments.use
|
||||
- commentsJsLoad = true
|
||||
|
||||
script.
|
||||
(() => {
|
||||
const loadShuoshuo = async () => {
|
||||
try {
|
||||
const fetchContent = await fetch('!{url_for(page.shuoshuo_url)}')
|
||||
const shuoshuo = await fetchContent.json()
|
||||
const commentDiv = `!{partial('includes/third-party/comments/index', {}, {cache: true})}`
|
||||
|
||||
let start = 0
|
||||
const container = document.getElementById('article-container')
|
||||
const runDestroy = (shuoshuoComment) => {
|
||||
if (!shuoshuoComment) return
|
||||
|
||||
const addData = data => {
|
||||
const cLength = data.length
|
||||
const end = start + 10 > cLength ? cLength : start + 10
|
||||
let result = ''
|
||||
data.slice(start, end).forEach((item) => {
|
||||
result += `
|
||||
for (const [key, fn] of Object.entries(shuoshuoComment)) {
|
||||
if (key.startsWith('destroy')) fn()
|
||||
}
|
||||
}
|
||||
|
||||
window.addCommentToShuoshuo = e => {
|
||||
const btn = e.target.closest('.shuoshuo-comment-btn')
|
||||
if (!btn) return
|
||||
|
||||
const ele = btn.closest('.container').nextElementSibling
|
||||
const { shuoshuoComment } = window
|
||||
const isInclude = ele.classList.contains('no-comment')
|
||||
runDestroy(shuoshuoComment)
|
||||
if (isInclude) {
|
||||
ele.classList.remove('no-comment')
|
||||
ele.innerHTML = commentDiv
|
||||
const key = `${location.pathname.replace(/\/$/, '')}?key=${ele.getAttribute('data-key')}`
|
||||
btf.switchComments(ele, key)
|
||||
shuoshuoComment.loadComment && shuoshuoComment.loadComment(ele, key)
|
||||
}
|
||||
}
|
||||
})()
|
||||
|
||||
|
||||
- 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.
|
||||
(() => {
|
||||
const limitConfig = !{ JSON.stringify(page.limit || {}) }
|
||||
|
||||
const sortDataByDate = data => data.sort((a, b) => new Date(b.date) - new Date(a.date))
|
||||
|
||||
const filterDataByLimit = (data, limit) => {
|
||||
if (!limit || !limit.type) return data
|
||||
if (limit.type === 'num') return data.slice(0, limit.value)
|
||||
if (limit.type === 'date') {
|
||||
const limitDate = new Date(limit.value)
|
||||
return data.filter(item => new Date(item.date) >= limitDate)
|
||||
}
|
||||
return data
|
||||
};
|
||||
|
||||
const formatToTimeZone = (date) => {
|
||||
const fullDate = date.length === 10 ? `${date} 00:00:00` : date
|
||||
const visitorTimeZone = '#{config.timezone}' || Intl.DateTimeFormat().resolvedOptions().timeZone
|
||||
const options = {
|
||||
timeZone: visitorTimeZone,
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hour12: false
|
||||
}
|
||||
const [day, month, year, hour, minute, second] = new Intl.DateTimeFormat('en-GB', options)
|
||||
.format(new Date(fullDate))
|
||||
.match(/\d+/g)
|
||||
return `${year}-${month}-${day} ${hour}:${minute}:${second}`
|
||||
}
|
||||
|
||||
const addLazyload = str => {
|
||||
const config = {
|
||||
enable: !{Boolean(enable)},
|
||||
native: !{Boolean(native)},
|
||||
field: '!{field}',
|
||||
placeholder: '!{url_for(placeholder)}',
|
||||
}
|
||||
|
||||
if (!config.enable || config.field !== 'site') return str
|
||||
const parser = new DOMParser()
|
||||
const doc = parser.parseFromString(str, 'text/html')
|
||||
const images = doc.querySelectorAll('img')
|
||||
|
||||
images.forEach(img => {
|
||||
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 content = dataSlice.map(item => {
|
||||
const formattedDate = formatToTimeZone(item.date)
|
||||
const tags = item.tags && item.tags.map(tag => `<span class="shuoshuo-tag">${tag}</span>`).join('') || ''
|
||||
const commentButton = item.key && !{commentsJsLoad}
|
||||
? `<div class="shuoshuo-comment-btn" onclick="addCommentToShuoshuo(event)">
|
||||
<i class="fa-solid fa-comments"></i>
|
||||
</div>`
|
||||
: ''
|
||||
const commentContainer = item.key
|
||||
? `<div class="shuoshuo-comment no-comment" data-key="${item.key}"></div>`
|
||||
: ''
|
||||
|
||||
return `
|
||||
<div class="shuoshuo-item">
|
||||
<div class="container">
|
||||
<div class="shuoshuo-item-header">
|
||||
<div class="shuoshuo-avatar">
|
||||
<img class="no-lightbox" src="${item.avatar || '!{url_for(theme.avatar.img)}'}">
|
||||
</div>
|
||||
<div class="shuoshuo-info">
|
||||
<div class="shuoshuo-author">${item.author || '!{config.author}'}</div>
|
||||
<div class="shuoshuo-date">${btf.diffDate(item.date, true)}</div>
|
||||
<time class="shuoshuo-date" title="${formattedDate}">
|
||||
${btf.diffDate(formattedDate, true)}
|
||||
</time>
|
||||
</div>
|
||||
</div>
|
||||
<div class="shuoshuo-content">
|
||||
${item.content}
|
||||
</div>
|
||||
<div class="shuoshuo-footer">
|
||||
<div class="shuoshuo-tags">
|
||||
${item.tags.map(tag => `<span class="shuoshuo-tag">${tag}</span>`).join('')}
|
||||
<div class="shuoshuo-content">${addLazyload(item.content)}</div>
|
||||
<div class="shuoshuo-footer ${tags ? 'flex-between' : 'flex-end'}">
|
||||
${tags ? `<div class="shuoshuo-tags">${tags}</div>` : ''}
|
||||
${commentButton}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
${commentContainer}
|
||||
</div>`
|
||||
}).join('')
|
||||
|
||||
start = end
|
||||
container.insertAdjacentHTML('beforeend', result)
|
||||
const container = document.getElementById('article-container')
|
||||
container.innerHTML = content
|
||||
|
||||
if (start >= cLength) {
|
||||
observer.disconnect()
|
||||
} else {
|
||||
setTimeout(() => observer.observe(container.lastElementChild), 100)
|
||||
}
|
||||
|
||||
window.lazyLoadInstance.update()
|
||||
window.lazyLoadInstance && window.lazyLoadInstance.update()
|
||||
btf.loadLightbox(document.querySelectorAll('#article-container img:not(.no-lightbox)'))
|
||||
}
|
||||
|
||||
addData(shuoshuo)
|
||||
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
if (entries[0].isIntersecting) {
|
||||
observer.unobserve(entries[0].target)
|
||||
addData(shuoshuo)
|
||||
const renderNavigation = () => {
|
||||
const container = document.getElementById('article-container')
|
||||
const existingNav = container.nextElementSibling
|
||||
if (existingNav && existingNav.classList.contains('shuoshuo-navigation')) {
|
||||
existingNav.remove()
|
||||
}
|
||||
}, {
|
||||
root: null,
|
||||
rootMargin: '0px',
|
||||
threshold: 1.0
|
||||
|
||||
const pageInfoTemplate = '#{__('pagination.page_info')}'
|
||||
const pageInfoText = pageInfoTemplate
|
||||
.replace(/\$\{current}/g, currentPage)
|
||||
.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 = ''
|
||||
})
|
||||
|
||||
if (container.lastElementChild) {
|
||||
observer.observe(container.lastElementChild)
|
||||
// Restore placeholder if no content when losing focus
|
||||
input.addEventListener('blur', (event) => {
|
||||
if (!event.target.value.trim()) {
|
||||
event.target.placeholder = currentPage
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
})
|
||||
|
||||
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) {
|
||||
console.error(error)
|
||||
}
|
||||
};
|
||||
|
||||
window.pjax ? loadShuoshuo() : window.addEventListener('load', loadShuoshuo)
|
||||
})()
|
||||
else
|
||||
if site.data.shuoshuo
|
||||
each i in site.data.shuoshuo
|
||||
.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
|
||||
.shuoshuo-date=relative_date(i.date)
|
||||
.shuoshuo-content
|
||||
!=markdown(i.content)
|
||||
.shuoshuo-footer
|
||||
.shuoshuo-tags
|
||||
each tag in i.tags
|
||||
span.shuoshuo-tag=tag
|
||||
@@ -1,2 +1,2 @@
|
||||
.tag-cloud-list.is-center
|
||||
!=cloudTags({source: site.tags, orderby: page.orderby || 'random', order: page.order || 1, minfontsize: 1.2, maxfontsize: 1.5, limit: 0, unit: 'em'})
|
||||
.tag-cloud-list.text-center
|
||||
!=cloudTags({source: site.tags, orderby: page.orderby || 'random', order: page.order || 1, minfontsize: 1.2, maxfontsize: 1.5, limit: 0, unit: 'em', custom_colors: page.custom_colors})
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
-
|
||||
if page.total !== 1
|
||||
-
|
||||
var options = {
|
||||
prev_text: '<i class="fas fa-chevron-left fa-fw"></i>',
|
||||
next_text: '<i class="fas fa-chevron-right fa-fw"></i>',
|
||||
@@ -6,34 +7,32 @@
|
||||
escape: false
|
||||
}
|
||||
|
||||
if is_post()
|
||||
- let prev = theme.post_pagination === 1 ? page.prev : page.next
|
||||
- let next = theme.post_pagination === 1 ? page.next : page.prev
|
||||
nav#pagination.pagination-post
|
||||
if(prev)
|
||||
- var hasPageNext = next ? 'pull-left' : 'pull-full'
|
||||
a.prev-post(class=hasPageNext href=url_for(prev.path) title=prev.title)
|
||||
if prev.cover_type === 'img'
|
||||
img.cover(src=url_for(prev.cover) onerror=`onerror=null;src='${url_for(theme.error_img.post_page)}'` alt='cover of previous post')
|
||||
else
|
||||
.cover(style=`background: ${prev.cover || 'var(--default-bg-color)'}`)
|
||||
.pagination-info
|
||||
.label=_p('pagination.prev')
|
||||
.prev_info=prev.title
|
||||
if globalPageType === 'post'
|
||||
- let paginationOrder = theme.post_pagination === 2 ? { prev: page.prev, next: page.next } : { prev: page.next, next: page.prev }
|
||||
|
||||
if(next)
|
||||
- var hasPagePrev = prev ? 'pull-right' : 'pull-full'
|
||||
a.next-post(class=hasPagePrev href=url_for(next.path) title=next.title)
|
||||
if next.cover_type === 'img'
|
||||
img.cover(src=url_for(next.cover) onerror=`onerror=null;src='${url_for(theme.error_img.post_page)}'` alt='cover of next post')
|
||||
nav#pagination.pagination-post
|
||||
each direction, key in paginationOrder
|
||||
if direction
|
||||
- const getPostDesc = direction.postDesc || postDesc(direction)
|
||||
- let className = key === 'prev' ? (paginationOrder.next ? '' : 'full-width') : (paginationOrder.prev ? '' : 'full-width')
|
||||
- className = getPostDesc ? className : className + ' no-desc'
|
||||
|
||||
a.pagination-related(class=className href=url_for(direction.path) title=direction.title)
|
||||
if direction.cover_type === 'img'
|
||||
img.cover(src=url_for(direction.pagination_cover || direction.cover) onerror=`onerror=null;src='${url_for(theme.error_img.post_page)}'` alt=`cover of ${key === 'prev' ? 'previous' : 'next'} post`)
|
||||
else
|
||||
.cover(style=`background: ${direction.cover || 'var(--default-bg-color)'}`)
|
||||
|
||||
.info(class=key === 'prev' ? '' : 'text-right')
|
||||
.info-1
|
||||
.info-item-1=_p(`pagination.${key}`)
|
||||
.info-item-2!=direction.title
|
||||
if getPostDesc
|
||||
.info-2
|
||||
.info-item-1!=getPostDesc
|
||||
else
|
||||
.cover(style=`background: ${next.cover || 'var(--default-bg-color)'}`)
|
||||
.pagination-info
|
||||
.label=_p('pagination.next')
|
||||
.next_info=next.title
|
||||
else
|
||||
nav#pagination
|
||||
.pagination
|
||||
if is_home()
|
||||
if globalPageType === 'home'
|
||||
- options.format = 'page/%d/#content-inner'
|
||||
!=paginator(options)
|
||||
8
layout/includes/post/outdate-notice.pug
Normal file
8
layout/includes/post/outdate-notice.pug
Normal 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)
|
||||
@@ -1,9 +1,10 @@
|
||||
- const { readmode, translate, darkmode, aside, chat } = theme
|
||||
|
||||
mixin rightsideItem(array)
|
||||
each item in array
|
||||
case item
|
||||
when 'readmode'
|
||||
if is_post() && readmode
|
||||
if globalPageType === 'post' && readmode
|
||||
button#readmode(type="button" title=_p('rightside.readmode_title'))
|
||||
i.fas.fa-book-open
|
||||
when 'translate'
|
||||
@@ -23,37 +24,29 @@ mixin rightsideItem(array)
|
||||
i.fas.fa-list-ul
|
||||
when 'chat'
|
||||
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
|
||||
when 'comment'
|
||||
if commentsJsLoad
|
||||
a#to_comment(href="#post-comment" title=_p("rightside.scroll_to_comment"))
|
||||
i.fas.fa-comments
|
||||
|
||||
- const { enable, hide, show } = theme.rightside_item_order
|
||||
- const hideArray = enable && hide ? hide.split(',') : ['readmode','translate','darkmode','hideAside']
|
||||
- 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
|
||||
- const { enable, hide, show } = theme.rightside_item_order
|
||||
- const hideArray = enable ? hide && hide.split(',') : ['readmode','translate','darkmode','hideAside']
|
||||
- const showArray = enable ? show && show.split(',') : ['toc','chat','comment']
|
||||
|
||||
|
||||
#rightside-config-hide
|
||||
if hideArray
|
||||
if hideArray.length
|
||||
+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)
|
||||
|
||||
button#go-up(type="button" title=_p("rightside.back_to_top"))
|
||||
|
||||
@@ -2,17 +2,17 @@ if theme.menu
|
||||
#sidebar
|
||||
#menu-mask
|
||||
#sidebar-menus
|
||||
.avatar-img.is-center
|
||||
img(src=url_for(theme.avatar.img) onerror=`onerror=null;src='${theme.error_img.flink}'` alt="avatar")
|
||||
.site-data.is-center
|
||||
a(href=url_for(config.archive_dir) + '/')
|
||||
.avatar-img.text-center
|
||||
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
|
||||
a(href=`${url_for(config.archive_dir)}/`)
|
||||
.headline= _p('aside.articles')
|
||||
.length-num= site.posts.length
|
||||
a(href=url_for(config.tag_dir) + '/' )
|
||||
a(href=`${url_for(config.tag_dir)}/`)
|
||||
.headline= _p('aside.tags')
|
||||
.length-num= site.tags.length
|
||||
a(href=url_for(config.category_dir) + '/')
|
||||
a(href=`${url_for(config.category_dir)}/`)
|
||||
.headline= _p('aside.categories')
|
||||
.length-num= site.categories.length
|
||||
|
||||
!=partial('includes/header/menu_item', {}, {cache: true})
|
||||
!= partial('includes/header/menu_item', {}, { cache: true })
|
||||
45
layout/includes/third-party/abcjs/abcjs.pug
vendored
45
layout/includes/third-party/abcjs/abcjs.pug
vendored
@@ -2,16 +2,45 @@ script.
|
||||
(() => {
|
||||
const abcjsInit = () => {
|
||||
const abcjsFn = () => {
|
||||
document.querySelectorAll(".abc-music-sheet").forEach(ele => {
|
||||
if (ele.children.length > 0) return
|
||||
ABCJS.renderAbc(ele, ele.innerHTML, {responsive: 'resize'})
|
||||
})
|
||||
setTimeout(() => {
|
||||
const sheets = document.querySelectorAll(".abc-music-sheet")
|
||||
for (let i = 0; i < sheets.length; i++) {
|
||||
const ele = sheets[i]
|
||||
if (ele.children.length > 0) continue
|
||||
|
||||
// Parse parameters from data-params attribute
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
typeof ABCJS === 'object' ? abcjsFn()
|
||||
: btf.getScript('!{url_for(theme.asset.abcjs_basic_js)}').then(abcjsFn)
|
||||
// Merge parsed parameters with the responsive option
|
||||
// Ensures params content appears before responsive
|
||||
const options = { ...params, responsive: "resize" }
|
||||
|
||||
// Render the music score using ABCJS.renderAbc
|
||||
ABCJS.renderAbc(ele, ele.textContent, options)
|
||||
}
|
||||
}, 100)
|
||||
}
|
||||
|
||||
window.pjax ? abcjsInit() : window.addEventListener('load', abcjsInit)
|
||||
btf.addGlobalFn('encrypt', abcjsInit, 'abcjs')
|
||||
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")
|
||||
})()
|
||||
5
layout/includes/third-party/abcjs/index.pug
vendored
5
layout/includes/third-party/abcjs/index.pug
vendored
@@ -1,6 +1,3 @@
|
||||
if theme.abcjs.enable
|
||||
if theme.abcjs.per_page
|
||||
if is_post() || is_page()
|
||||
include ./abcjs.pug
|
||||
else if page.abcjs
|
||||
if theme.abcjs.per_page && (['post','page'].includes(globalPageType)) || page.abcjs
|
||||
include ./abcjs.pug
|
||||
|
||||
@@ -18,7 +18,9 @@ script.
|
||||
includeReply: false
|
||||
}).then(function (res) {
|
||||
document.querySelectorAll('#recent-posts .twikoo-count').forEach((item,index) => {
|
||||
if (res[index]) {
|
||||
item.textContent = res[index].count
|
||||
}
|
||||
})
|
||||
}).catch(function (err) {
|
||||
console.log(err)
|
||||
|
||||
24
layout/includes/third-party/chat/chatra.pug
vendored
24
layout/includes/third-party/chat/chatra.pug
vendored
@@ -1,6 +1,12 @@
|
||||
//- https://chatra.io/help/api/
|
||||
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 isChatHideShow = !{theme.chat.button_hide_show}
|
||||
|
||||
@@ -17,26 +23,16 @@ script.
|
||||
|
||||
window.ChatraSetup = { startHidden: true }
|
||||
|
||||
window.chatBtnFn = () => {
|
||||
document.getElementById('chatra').classList.contains('chatra--expanded') ? close() : open()
|
||||
}
|
||||
window.chatBtnFn = () => document.getElementById('chatra').classList.contains('chatra--expanded') ? close() : open()
|
||||
|
||||
document.getElementById('chat-btn').style.display = 'block'
|
||||
} else if (isChatHideShow) {
|
||||
window.chatBtn = {
|
||||
hide: () => Chatra('hide'),
|
||||
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')
|
||||
})
|
||||
})()
|
||||
|
||||
|
||||
|
||||
15
layout/includes/third-party/chat/crisp.pug
vendored
15
layout/includes/third-party/chat/crisp.pug
vendored
@@ -1,16 +1,9 @@
|
||||
script.
|
||||
(() => {
|
||||
window.$crisp = [];
|
||||
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])
|
||||
window.$crisp = ['safe', true]
|
||||
window.CRISP_WEBSITE_ID = "!{theme.crisp.website_id}"
|
||||
|
||||
btf.getScript('https://client.crisp.chat/l.js').then(() => {
|
||||
const isChatBtn = !{theme.chat.rightside_button}
|
||||
const isChatHideShow = !{theme.chat.button_hide_show}
|
||||
|
||||
@@ -28,10 +21,12 @@ script.
|
||||
|
||||
window.chatBtnFn = () => $crisp.is("chat:visible") ? close() : open()
|
||||
|
||||
document.getElementById('chat-btn').style.display = 'block'
|
||||
} else if (isChatHideShow) {
|
||||
window.chatBtn = {
|
||||
hide: () => $crisp.push(["do", "chat:hide"]),
|
||||
show: () => $crisp.push(["do", "chat:show"])
|
||||
}
|
||||
}
|
||||
})
|
||||
})()
|
||||
40
layout/includes/third-party/chat/daovoice.pug
vendored
40
layout/includes/third-party/chat/daovoice.pug
vendored
@@ -1,40 +0,0 @@
|
||||
//- https://guide.daocloud.io/daovoice/javascript-api-5869833.html
|
||||
script.
|
||||
(() => {
|
||||
(function(i,s,o,g,r,a,m){i["DaoVoiceObject"]=r;i[r]=i[r]||function(){(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;a.charset="utf-8";m.parentNode.insertBefore(a,m)})(window,document,"script",('https:' == document.location.protocol ? 'https:' : 'http:') + "//widget.daovoice.io/widget/!{theme.daovoice.app_id}.js","daovoice")
|
||||
|
||||
const isChatBtn = !{theme.chat.rightside_button}
|
||||
const isChatHideShow = !{theme.chat.button_hide_show}
|
||||
|
||||
daovoice('init', {
|
||||
app_id: '!{theme.daovoice.app_id}',},{
|
||||
launcher: {
|
||||
disableLauncherIcon: isChatBtn
|
||||
},
|
||||
});
|
||||
daovoice('update');
|
||||
|
||||
if (isChatBtn) {
|
||||
window.chatBtnFn = () => {
|
||||
const isShow = document.getElementById('daodream-messenger').classList.contains('daodream-messenger-active')
|
||||
isShow ? daovoice('hide') : daovoice('show')
|
||||
}
|
||||
} else if (isChatHideShow) {
|
||||
window.chatBtn = {
|
||||
hide: () => {
|
||||
daovoice('update', {},{
|
||||
launcher: {
|
||||
disableLauncherIcon: true
|
||||
}
|
||||
})
|
||||
},
|
||||
show: () => {
|
||||
daovoice('update', {}, {
|
||||
launcher: {
|
||||
disableLauncherIcon: false
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})()
|
||||
2
layout/includes/third-party/chat/index.pug
vendored
2
layout/includes/third-party/chat/index.pug
vendored
@@ -3,7 +3,5 @@ case theme.chat.use
|
||||
include ./chatra.pug
|
||||
when 'tidio'
|
||||
include ./tidio.pug
|
||||
when 'daovoice'
|
||||
include ./daovoice.pug
|
||||
when 'crisp'
|
||||
include ./crisp.pug
|
||||
6
layout/includes/third-party/chat/tidio.pug
vendored
6
layout/includes/third-party/chat/tidio.pug
vendored
@@ -1,6 +1,6 @@
|
||||
script(src=`//code.tidio.co/${theme.tidio.public_key}.js` async)
|
||||
script.
|
||||
(() => {
|
||||
btf.getScript('//code.tidio.co/!{theme.tidio.public_key}.js').then(() => {
|
||||
const isChatBtn = !{theme.chat.rightside_button}
|
||||
const isChatHideShow = !{theme.chat.button_hide_show}
|
||||
|
||||
@@ -31,11 +31,15 @@ script.
|
||||
if (!window.tidioChatApi) return
|
||||
isShow ? close() : open()
|
||||
}
|
||||
|
||||
document.getElementById('chat-btn').style.display = 'block'
|
||||
|
||||
} else if (isChatHideShow) {
|
||||
window.chatBtn = {
|
||||
hide: () => window.tidioChatApi && window.tidioChatApi.hide(),
|
||||
show: () => window.tidioChatApi && window.tidioChatApi.show()
|
||||
}
|
||||
}
|
||||
})
|
||||
})()
|
||||
|
||||
|
||||
68
layout/includes/third-party/comments/artalk.pug
vendored
68
layout/includes/third-party/comments/artalk.pug
vendored
@@ -4,14 +4,27 @@
|
||||
script.
|
||||
(() => {
|
||||
let artalkItem = null
|
||||
const initArtalk = () => {
|
||||
artalkItem = Artalk.init(Object.assign({
|
||||
el: '#artalk-wrap',
|
||||
const option = !{JSON.stringify(option)}
|
||||
const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'
|
||||
|
||||
const destroyArtalk = () => {
|
||||
if (artalkItem) {
|
||||
artalkItem.destroy()
|
||||
artalkItem = null
|
||||
}
|
||||
}
|
||||
|
||||
const artalkChangeMode = theme => artalkItem && artalkItem.setDarkMode(theme === 'dark')
|
||||
|
||||
const initArtalk = (el = document, pageKey = location.pathname) => {
|
||||
artalkItem = Artalk.init({
|
||||
el: el.querySelector('#artalk-wrap'),
|
||||
server: '!{server}',
|
||||
site: '!{site}',
|
||||
pageKey: location.pathname,
|
||||
darkMode: document.documentElement.getAttribute('data-theme') === 'dark',
|
||||
},!{JSON.stringify(option)}))
|
||||
...option,
|
||||
pageKey: isShuoshuo ? pageKey : (option && option.pageKey) || pageKey
|
||||
})
|
||||
|
||||
if (GLOBAL_CONFIG.lightbox === 'null') return
|
||||
artalkItem.on('list-loaded', () => {
|
||||
@@ -21,30 +34,35 @@ script.
|
||||
})
|
||||
})
|
||||
|
||||
const destroyArtalk = () => {
|
||||
artalkItem.destroy()
|
||||
if (isShuoshuo) {
|
||||
window.shuoshuoComment.destroyArtalk = () => {
|
||||
destroyArtalk()
|
||||
if (el.children.length) {
|
||||
el.innerHTML = ''
|
||||
el.classList.add('no-comment')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
btf.addGlobalFn('pjaxSendOnce', destroyArtalk, 'destroyArtalk')
|
||||
}
|
||||
|
||||
const loadArtalk = async () => {
|
||||
if (typeof Artalk === 'object') initArtalk()
|
||||
else {
|
||||
await btf.getCSS('!{theme.asset.artalk_css}')
|
||||
await btf.getScript('!{theme.asset.artalk_js}')
|
||||
initArtalk()
|
||||
}
|
||||
}
|
||||
|
||||
const artalkChangeMode = theme => {
|
||||
const artalkWrap = document.getElementById('artalk-wrap')
|
||||
if (!(artalkWrap && artalkWrap.children.length)) return
|
||||
const isDark = theme === 'dark'
|
||||
artalkItem.setDarkMode(isDark)
|
||||
}
|
||||
|
||||
btf.addGlobalFn('themeChange', artalkChangeMode, 'artalk')
|
||||
}
|
||||
|
||||
const loadArtalk = async (el, pageKey) => {
|
||||
if (typeof Artalk === 'object') initArtalk(el, pageKey)
|
||||
else {
|
||||
await btf.getCSS('!{url_for(theme.asset.artalk_css)}')
|
||||
await btf.getScript('!{url_for(theme.asset.artalk_js)}')
|
||||
initArtalk(el, pageKey)
|
||||
}
|
||||
}
|
||||
|
||||
if (isShuoshuo) {
|
||||
'!{use[0]}' === 'Artalk'
|
||||
? window.shuoshuoComment = { loadComment: loadArtalk }
|
||||
: window.loadOtherComment = loadArtalk
|
||||
return
|
||||
}
|
||||
|
||||
if ('!{use[0]}' === 'Artalk' || !!{lazyload}) {
|
||||
if (!{lazyload}) btf.loadComment(document.getElementById('artalk-wrap'), loadArtalk)
|
||||
|
||||
43
layout/includes/third-party/comments/disqus.pug
vendored
43
layout/includes/third-party/comments/disqus.pug
vendored
@@ -4,29 +4,43 @@
|
||||
|
||||
script.
|
||||
(() => {
|
||||
const disqus_config = function () {
|
||||
this.page.url = '!{ page.permalink }'
|
||||
this.page.identifier = '!{ url_for(page.path) }'
|
||||
this.page.title = '!{ disqusPageTitle }'
|
||||
}
|
||||
const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'
|
||||
|
||||
const disqusReset = () => {
|
||||
const disqusReset = conf => {
|
||||
window.DISQUS && window.DISQUS.reset({
|
||||
reload: true,
|
||||
config: disqus_config
|
||||
config: conf
|
||||
})
|
||||
}
|
||||
|
||||
btf.addGlobalFn('themeChange', disqusReset, 'disqus')
|
||||
const loadDisqus = (el, path) => {
|
||||
if (isShuoshuo) {
|
||||
window.shuoshuoComment.destroyDisqus = () => {
|
||||
if (el.children.length) {
|
||||
el.innerHTML = ''
|
||||
el.classList.add('no-comment')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const loadDisqus = () =>{
|
||||
if (window.DISQUS) disqusReset()
|
||||
window.disqus_identifier = isShuoshuo ? path : '!{ url_for(page.path) }'
|
||||
window.disqus_url = isShuoshuo ? location.origin + path : '!{ page.permalink }'
|
||||
|
||||
const disqus_config = function () {
|
||||
this.page.url = disqus_url
|
||||
this.page.identifier = disqus_identifier
|
||||
this.page.title = '!{ disqusPageTitle }'
|
||||
}
|
||||
|
||||
if (window.DISQUS) disqusReset(disqus_config)
|
||||
else {
|
||||
const script = document.createElement('script')
|
||||
script.src = 'https://!{shortname}.disqus.com/embed.js'
|
||||
script.setAttribute('data-timestamp', +new Date())
|
||||
document.head.appendChild(script)
|
||||
}
|
||||
|
||||
btf.addGlobalFn('themeChange', () => disqusReset(disqus_config), 'disqus')
|
||||
}
|
||||
|
||||
const getCount = async() => {
|
||||
@@ -47,11 +61,18 @@ script.
|
||||
}
|
||||
}
|
||||
|
||||
if (isShuoshuo) {
|
||||
'!{use[0]}' === 'Disqus'
|
||||
? window.shuoshuoComment = { loadComment: loadDisqus }
|
||||
: window.loadOtherComment = loadDisqus
|
||||
return
|
||||
}
|
||||
|
||||
if ('!{use[0]}' === 'Disqus' || !!{lazyload}) {
|
||||
if (!{lazyload}) btf.loadComment(document.getElementById('disqus_thread'), loadDisqus)
|
||||
else {
|
||||
loadDisqus()
|
||||
!{ count ? 'GLOBAL_CONFIG_SITE.isPost && getCount()' : '' }
|
||||
!{ count ? `GLOBAL_CONFIG_SITE.pageType === 'post' && getCount()` : '' }
|
||||
}
|
||||
} else {
|
||||
window.loadOtherComment = loadDisqus
|
||||
|
||||
@@ -1,36 +1,52 @@
|
||||
- let disqusjsPageTitle = page.title.replace(/'/ig,"\\'")
|
||||
- let disqusjsPageTitle = page.title && page.title.replace(/'/ig,"\\'")
|
||||
- const { shortname:dqShortname, apikey:dqApikey, option:dqOption } = theme.disqusjs
|
||||
|
||||
script.
|
||||
(() => {
|
||||
const initDisqusjs = () => {
|
||||
const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'== 'shuoshuo'
|
||||
const dqOption = !{JSON.stringify(dqOption)}
|
||||
|
||||
const destroyDisqusjs = () => {
|
||||
disqusjs.destroy()
|
||||
window.disqusjs = null
|
||||
disqusjs = new DisqusJS(Object.assign({
|
||||
}
|
||||
|
||||
const themeChange = (el, path) => {
|
||||
destroyDisqusjs()
|
||||
initDisqusjs(el, path)
|
||||
}
|
||||
|
||||
const initDisqusjs = (el = document, path) => {
|
||||
if (isShuoshuo) {
|
||||
window.shuoshuoComment.destroyDisqusjs = () => {
|
||||
destroyDisqusjs()
|
||||
if (el.children.length) {
|
||||
el.innerHTML = ''
|
||||
el.classList.add('no-comment')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
disqusjs = new DisqusJS({
|
||||
shortname: '!{dqShortname}',
|
||||
identifier: '!{ url_for(page.path) }',
|
||||
url: '!{ page.permalink }',
|
||||
title: '!{ disqusjsPageTitle }',
|
||||
apikey: '!{dqApikey}',
|
||||
},!{JSON.stringify(dqOption)}))
|
||||
...dqOption,
|
||||
identifier: isShuoshuo ? path : (dqOption && dqOption.identifier) || '!{ url_for(page.path) }',
|
||||
url: isShuoshuo ? location.origin + path : (dqOption && dqOption.url) || '!{ page.permalink }'
|
||||
})
|
||||
|
||||
disqusjs.render(document.getElementById('disqusjs-wrap'))
|
||||
disqusjs.render(el.querySelector('#disqusjs-wrap'))
|
||||
|
||||
btf.addGlobalFn('themeChange', () => themeChange(el, path), 'disqusjs')
|
||||
}
|
||||
|
||||
const themeChange = () => {
|
||||
const ele = document.getElementById('disqus_thread')
|
||||
if(!ele) return
|
||||
disqusjs.destroy()
|
||||
initDisqusjs()
|
||||
}
|
||||
|
||||
btf.addGlobalFn('themeChange', themeChange, 'disqusjs')
|
||||
|
||||
const loadDisqusjs = async() => {
|
||||
if (window.disqusJsLoad) initDisqusjs()
|
||||
const loadDisqusjs = async(el, path) => {
|
||||
if (window.disqusJsLoad) initDisqusjs(el, path)
|
||||
else {
|
||||
await btf.getCSS('!{url_for(theme.asset.disqusjs_css)}')
|
||||
await btf.getScript('!{url_for(theme.asset.disqusjs)}')
|
||||
initDisqusjs()
|
||||
initDisqusjs(el, path)
|
||||
window.disqusJsLoad = true
|
||||
}
|
||||
}
|
||||
@@ -52,11 +68,18 @@ script.
|
||||
}
|
||||
}
|
||||
|
||||
if (isShuoshuo) {
|
||||
'!{theme.comments.use[0]}' === 'Disqusjs'
|
||||
? window.shuoshuoComment = { loadComment: loadDisqusjs }
|
||||
: window.loadOtherComment = loadDisqusjs
|
||||
return
|
||||
}
|
||||
|
||||
if ('!{theme.comments.use[0]}' === 'Disqusjs' || !!{theme.comments.lazyload}) {
|
||||
if (!{theme.comments.lazyload}) btf.loadComment(document.getElementById('disqusjs-wrap'), loadDisqusjs)
|
||||
else {
|
||||
loadDisqusjs()
|
||||
!{ theme.comments.count ? 'GLOBAL_CONFIG_SITE.isPost && getCount()' : '' }
|
||||
!{ theme.comments.count ? `GLOBAL_CONFIG_SITE.pageType === 'post' && getCount()` : '' }
|
||||
}
|
||||
} else {
|
||||
window.loadOtherComment = loadDisqusjs
|
||||
|
||||
@@ -3,17 +3,28 @@
|
||||
|
||||
script.
|
||||
(()=>{
|
||||
const loadFBComment = () => {
|
||||
const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'== 'shuoshuo'
|
||||
|
||||
const loadFBComment = (el = document, path) => {
|
||||
if (isShuoshuo) {
|
||||
window.shuoshuoComment.destroyFB = () => {
|
||||
if (el.children.length) {
|
||||
el.innerHTML = ''
|
||||
el.classList.add('no-comment')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('fb-root') ? '' : document.body.insertAdjacentHTML('afterend', '<div id="fb-root"></div>')
|
||||
|
||||
const themeNow = document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'light'
|
||||
const $fbComment = document.getElementsByClassName('fb-comments')[0]
|
||||
const $fbComment = el.getElementsByClassName('fb-comments')[0]
|
||||
$fbComment.setAttribute('data-colorscheme',themeNow)
|
||||
$fbComment.setAttribute('data-href', '!{urlNoIndex(page.permalink)}')
|
||||
$fbComment.setAttribute('data-href', isShuoshuo ? '!{urlNoIndex(page.permalink)}' + '#' + path : '!{urlNoIndex(page.permalink)}')
|
||||
|
||||
if (typeof FB === 'object') {
|
||||
FB.XFBML.parse(document.getElementsByClassName('post-meta-commentcount')[0])
|
||||
FB.XFBML.parse(document.getElementById('post-comment'))
|
||||
FB.XFBML.parse(el.querySelector('#post-comment'))
|
||||
}
|
||||
else {
|
||||
let ele = document.createElement('script')
|
||||
@@ -36,6 +47,13 @@ script.
|
||||
|
||||
btf.addGlobalFn('themeChange', fbModeChange, 'facebook_comments')
|
||||
|
||||
if (isShuoshuo) {
|
||||
'!{theme.comments.use[0]}' === 'Facebook Comments'
|
||||
? window.shuoshuoComment = { loadComment: loadFBComment }
|
||||
: window.loadOtherComment = loadFBComment
|
||||
return
|
||||
}
|
||||
|
||||
if ('!{theme.comments.use[0]}' === 'Facebook Comments' || !!{theme.comments.lazyload}) {
|
||||
if (!{theme.comments.lazyload}) btf.loadComment(document.querySelector('#post-comment .fb-comments'), loadFBComment)
|
||||
else loadFBComment()
|
||||
|
||||
54
layout/includes/third-party/comments/giscus.pug
vendored
54
layout/includes/third-party/comments/giscus.pug
vendored
@@ -4,27 +4,50 @@
|
||||
- const giscusOriginUrl = new URL(giscusUrl).origin
|
||||
|
||||
script.
|
||||
(()=>{
|
||||
(() => {
|
||||
const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'
|
||||
const option = !{JSON.stringify(option)}
|
||||
|
||||
const getGiscusTheme = theme => theme === 'dark' ? '!{dark_theme}' : '!{light_theme}'
|
||||
|
||||
const loadGiscus = () => {
|
||||
const config = Object.assign({
|
||||
const createScriptElement = config => {
|
||||
const ele = document.createElement('script')
|
||||
Object.entries(config).forEach(([key, value]) => {
|
||||
ele.setAttribute(key, value)
|
||||
})
|
||||
return ele
|
||||
}
|
||||
|
||||
const loadGiscus = (el = document, key) => {
|
||||
const mappingConfig = isShuoshuo
|
||||
? { 'data-mapping': 'specific', 'data-term': key }
|
||||
: { 'data-mapping': (option && option['data-mapping']) || 'pathname' }
|
||||
|
||||
const giscusConfig = {
|
||||
src: '!{giscusUrl}',
|
||||
'data-repo': '!{repo}',
|
||||
'data-repo-id': '!{repo_id}',
|
||||
'data-category-id': '!{category_id}',
|
||||
'data-mapping': 'pathname',
|
||||
'data-theme': getGiscusTheme(document.documentElement.getAttribute('data-theme')),
|
||||
'data-reactions-enabled': '1',
|
||||
crossorigin: 'anonymous',
|
||||
async: true
|
||||
},!{JSON.stringify(option)})
|
||||
|
||||
const ele = document.createElement('script')
|
||||
for (let key in config) {
|
||||
ele.setAttribute(key, config[key])
|
||||
async: true,
|
||||
...option,
|
||||
...mappingConfig
|
||||
}
|
||||
|
||||
const scriptElement = createScriptElement(giscusConfig)
|
||||
|
||||
el.querySelector('#giscus-wrap').appendChild(scriptElement)
|
||||
|
||||
if (isShuoshuo) {
|
||||
window.shuoshuoComment.destroyGiscus = () => {
|
||||
if (el.children.length) {
|
||||
el.innerHTML = ''
|
||||
el.classList.add('no-comment')
|
||||
}
|
||||
}
|
||||
}
|
||||
document.getElementById('giscus-wrap').appendChild(ele)
|
||||
}
|
||||
|
||||
const changeGiscusTheme = theme => {
|
||||
@@ -43,10 +66,17 @@ script.
|
||||
|
||||
btf.addGlobalFn('themeChange', changeGiscusTheme, 'giscus')
|
||||
|
||||
if (isShuoshuo) {
|
||||
'!{use[0]}' === 'Giscus'
|
||||
? window.shuoshuoComment = { loadComment: loadGiscus }
|
||||
: window.loadOtherComment = loadGiscus
|
||||
return
|
||||
}
|
||||
|
||||
if ('!{use[0]}' === 'Giscus' || !!{lazyload}) {
|
||||
if (!{lazyload}) btf.loadComment(document.getElementById('giscus-wrap'), loadGiscus)
|
||||
else loadGiscus()
|
||||
} else {
|
||||
window.loadOtherComment= loadGiscus
|
||||
window.loadOtherComment = loadGiscus
|
||||
}
|
||||
})()
|
||||
|
||||
64
layout/includes/third-party/comments/gitalk.pug
vendored
64
layout/includes/third-party/comments/gitalk.pug
vendored
@@ -2,28 +2,8 @@
|
||||
|
||||
script.
|
||||
(() => {
|
||||
const initGitalk = () => {
|
||||
const gitalk = new Gitalk(Object.assign({
|
||||
clientID: '!{client_id}',
|
||||
clientSecret: '!{client_secret}',
|
||||
repo: '!{repo}',
|
||||
owner: '!{owner}',
|
||||
admin: ['!{admin}'],
|
||||
id: '!{md5(page.path)}',
|
||||
updateCountCallback: commentCount
|
||||
},!{JSON.stringify(option)}))
|
||||
|
||||
gitalk.render('gitalk-container')
|
||||
}
|
||||
|
||||
const loadGitalk = async() => {
|
||||
if (typeof Gitalk === 'function') initGitalk()
|
||||
else {
|
||||
await btf.getCSS('!{url_for(theme.asset.gitalk_css)}')
|
||||
await btf.getScript('!{url_for(theme.asset.gitalk)}')
|
||||
initGitalk()
|
||||
}
|
||||
}
|
||||
const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'
|
||||
const option = !{JSON.stringify(option)}
|
||||
|
||||
const commentCount = n => {
|
||||
const isCommentCount = document.querySelector('#post-meta .gitalk-comment-count')
|
||||
@@ -32,6 +12,46 @@ script.
|
||||
}
|
||||
}
|
||||
|
||||
const initGitalk = (el, path) => {
|
||||
if (isShuoshuo) {
|
||||
window.shuoshuoComment.destroyGitalk = () => {
|
||||
if (el.children.length) {
|
||||
el.innerHTML = ''
|
||||
el.classList.add('no-comment')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const gitalk = new Gitalk({
|
||||
clientID: '!{client_id}',
|
||||
clientSecret: '!{client_secret}',
|
||||
repo: '!{repo}',
|
||||
owner: '!{owner}',
|
||||
admin: ['!{admin}'],
|
||||
updateCountCallback: commentCount,
|
||||
...option,
|
||||
id: isShuoshuo ? path : (option && option.id) || '!{md5(page.path)}'
|
||||
})
|
||||
|
||||
gitalk.render('gitalk-container')
|
||||
}
|
||||
|
||||
const loadGitalk = async(el, path) => {
|
||||
if (typeof Gitalk === 'function') initGitalk(el, path)
|
||||
else {
|
||||
await btf.getCSS('!{url_for(theme.asset.gitalk_css)}')
|
||||
await btf.getScript('!{url_for(theme.asset.gitalk)}')
|
||||
initGitalk(el, path)
|
||||
}
|
||||
}
|
||||
|
||||
if (isShuoshuo) {
|
||||
'!{theme.comments.use[0]}' === 'Gitalk'
|
||||
? window.shuoshuoComment = { loadComment: loadGitalk }
|
||||
: window.loadOtherComment = loadGitalk
|
||||
return
|
||||
}
|
||||
|
||||
if ('!{theme.comments.use[0]}' === 'Gitalk' || !!{theme.comments.lazyload}) {
|
||||
if (!{theme.comments.lazyload}) btf.loadComment(document.getElementById('gitalk-container'), loadGitalk)
|
||||
else loadGitalk()
|
||||
|
||||
26
layout/includes/third-party/comments/livere.pug
vendored
26
layout/includes/third-party/comments/livere.pug
vendored
@@ -1,8 +1,23 @@
|
||||
- const { use, lazyload } = theme.comments
|
||||
|
||||
script.
|
||||
(()=>{
|
||||
const loadLivere = () => {
|
||||
(() => {
|
||||
const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'
|
||||
|
||||
const loadLivere = (el, path) => {
|
||||
window.livereOptions = {
|
||||
refer: path || location.pathname
|
||||
}
|
||||
|
||||
if (isShuoshuo) {
|
||||
window.shuoshuoComment.destroyLivere = () => {
|
||||
if (el.children.length) {
|
||||
el.innerHTML = ''
|
||||
el.classList.add('no-comment')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof LivereTower === 'object') window.LivereTower.init()
|
||||
else {
|
||||
(function(d, s) {
|
||||
@@ -16,6 +31,13 @@ script.
|
||||
}
|
||||
}
|
||||
|
||||
if (isShuoshuo) {
|
||||
'!{use[0]}' === 'Livere'
|
||||
? window.shuoshuoComment = { loadComment: loadLivere }
|
||||
: window.loadOtherComment = loadLivere
|
||||
return
|
||||
}
|
||||
|
||||
if ('!{use[0]}' === 'Livere' || !!{lazyload}) {
|
||||
if (!{lazyload}) btf.loadComment(document.getElementById('lv-container'), loadLivere)
|
||||
else loadLivere()
|
||||
|
||||
@@ -1,68 +1,78 @@
|
||||
- const { host, siteId, option } = theme.remark42
|
||||
|
||||
script.
|
||||
var remark_config = Object.assign({
|
||||
host: '!{host}',
|
||||
site_id: '!{siteId}',
|
||||
components: ['embed'],
|
||||
theme: document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'light'
|
||||
},!{JSON.stringify(option)})
|
||||
(() => {
|
||||
const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'
|
||||
const options = !{JSON.stringify(option)}
|
||||
|
||||
function addRemark42(){
|
||||
for (let i = 0; i < remark_config.components.length; i++) {
|
||||
const s = document.createElement('script')
|
||||
s.src = remark_config.host + '/web/' + remark_config.components[i] + '.js'
|
||||
s.defer = true
|
||||
document.head.appendChild(s)
|
||||
}
|
||||
const loadScript = src => {
|
||||
const script = document.createElement('script')
|
||||
script.src = src
|
||||
script.defer = true
|
||||
document.head.appendChild(script)
|
||||
}
|
||||
|
||||
function initRemark42() {
|
||||
const addRemark42 = () => loadScript('!{host}/web/embed.js')
|
||||
|
||||
const getCount = () => document.querySelector('.remark42__counter') && loadScript('!{host}/web/count.js')
|
||||
|
||||
const destroyRemark42 = () => window.remark42Instance && window.remark42Instance.destroy()
|
||||
|
||||
const initRemark42 = remark_config => {
|
||||
if (window.REMARK42) {
|
||||
if (this.remark42Instance) {
|
||||
this.remark42Instance.destroy()
|
||||
}
|
||||
|
||||
this.remark42Instance = window.REMARK42.createInstance({
|
||||
destroyRemark42()
|
||||
window.remark42Instance = window.REMARK42.createInstance({
|
||||
...remark_config
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function getCount () {
|
||||
const ele = document.querySelector('.remark42__counter')
|
||||
if (ele) {
|
||||
const s = document.createElement('script')
|
||||
s.src = remark_config.host + '/web/counter.js'
|
||||
s.defer = true
|
||||
document.head.appendChild(s)
|
||||
const loadRemark42 = (el, path) => {
|
||||
if (isShuoshuo) {
|
||||
window.shuoshuoComment.destroyRemark42 = () => {
|
||||
destroyRemark42()
|
||||
if (el.children.length) {
|
||||
el.innerHTML = ''
|
||||
el.classList.add('no-comment')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function loadRemark42 () {
|
||||
window.remark_config = {
|
||||
host: '!{host}',
|
||||
site_id: '!{siteId}',
|
||||
theme: document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'light',
|
||||
...options,
|
||||
url: isShuoshuo ? window.location.origin + path : (options && options.url) || window.location.origin + window.location.pathname
|
||||
}
|
||||
|
||||
if (window.REMARK42) {
|
||||
this.initRemark42()
|
||||
initRemark42(remark_config)
|
||||
getCount()
|
||||
} else {
|
||||
addRemark42()
|
||||
window.addEventListener('REMARK42::ready', () => {
|
||||
this.initRemark42()
|
||||
initRemark42(remark_config)
|
||||
getCount()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function remarkChangeMode (theme) {
|
||||
if (!window.REMARK42) return
|
||||
window.REMARK42.changeTheme(theme)
|
||||
}
|
||||
const remarkChangeMode = theme => window.REMARK42 && window.REMARK42.changeTheme(theme)
|
||||
|
||||
btf.addGlobalFn('themeChange', remarkChangeMode, 'remark42')
|
||||
|
||||
if (isShuoshuo) {
|
||||
'!{theme.comments.use[0]}' === 'Remark42'
|
||||
? window.shuoshuoComment = { loadComment: loadRemark42 }
|
||||
: window.loadOtherComment = loadRemark42
|
||||
return
|
||||
}
|
||||
|
||||
if ('!{theme.comments.use[0]}' === 'Remark42' || !!{theme.comments.lazyload}) {
|
||||
if (!{theme.comments.lazyload}) btf.loadComment(document.getElementById('remark42'), loadRemark42)
|
||||
else loadRemark42()
|
||||
} else {
|
||||
function loadOtherComment () {
|
||||
loadRemark42()
|
||||
}
|
||||
window.loadOtherComment = loadRemark42
|
||||
}
|
||||
})()
|
||||
37
layout/includes/third-party/comments/twikoo.pug
vendored
37
layout/includes/third-party/comments/twikoo.pug
vendored
@@ -3,6 +3,9 @@
|
||||
|
||||
script.
|
||||
(() => {
|
||||
const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'
|
||||
const option = !{JSON.stringify(option)}
|
||||
|
||||
const getCount = () => {
|
||||
const countELement = document.getElementById('twikoo-count')
|
||||
if(!countELement) return
|
||||
@@ -18,22 +21,38 @@ script.
|
||||
})
|
||||
}
|
||||
|
||||
const init = () => {
|
||||
twikoo.init(Object.assign({
|
||||
el: '#twikoo-wrap',
|
||||
const init = (el = document, path = location.pathname) => {
|
||||
twikoo.init({
|
||||
el: el.querySelector('#twikoo-wrap'),
|
||||
envId: '!{envId}',
|
||||
region: '!{region}',
|
||||
onCommentLoaded: () => {
|
||||
btf.loadLightbox(document.querySelectorAll('#twikoo .tk-content img:not(.tk-owo-emotion)'))
|
||||
}
|
||||
}, !{JSON.stringify(option)}))
|
||||
},
|
||||
...option,
|
||||
path: isShuoshuo ? path : (option && option.path) || path
|
||||
})
|
||||
|
||||
!{count ? 'GLOBAL_CONFIG_SITE.isPost && getCount()' : ''}
|
||||
!{count ? `GLOBAL_CONFIG_SITE.pageType === 'post' && getCount()` : ''}
|
||||
|
||||
isShuoshuo && (window.shuoshuoComment.destroyTwikoo = () => {
|
||||
if (el.children.length) {
|
||||
el.innerHTML = ''
|
||||
el.classList.add('no-comment')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const loadTwikoo = () => {
|
||||
if (typeof twikoo === 'object') setTimeout(init,0)
|
||||
else btf.getScript('!{url_for(theme.asset.twikoo)}').then(init)
|
||||
const loadTwikoo = (el, path) => {
|
||||
if (typeof twikoo === 'object') setTimeout(() => init(el, path), 0)
|
||||
else btf.getScript('!{url_for(theme.asset.twikoo)}').then(() => init(el, path))
|
||||
}
|
||||
|
||||
if (isShuoshuo) {
|
||||
'!{use[0]}' === 'Twikoo'
|
||||
? window.shuoshuoComment = { loadComment: loadTwikoo }
|
||||
: window.loadOtherComment = loadTwikoo
|
||||
return
|
||||
}
|
||||
|
||||
if ('!{use[0]}' === 'Twikoo' || !!{lazyload}) {
|
||||
|
||||
@@ -5,24 +5,33 @@
|
||||
|
||||
script.
|
||||
(() => {
|
||||
const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'
|
||||
const option = !{JSON.stringify(option)}
|
||||
const getUtterancesTheme = theme => theme === 'dark' ? '#{dark_theme}' : '#{light_theme}'
|
||||
|
||||
const loadUtterances = () => {
|
||||
const config = Object.assign({
|
||||
id: 'utterances_comment',
|
||||
const loadUtterances = (el = document, key) => {
|
||||
if (isShuoshuo) {
|
||||
window.shuoshuoComment.destroyUtterances = () => {
|
||||
if (el.children.length) {
|
||||
el.innerHTML = ''
|
||||
el.classList.add('no-comment')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const config = {
|
||||
src: '!{utterancesUrl}',
|
||||
repo: '!{repo}',
|
||||
'issue-term': '!{issue_term}',
|
||||
theme: getUtterancesTheme(document.documentElement.getAttribute('data-theme')),
|
||||
crossorigin: 'anonymous',
|
||||
async: true
|
||||
},!{JSON.stringify(option)})
|
||||
async: true,
|
||||
...option,
|
||||
'issue-term': isShuoshuo ? key : (option && option['issue-term']) || '!{issue_term}'
|
||||
}
|
||||
|
||||
const ele = document.createElement('script')
|
||||
for (let key in config) {
|
||||
ele.setAttribute(key, config[key])
|
||||
}
|
||||
document.getElementById('utterances-wrap').appendChild(ele)
|
||||
Object.entries(config).forEach(([key, value]) => ele.setAttribute(key, value))
|
||||
el.querySelector('#utterances-wrap').appendChild(ele)
|
||||
}
|
||||
|
||||
const changeUtterancesTheme = theme => {
|
||||
@@ -38,6 +47,13 @@ script.
|
||||
|
||||
btf.addGlobalFn('themeChange', changeUtterancesTheme, 'utterances')
|
||||
|
||||
if (isShuoshuo) {
|
||||
'!{use[0]}' === 'Utterances'
|
||||
? window.shuoshuoComment = { loadComment: loadUtterances }
|
||||
: window.loadOtherComment = loadUtterances
|
||||
return
|
||||
}
|
||||
|
||||
if ('!{use[0]}' === 'Utterances' || !!{lazyload}) {
|
||||
if (!{lazyload}) btf.loadComment(document.getElementById('utterances-wrap'), loadUtterances)
|
||||
else loadUtterances()
|
||||
|
||||
44
layout/includes/third-party/comments/valine.pug
vendored
44
layout/includes/third-party/comments/valine.pug
vendored
@@ -7,25 +7,48 @@ if site.data.valine
|
||||
|
||||
script.
|
||||
(() => {
|
||||
const initValine = () => {
|
||||
const valine = new Valine(Object.assign({
|
||||
const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'
|
||||
const option = !{JSON.stringify(option)}
|
||||
|
||||
const initValine = (el, path) => {
|
||||
if (isShuoshuo) {
|
||||
window.shuoshuoComment.destroyValine = () => {
|
||||
if (el.children.length) {
|
||||
el.innerHTML = ''
|
||||
el.classList.add('no-comment')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const valineConfig = {
|
||||
el: '#vcomment',
|
||||
appId: '#{appId}',
|
||||
appKey: '#{appKey}',
|
||||
avatar: '#{avatar}',
|
||||
serverURLs: '#{serverURLs}',
|
||||
emojiMaps: !{emojiMaps},
|
||||
path: window.location.pathname,
|
||||
visitor: #{visitor}
|
||||
}, !{JSON.stringify(option)}))
|
||||
visitor: #{visitor},
|
||||
...option,
|
||||
path: isShuoshuo ? path : (option && option.path) || window.location.pathname
|
||||
}
|
||||
|
||||
const loadValine = async () => {
|
||||
if (typeof Valine === 'function') initValine()
|
||||
else {
|
||||
await btf.getScript('!{url_for(theme.asset.valine)}')
|
||||
initValine()
|
||||
new Valine(valineConfig)
|
||||
}
|
||||
|
||||
const loadValine = async (el, path) => {
|
||||
if (typeof Valine === 'function') {
|
||||
initValine(el, path)
|
||||
} else {
|
||||
await btf.getScript('!{url_for(theme.asset.valine)}')
|
||||
initValine(el, path)
|
||||
}
|
||||
}
|
||||
|
||||
if (isShuoshuo) {
|
||||
'!{use[0]}' === 'Valine'
|
||||
? window.shuoshuoComment = { loadComment: loadValine }
|
||||
: window.loadOtherComment = loadValine
|
||||
return
|
||||
}
|
||||
|
||||
if ('!{use[0]}' === 'Valine' || !!{lazyload}) {
|
||||
@@ -35,4 +58,3 @@ script.
|
||||
window.loadOtherComment = loadValine
|
||||
}
|
||||
})()
|
||||
|
||||
|
||||
42
layout/includes/third-party/comments/waline.pug
vendored
42
layout/includes/third-party/comments/waline.pug
vendored
@@ -4,37 +4,53 @@
|
||||
script.
|
||||
(() => {
|
||||
let initFn = window.walineFn || null
|
||||
const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'
|
||||
const option = !{JSON.stringify(option)}
|
||||
|
||||
const initWaline = (Fn) => {
|
||||
const waline = Fn(Object.assign({
|
||||
el: '#waline-wrap',
|
||||
const destroyWaline = ele => ele.destroy()
|
||||
|
||||
const initWaline = (Fn, el = document, path = window.location.pathname) => {
|
||||
const waline = Fn({
|
||||
el: el.querySelector('#waline-wrap'),
|
||||
serverURL: '!{serverURL}',
|
||||
pageview: !{lazyload ? false : pageview},
|
||||
dark: 'html[data-theme="dark"]',
|
||||
path: window.location.pathname,
|
||||
comment: !{lazyload ? false : count},
|
||||
}, !{JSON.stringify(option)}))
|
||||
...option,
|
||||
path: isShuoshuo ? path : (option && option.path) || path
|
||||
})
|
||||
|
||||
const destroyWaline = () => {
|
||||
waline.destroy()
|
||||
if (isShuoshuo) {
|
||||
window.shuoshuoComment.destroyWaline = () => {
|
||||
destroyWaline(waline)
|
||||
if (el.children.length) {
|
||||
el.innerHTML = ''
|
||||
el.classList.add('no-comment')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
btf.addGlobalFn('pjaxSendOnce', destroyWaline, 'destroyWaline')
|
||||
}
|
||||
|
||||
const loadWaline = () => {
|
||||
if (initFn) initWaline(initFn)
|
||||
const loadWaline = (el, path) => {
|
||||
if (initFn) initWaline(initFn, el, path)
|
||||
else {
|
||||
btf.getCSS('!{url_for(theme.asset.waline_css)}')
|
||||
.then(() => import('!{url_for(theme.asset.waline_js)}'))
|
||||
.then(({ init }) => {
|
||||
initFn = init || Waline.init
|
||||
initWaline(initFn)
|
||||
initWaline(initFn, el, path)
|
||||
window.walineFn = initFn
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (isShuoshuo) {
|
||||
'!{use[0]}' === 'Waline'
|
||||
? window.shuoshuoComment = { loadComment: loadWaline }
|
||||
: window.loadOtherComment = loadWaline
|
||||
return
|
||||
}
|
||||
|
||||
if ('!{use[0]}' === 'Waline' || !!{lazyload}) {
|
||||
if (!{lazyload}) btf.loadComment(document.getElementById('waline-wrap'),loadWaline)
|
||||
else setTimeout(loadWaline, 0)
|
||||
|
||||
91
layout/includes/third-party/math/chartjs.pug
vendored
Normal file
91
layout/includes/third-party/math/chartjs.pug
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
- const { fontColor, borderColor, scale_ticks_backdropColor } = theme.chartjs
|
||||
|
||||
script.
|
||||
(() => {
|
||||
const applyThemeDefaultsConfig = theme => {
|
||||
if (theme === 'dark-mode') {
|
||||
Chart.defaults.color = "!{fontColor.dark}"
|
||||
Chart.defaults.borderColor = "!{borderColor.dark}"
|
||||
Chart.defaults.scale.ticks.backdropColor = "!{scale_ticks_backdropColor.dark}"
|
||||
} else {
|
||||
Chart.defaults.color = "!{fontColor.light}"
|
||||
Chart.defaults.borderColor = "!{borderColor.light}"
|
||||
Chart.defaults.scale.ticks.backdropColor = "!{scale_ticks_backdropColor.light}"
|
||||
}
|
||||
}
|
||||
|
||||
// Recursively traverse the config object and automatically apply theme-specific color schemes
|
||||
const applyThemeConfig = (obj, theme) => {
|
||||
if (typeof obj !== 'object' || obj === null) return
|
||||
|
||||
Object.keys(obj).forEach(key => {
|
||||
const value = obj[key]
|
||||
// If the property is an object and has theme-specific options, apply them
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
if (value[theme]) {
|
||||
obj[key] = value[theme] // Apply the value for the current theme
|
||||
} else {
|
||||
// Recursively process child objects
|
||||
applyThemeConfig(value, theme)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const runChartJS = ele => {
|
||||
window.loadChartJS = true
|
||||
|
||||
Array.from(ele).forEach((item, index) => {
|
||||
const chartSrc = item.firstElementChild
|
||||
const chartID = item.getAttribute('data-chartjs-id') || ('chartjs-' + index) // Use custom ID or default ID
|
||||
const width = item.getAttribute('data-width')
|
||||
const existingCanvas = document.getElementById(chartID)
|
||||
|
||||
// If a canvas already exists, remove it to avoid rendering duplicates
|
||||
if (existingCanvas) {
|
||||
existingCanvas.parentNode.remove()
|
||||
}
|
||||
|
||||
const chartDefinition = chartSrc.textContent
|
||||
const canvas = document.createElement('canvas')
|
||||
canvas.id = chartID
|
||||
|
||||
const div = document.createElement('div')
|
||||
div.className = 'chartjs-wrap'
|
||||
|
||||
if (width) {
|
||||
div.style.width = width
|
||||
}
|
||||
|
||||
div.appendChild(canvas)
|
||||
chartSrc.insertAdjacentElement('afterend', div)
|
||||
|
||||
const ctx = document.getElementById(chartID).getContext('2d')
|
||||
|
||||
const config = JSON.parse(chartDefinition)
|
||||
|
||||
const theme = document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark-mode' : 'light-mode'
|
||||
|
||||
// Set default styles (initial setup)
|
||||
applyThemeDefaultsConfig(theme)
|
||||
|
||||
// Automatically traverse the config and apply dual-mode color schemes
|
||||
applyThemeConfig(config, theme)
|
||||
|
||||
new Chart(ctx, config)
|
||||
})
|
||||
}
|
||||
|
||||
const loadChartJS = () => {
|
||||
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
|
||||
btf.addGlobalFn('themeChange', loadChartJS, 'chartjs')
|
||||
btf.addGlobalFn('encrypt', loadChartJS, 'chartjs')
|
||||
|
||||
window.pjax ? loadChartJS() : document.addEventListener('DOMContentLoaded', loadChartJS)
|
||||
})()
|
||||
7
layout/includes/third-party/math/index.pug
vendored
7
layout/includes/third-party/math/index.pug
vendored
@@ -1,11 +1,14 @@
|
||||
case theme.math.use
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
if theme.mermaid.enable
|
||||
include ./mermaid.pug
|
||||
|
||||
if theme.chartjs.enable
|
||||
include ./chartjs.pug
|
||||
33
layout/includes/third-party/math/mathjax.pug
vendored
33
layout/includes/third-party/math/mathjax.pug
vendored
@@ -1,19 +1,50 @@
|
||||
//- Mathjax 3
|
||||
//- Mathjax 4/5
|
||||
- const { tags, enableMenu } = theme.math.mathjax
|
||||
script.
|
||||
(() => {
|
||||
const loadMathjax = () => {
|
||||
if (!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: {
|
||||
inlineMath: [['$', '$'], ['\\(', '\\)']],
|
||||
tags: '!{tags}',
|
||||
packages: {
|
||||
'[+]': [
|
||||
'mhchem'
|
||||
]
|
||||
}
|
||||
},
|
||||
chtml: {
|
||||
scale: 1.1
|
||||
},
|
||||
options: {
|
||||
enableMenu: !{enableMenu},
|
||||
menuOptions: {
|
||||
settings: {
|
||||
enrich: false // Turn off Braille and voice narration text automatic generation
|
||||
}
|
||||
},
|
||||
renderActions: {
|
||||
findScript: [10, doc => {
|
||||
for (const node of document.querySelectorAll('script[type^="math/tex"]')) {
|
||||
|
||||
274
layout/includes/third-party/math/mermaid.pug
vendored
274
layout/includes/third-party/math/mermaid.pug
vendored
@@ -1,20 +1,292 @@
|
||||
script.
|
||||
(() => {
|
||||
const parseViewBox = viewBox => {
|
||||
if (!viewBox) return null
|
||||
const parts = viewBox.trim().split(/[\s,]+/).map(n => Number(n))
|
||||
if (parts.length !== 4 || parts.some(n => Number.isNaN(n))) return null
|
||||
return parts
|
||||
}
|
||||
|
||||
const getSvgViewBox = svg => {
|
||||
const attr = parseViewBox(svg.getAttribute('viewBox'))
|
||||
if (attr) return attr
|
||||
|
||||
// Fallback: use bbox to build a viewBox
|
||||
try {
|
||||
const bbox = svg.getBBox()
|
||||
if (bbox && bbox.width && bbox.height) return [bbox.x, bbox.y, bbox.width, bbox.height]
|
||||
} catch (e) {
|
||||
// getBBox may fail on some edge cases; ignore
|
||||
}
|
||||
|
||||
const w = Number(svg.getAttribute('width')) || 0
|
||||
const h = Number(svg.getAttribute('height')) || 0
|
||||
if (w > 0 && h > 0) return [0, 0, w, h]
|
||||
return [0, 0, 100, 100]
|
||||
}
|
||||
|
||||
const setSvgViewBox = (svg, vb) => {
|
||||
svg.setAttribute('viewBox', `${vb[0]} ${vb[1]} ${vb[2]} ${vb[3]}`)
|
||||
}
|
||||
|
||||
const clamp = (v, min, max) => Math.max(min, Math.min(max, v))
|
||||
|
||||
const openSvgInNewTab = ({ source, initViewBox }) => {
|
||||
const getClonedSvg = () => {
|
||||
if (typeof source === 'string') {
|
||||
const template = document.createElement('template')
|
||||
template.innerHTML = source.trim()
|
||||
const svg = template.content.querySelector('svg')
|
||||
return svg ? svg.cloneNode(true) : null
|
||||
}
|
||||
if (source && typeof source.cloneNode === 'function') {
|
||||
return source.cloneNode(true)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const clone = getClonedSvg()
|
||||
if (!clone) return
|
||||
if (initViewBox && initViewBox.length === 4) {
|
||||
clone.setAttribute('viewBox', initViewBox.join(' '))
|
||||
}
|
||||
if (!clone.getAttribute('xmlns')) clone.setAttribute('xmlns', 'http://www.w3.org/2000/svg')
|
||||
if (!clone.getAttribute('xmlns:xlink') && clone.outerHTML.includes('xlink:')) {
|
||||
clone.setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink')
|
||||
}
|
||||
// inject background to match current theme
|
||||
const isDark = document.documentElement.getAttribute('data-theme') === 'dark'
|
||||
const bg = getComputedStyle(document.body).backgroundColor || (isDark ? '#1e1e1e' : '#ffffff')
|
||||
if (!clone.style.background) clone.style.background = bg
|
||||
|
||||
const serializer = new XMLSerializer()
|
||||
const svgSource = serializer.serializeToString(clone)
|
||||
const htmlSource = `<!doctype html><html><head><meta charset="utf-8" />
|
||||
<style>
|
||||
html, body { width: 100%; height: 100%; margin: 0; display: flex; align-items: center; justify-content: center; background: ${bg}; }
|
||||
svg { max-width: 100%; max-height: 100%; height: auto; width: auto; }
|
||||
</style>
|
||||
</head><body>${svgSource}</body></html>`
|
||||
const blob = new Blob([htmlSource], { type: 'text/html;charset=utf-8' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
window.open(url, '_blank', 'noopener')
|
||||
setTimeout(() => URL.revokeObjectURL(url), 30000)
|
||||
}
|
||||
|
||||
const attachMermaidViewerButton = wrap => {
|
||||
let btn = wrap.querySelector('.mermaid-open-btn')
|
||||
if (!btn) {
|
||||
btn = document.createElement('button')
|
||||
btn.type = 'button'
|
||||
btn.className = 'mermaid-open-btn'
|
||||
wrap.appendChild(btn)
|
||||
}
|
||||
|
||||
btn.innerHTML = '<i class="fa fa-search fa-fw" aria-hidden="true"></i>'
|
||||
|
||||
if (!btn.__mermaidViewerBound) {
|
||||
btn.addEventListener('click', e => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
const svg = wrap.__mermaidOriginalSvg || wrap.querySelector('svg')
|
||||
if (!svg) return
|
||||
const initViewBox = wrap.__mermaidInitViewBox
|
||||
if (typeof svg === 'string') {
|
||||
openSvgInNewTab({ source: svg, initViewBox })
|
||||
return
|
||||
}
|
||||
openSvgInNewTab({ source: svg, initViewBox })
|
||||
})
|
||||
btn.__mermaidViewerBound = true
|
||||
}
|
||||
}
|
||||
|
||||
// Zoom around a point (px, py) in the SVG viewport (in viewBox coordinates)
|
||||
const zoomAtPoint = (vb, factor, px, py) => {
|
||||
const w = vb[2] * factor
|
||||
const h = vb[3] * factor
|
||||
const nx = px - (px - vb[0]) * factor
|
||||
const ny = py - (py - vb[1]) * factor
|
||||
return [nx, ny, w, h]
|
||||
}
|
||||
|
||||
const initMermaidGestures = wrap => {
|
||||
const svg = wrap.querySelector('svg')
|
||||
if (!svg) return
|
||||
|
||||
// Ensure viewBox exists so gestures always work
|
||||
const initVb = getSvgViewBox(svg)
|
||||
wrap.__mermaidInitViewBox = initVb
|
||||
wrap.__mermaidCurViewBox = initVb.slice()
|
||||
setSvgViewBox(svg, initVb)
|
||||
|
||||
// Avoid binding multiple times on themeChange/pjax
|
||||
if (wrap.__mermaidGestureBound) return
|
||||
wrap.__mermaidGestureBound = true
|
||||
|
||||
// Helper: map client (viewport) coordinate -> viewBox coordinate
|
||||
const clientToViewBox = (clientX, clientY) => {
|
||||
const rect = svg.getBoundingClientRect()
|
||||
const vb = wrap.__mermaidCurViewBox || getSvgViewBox(svg)
|
||||
const x = vb[0] + (clientX - rect.left) * (vb[2] / rect.width)
|
||||
const y = vb[1] + (clientY - rect.top) * (vb[3] / rect.height)
|
||||
return { x, y, rect, vb }
|
||||
}
|
||||
|
||||
const state = {
|
||||
pointers: new Map(),
|
||||
startVb: null,
|
||||
startDist: 0,
|
||||
startCenter: null
|
||||
}
|
||||
|
||||
const clampVb = vb => {
|
||||
const init = wrap.__mermaidInitViewBox || vb
|
||||
const minW = init[2] * 0.1
|
||||
const maxW = init[2] * 10
|
||||
const minH = init[3] * 0.1
|
||||
const maxH = init[3] * 10
|
||||
vb[2] = clamp(vb[2], minW, maxW)
|
||||
vb[3] = clamp(vb[3], minH, maxH)
|
||||
return vb
|
||||
}
|
||||
|
||||
const setCurVb = vb => {
|
||||
vb = clampVb(vb)
|
||||
wrap.__mermaidCurViewBox = vb
|
||||
setSvgViewBox(svg, vb)
|
||||
}
|
||||
|
||||
const onPointerDown = e => {
|
||||
// Allow only primary button for mouse
|
||||
if (e.pointerType === 'mouse' && e.button !== 0) return
|
||||
svg.setPointerCapture(e.pointerId)
|
||||
state.pointers.set(e.pointerId, { x: e.clientX, y: e.clientY })
|
||||
|
||||
if (state.pointers.size === 1) {
|
||||
state.startVb = (wrap.__mermaidCurViewBox || getSvgViewBox(svg)).slice()
|
||||
} else if (state.pointers.size === 2) {
|
||||
const pts = [...state.pointers.values()]
|
||||
const dx = pts[0].x - pts[1].x
|
||||
const dy = pts[0].y - pts[1].y
|
||||
state.startDist = Math.hypot(dx, dy)
|
||||
state.startVb = (wrap.__mermaidCurViewBox || getSvgViewBox(svg)).slice()
|
||||
state.startCenter = { x: (pts[0].x + pts[1].x) / 2, y: (pts[0].y + pts[1].y) / 2 }
|
||||
}
|
||||
}
|
||||
|
||||
const onPointerMove = e => {
|
||||
if (!state.pointers.has(e.pointerId)) return
|
||||
state.pointers.set(e.pointerId, { x: e.clientX, y: e.clientY })
|
||||
|
||||
// Pan with 1 pointer
|
||||
if (state.pointers.size === 1 && state.startVb) {
|
||||
const p = [...state.pointers.values()][0]
|
||||
const prev = { x: e.clientX - e.movementX, y: e.clientY - e.movementY }
|
||||
// movementX/Y unreliable on touch, compute from stored last position
|
||||
const last = wrap.__mermaidLastSinglePointer || p
|
||||
const dxClient = p.x - last.x
|
||||
const dyClient = p.y - last.y
|
||||
wrap.__mermaidLastSinglePointer = p
|
||||
|
||||
const { rect } = clientToViewBox(p.x, p.y)
|
||||
const vb = (wrap.__mermaidCurViewBox || getSvgViewBox(svg)).slice()
|
||||
const dx = dxClient * (vb[2] / rect.width)
|
||||
const dy = dyClient * (vb[3] / rect.height)
|
||||
setCurVb([vb[0] - dx, vb[1] - dy, vb[2], vb[3]])
|
||||
return
|
||||
}
|
||||
|
||||
// Pinch zoom with 2 pointers
|
||||
if (state.pointers.size === 2 && state.startVb && state.startDist > 0) {
|
||||
const pts = [...state.pointers.values()]
|
||||
const dx = pts[0].x - pts[1].x
|
||||
const dy = pts[0].y - pts[1].y
|
||||
const dist = Math.hypot(dx, dy)
|
||||
if (!dist) return
|
||||
const factor = state.startDist / dist // dist bigger => zoom in (viewBox smaller)
|
||||
|
||||
const cx = (pts[0].x + pts[1].x) / 2
|
||||
const cy = (pts[0].y + pts[1].y) / 2
|
||||
const centerClient = { x: cx, y: cy }
|
||||
|
||||
const pxy = clientToViewBox(centerClient.x, centerClient.y)
|
||||
const cpx = pxy.x
|
||||
const cpy = pxy.y
|
||||
|
||||
const vb = zoomAtPoint(state.startVb, factor, cpx, cpy)
|
||||
setCurVb(vb)
|
||||
}
|
||||
}
|
||||
|
||||
const onPointerUpOrCancel = e => {
|
||||
state.pointers.delete(e.pointerId)
|
||||
if (state.pointers.size === 0) {
|
||||
state.startVb = null
|
||||
state.startDist = 0
|
||||
state.startCenter = null
|
||||
wrap.__mermaidLastSinglePointer = null
|
||||
} else if (state.pointers.size === 1) {
|
||||
// reset single pointer baseline to avoid jump
|
||||
wrap.__mermaidLastSinglePointer = [...state.pointers.values()][0]
|
||||
}
|
||||
}
|
||||
|
||||
// Wheel zoom (mouse/trackpad)
|
||||
const onWheel = e => {
|
||||
// ctrlKey on mac trackpad pinch; we treat both as zoom
|
||||
e.preventDefault()
|
||||
const delta = e.deltaY
|
||||
const zoomFactor = delta > 0 ? 1.1 : 0.9
|
||||
const { x, y } = clientToViewBox(e.clientX, e.clientY)
|
||||
const vb = (wrap.__mermaidCurViewBox || getSvgViewBox(svg)).slice()
|
||||
setCurVb(zoomAtPoint(vb, zoomFactor, x, y))
|
||||
}
|
||||
|
||||
const onDblClick = () => {
|
||||
const init = wrap.__mermaidInitViewBox
|
||||
if (!init) return
|
||||
wrap.__mermaidCurViewBox = init.slice()
|
||||
setSvgViewBox(svg, init)
|
||||
}
|
||||
|
||||
svg.addEventListener('pointerdown', onPointerDown)
|
||||
svg.addEventListener('pointermove', onPointerMove)
|
||||
svg.addEventListener('pointerup', onPointerUpOrCancel)
|
||||
svg.addEventListener('pointercancel', onPointerUpOrCancel)
|
||||
svg.addEventListener('wheel', onWheel, { passive: false })
|
||||
svg.addEventListener('dblclick', onDblClick)
|
||||
}
|
||||
|
||||
const runMermaid = ele => {
|
||||
window.loadMermaid = true
|
||||
const theme = document.documentElement.getAttribute('data-theme') === 'dark' ? '!{theme.mermaid.theme.dark}' : '!{theme.mermaid.theme.light}'
|
||||
|
||||
ele.forEach((item, index) => {
|
||||
const mermaidSrc = item.firstElementChild
|
||||
const mermaidThemeConfig = `%%{init:{ 'theme':'${theme}'}}%%\n`
|
||||
|
||||
// Clear old render (themeChange/pjax will rerun)
|
||||
const oldSvg = item.querySelector('svg')
|
||||
if (oldSvg) oldSvg.remove()
|
||||
item.__mermaidGestureBound = false
|
||||
|
||||
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 mermaidDefinition = mermaidThemeConfig + mermaidSrc.textContent
|
||||
|
||||
const renderFn = mermaid.render(mermaidID, mermaidDefinition)
|
||||
const renderMermaid = svg => {
|
||||
mermaidSrc.insertAdjacentHTML('afterend', svg)
|
||||
if (!{theme.mermaid.zoom_pan}) initMermaidGestures(item)
|
||||
item.__mermaidOriginalSvg = svg
|
||||
if (!{theme.mermaid.open_in_new_tab}) attachMermaidViewerButton(item)
|
||||
}
|
||||
|
||||
|
||||
// mermaid v9 and v10 compatibility
|
||||
typeof renderFn === 'string' ? renderMermaid(renderFn) : renderFn.then(({ svg }) => renderMermaid(svg))
|
||||
})
|
||||
|
||||
@@ -34,7 +34,7 @@ script.
|
||||
|
||||
const searchParams = new URLSearchParams({
|
||||
'site_name': '!{site}',
|
||||
'limit': '!{theme.aside.card_newest_comments.limit}',
|
||||
'limit': '!{newestCommentsLimit * 2}', // Fetch more comments to filter pending comments
|
||||
})
|
||||
|
||||
const getComment = async (ele) => {
|
||||
@@ -42,7 +42,10 @@ script.
|
||||
const res = await fetch(`!{server}/api/v2/stats/latest_comments?${searchParams}`)
|
||||
const result = await res.json()
|
||||
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}` : ''
|
||||
return {
|
||||
'avatar': avatar,
|
||||
|
||||
@@ -23,8 +23,9 @@ script.
|
||||
result += '<div class="aside-list-item">'
|
||||
|
||||
if (!{theme.aside.card_newest_comments.avatar} && array[i].avatar) {
|
||||
const imgAttr = '!{theme.lazyload.enable ? "data-lazy-src" : "src"}'
|
||||
result += `<a href="${array[i].url}" class="thumbnail"><img ${imgAttr}="${array[i].avatar}" alt="${array[i].nick}"></a>`
|
||||
const imgAttr = '!{theme.lazyload.enable && !theme.lazyload.native ? "data-lazy-src" : "src"}'
|
||||
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">
|
||||
|
||||
@@ -6,7 +6,7 @@ script.
|
||||
const { changeContent, generateHtml, run } = window.newestComments
|
||||
|
||||
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(data => {
|
||||
const disqusArray = data.response.map(item => {
|
||||
|
||||
@@ -32,7 +32,7 @@ script.
|
||||
}
|
||||
|
||||
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": {
|
||||
Accept: 'application/vnd.github.v3.html+json'
|
||||
}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
- let { use } = theme.comments
|
||||
|
||||
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]
|
||||
when 'Valine'
|
||||
include ./valine.pug
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
- const { host, siteId } = theme.remark42
|
||||
!= partial("includes/third-party/newest-comments/common.pug", {}, { cache: true })
|
||||
|
||||
script.
|
||||
window.addEventListener('load', () => {
|
||||
@@ -6,7 +7,7 @@ script.
|
||||
const { changeContent, generateHtml, run } = window.newestComments
|
||||
|
||||
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(data => {
|
||||
const remark42 = data.map(e => {
|
||||
|
||||
@@ -10,7 +10,7 @@ script.
|
||||
twikoo.getRecentComments({
|
||||
envId: '!{theme.twikoo.envId}',
|
||||
region: '!{theme.twikoo.region}',
|
||||
pageSize: !{theme.aside.card_newest_comments.limit},
|
||||
pageSize: !{newestCommentsLimit},
|
||||
includeReply: true
|
||||
}).then(res => {
|
||||
const twikooArray = res.map(e => {
|
||||
|
||||
@@ -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(data => {
|
||||
const valineArray = data.results.map(e => {
|
||||
|
||||
@@ -9,7 +9,7 @@ script.
|
||||
|
||||
const getComment = async (ele) => {
|
||||
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 walineArray = result.data.map(e => {
|
||||
return {
|
||||
|
||||
23
layout/includes/third-party/pangu.pug
vendored
23
layout/includes/third-party/pangu.pug
vendored
@@ -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)
|
||||
})()
|
||||
26
layout/includes/third-party/pjax.pug
vendored
26
layout/includes/third-party/pjax.pug
vendored
@@ -7,14 +7,16 @@ if theme.pjax.exclude
|
||||
|
||||
- let choose = theme.comments.use
|
||||
if choose
|
||||
if theme.Open_Graph_meta.enable && (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')
|
||||
if choose.includes('Livere') || choose.includes('Utterances') || choose.includes('Giscus')
|
||||
- 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) defer)
|
||||
script.
|
||||
(() => {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const pjaxSelectors = !{JSON.stringify(pjaxSelectors)}
|
||||
|
||||
window.pjax = new Pjax({
|
||||
@@ -27,7 +29,13 @@ script.
|
||||
|
||||
const triggerPjaxFn = (val) => {
|
||||
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', () => {
|
||||
@@ -57,7 +65,9 @@ script.
|
||||
|
||||
document.addEventListener('pjax:error', e => {
|
||||
if (e.request.status === 404) {
|
||||
pjax.loadUrl('!{url_for("/404.html")}')
|
||||
!{theme.error_404 && theme.error_404.enable}
|
||||
? pjax.loadUrl('!{url_for("/404.html")}')
|
||||
: window.location.href = e.request.responseURL
|
||||
}
|
||||
})
|
||||
})()
|
||||
})
|
||||
6
layout/includes/third-party/prismjs.pug
vendored
6
layout/includes/third-party/prismjs.pug
vendored
@@ -17,7 +17,7 @@ if (syntax_highlighter === 'prismjs' || enable) && !preprocess
|
||||
btf.addGlobalFn('encrypt', highlightAll, 'prismjs')
|
||||
})()
|
||||
|
||||
script(src=url_for(prismjs_js))
|
||||
script(src=url_for(prismjs_autoloader))
|
||||
script(src=url_for(prismjs_js) defer)
|
||||
script(src=url_for(prismjs_autoloader) defer)
|
||||
if (line_number)
|
||||
script(src=url_for(prismjs_lineNumber_js))
|
||||
script(src=url_for(prismjs_lineNumber_js) defer)
|
||||
22
layout/includes/third-party/search/algolia.pug
vendored
22
layout/includes/third-party/search/algolia.pug
vendored
@@ -2,21 +2,33 @@
|
||||
.search-dialog
|
||||
nav.search-nav
|
||||
span.search-dialog-title= _p('search.title')
|
||||
i.fas.fa-spinner.fa-pulse#loading-status(hidden)
|
||||
button.search-close-button
|
||||
i.fas.fa-times
|
||||
|
||||
.search-wrap
|
||||
#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
|
||||
#algolia-search-results
|
||||
#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-stats
|
||||
.algolia-poweredBy
|
||||
span.ais-Stats-text
|
||||
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
|
||||
|
||||
script(src=url_for(theme.asset.algolia_search))
|
||||
script(src=url_for(theme.asset.instantsearch))
|
||||
script(src=url_for(theme.asset.algolia_js))
|
||||
@@ -2,21 +2,23 @@
|
||||
.search-dialog
|
||||
nav.search-nav
|
||||
span.search-dialog-title= _p('search.title')
|
||||
span#loading-status
|
||||
i.fas.fa-spinner.fa-pulse#loading-status(hidden)
|
||||
button.search-close-button
|
||||
i.fas.fa-times
|
||||
|
||||
#loading-database.is-center
|
||||
#loading-database.text-center
|
||||
i.fas.fa-spinner.fa-pulse
|
||||
span= ' ' + _p("search.load_data")
|
||||
|
||||
.search-wrap
|
||||
#local-search-input
|
||||
.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
|
||||
.local-search-input
|
||||
input(placeholder=theme.search.placeholder || _p("search.input_placeholder") type="text")
|
||||
|
||||
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))
|
||||
@@ -1,6 +1,6 @@
|
||||
.addtoany
|
||||
.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
|
||||
a(class="a2a_button_" + name)
|
||||
|
||||
|
||||
82
layout/includes/third-party/subtitle.pug
vendored
82
layout/includes/third-party/subtitle.pug
vendored
@@ -1,5 +1,5 @@
|
||||
- const { effect,source,sub,typed_option } = theme.subtitle
|
||||
- let subContent = sub || new Array()
|
||||
- const { effect, source, sub, typed_option } = theme.subtitle
|
||||
- let subContent = typeof sub === 'string' ? [sub] : (sub || new Array())
|
||||
|
||||
script.
|
||||
window.typedJSFn = {
|
||||
@@ -22,6 +22,26 @@ script.
|
||||
} else {
|
||||
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')
|
||||
@@ -33,14 +53,12 @@ case source
|
||||
fetch('https://v1.hitokoto.cn')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (!{effect}) {
|
||||
const from = '出自 ' + data.from
|
||||
const sub = !{JSON.stringify(subContent)}
|
||||
sub.unshift(data.hitokoto, from)
|
||||
typedJSFn.init(sub)
|
||||
} else {
|
||||
document.getElementById('subtitle').textContent = data.hitokoto
|
||||
}
|
||||
typedJSFn.processSubtitle(data.hitokoto, [from])
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Failed to get the Hitokoto API:', err)
|
||||
typedJSFn.processSubtitle(!{JSON.stringify(subContent)})
|
||||
})
|
||||
}
|
||||
typedJSFn.run(subtitleType)
|
||||
@@ -48,46 +66,48 @@ case source
|
||||
when 2
|
||||
script.
|
||||
function subtitleType () {
|
||||
btf.getScript('https://yijuzhan.com/api/word.php?m=js').then(() => {
|
||||
const con = str[0]
|
||||
if (!{effect}) {
|
||||
const from = '出自 ' + str[1]
|
||||
const sub = !{JSON.stringify(subContent)}
|
||||
sub.unshift(con, from)
|
||||
typedJSFn.init(sub)
|
||||
fetch('https://v.api.aa1.cn/api/yiyan/index.php')
|
||||
.then(response => response.text())
|
||||
.then(data => {
|
||||
const reg = /<p>(.*?)<\/p>/g
|
||||
const result = reg.exec(data)
|
||||
if (result && result[1]) {
|
||||
typedJSFn.processSubtitle(result[1])
|
||||
} 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)
|
||||
|
||||
when 3
|
||||
script.
|
||||
function subtitleType () {
|
||||
btf.getScript('https://sdk.jinrishici.com/v2/browser/jinrishici.js').then(() => {
|
||||
jinrishici.load(result =>{
|
||||
if (!{effect}) {
|
||||
const sub = !{JSON.stringify(subContent)}
|
||||
const content = result.data.content
|
||||
sub.unshift(content)
|
||||
typedJSFn.init(sub)
|
||||
btf.getScript('https://sdk.jinrishici.com/v2/browser/jinrishici.js')
|
||||
.then(() => {
|
||||
jinrishici.load(result => {
|
||||
if (result && result.data && result.data.content) {
|
||||
typedJSFn.processSubtitle(result.data.content)
|
||||
} 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)
|
||||
|
||||
default
|
||||
- subContent = subContent.length ? subContent : new Array(config.subtitle)
|
||||
if subContent.length > 0
|
||||
script.
|
||||
function subtitleType () {
|
||||
if (!{effect}) {
|
||||
typedJSFn.init(!{JSON.stringify(subContent)})
|
||||
} else {
|
||||
document.getElementById("subtitle").textContent = !{JSON.stringify(subContent[0])}
|
||||
}
|
||||
typedJSFn.processSubtitle(!{JSON.stringify(subContent)})
|
||||
}
|
||||
typedJSFn.run(subtitleType)
|
||||
75
layout/includes/third-party/umami_analytics.pug
vendored
75
layout/includes/third-party/umami_analytics.pug
vendored
@@ -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 baseURL = serverURL ? serverURL.replace(/\/$/, '') : 'https://cloud.umami.is'
|
||||
- const apiUrl = serverURL ? serverURL.replace(/\/$/, '') + '/api' : 'https://api.umami.is/v1'
|
||||
@@ -9,57 +9,102 @@ script.
|
||||
const config = !{JSON.stringify(UV_PV)}
|
||||
|
||||
const runTrack = () => {
|
||||
if (typeof umami !== 'undefined' && typeof umami.track === 'function') {
|
||||
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 = () => {
|
||||
btf.getScript('!{baseURL}/script.js', {
|
||||
btf.getScript('!{baseURL}/!{script_name}', {
|
||||
'data-website-id': '!{website_id}',
|
||||
'data-auto-track': 'false',
|
||||
...option
|
||||
}).then(runTrack)
|
||||
}).then(() => {
|
||||
runTrack()
|
||||
}).catch(error => {
|
||||
console.error('Umami Analytics: Error loading script', error)
|
||||
})
|
||||
}
|
||||
|
||||
const getData = async (isPost) => {
|
||||
try {
|
||||
const now = Date.now()
|
||||
const keyUrl = isPost ? `&url=${window.location.pathname}` : ''
|
||||
const keyUrl = isPost ? `&url=${window.location.pathname}&path=${window.location.pathname}` : ''
|
||||
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}`, {
|
||||
method: "GET",
|
||||
headers: headerList
|
||||
})
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`HTTP error! status: ${res.status}`)
|
||||
}
|
||||
|
||||
return await res.json()
|
||||
} catch (error) {
|
||||
console.error('Umami Analytics: Failed to fetch data', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
const insertData = async () => {
|
||||
try {
|
||||
if (GLOBAL_CONFIG_SITE.isPost && config.page_pv) {
|
||||
if (GLOBAL_CONFIG_SITE.pageType === 'post' && config.page_pv) {
|
||||
const pagePV = document.getElementById('umamiPV')
|
||||
if (pagePV) {
|
||||
const data = await getData(true)
|
||||
pagePV.textContent = data.pageviews.value
|
||||
}
|
||||
if (data && data.pageviews) {
|
||||
pagePV.textContent = typeof data.pageviews.value !== 'undefined' ? data.pageviews.value : data.pageviews
|
||||
} 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) {
|
||||
const siteUV = document.getElementById('umami-site-uv')
|
||||
if (siteUV) siteUV.textContent = data.visitors.value
|
||||
if (siteUV && data && data.visitors) {
|
||||
siteUV.textContent = typeof data.visitors.value !== 'undefined' ? data.visitors.value : data.visitors
|
||||
} else if (siteUV) {
|
||||
console.warn('Umami Analytics: Invalid site UV data received')
|
||||
}
|
||||
}
|
||||
|
||||
if (config.site_pv) {
|
||||
const sitePV = document.getElementById('umami-site-pv')
|
||||
if (sitePV) sitePV.textContent = data.pageviews.value
|
||||
if (sitePV && data && data.pageviews) {
|
||||
sitePV.textContent = typeof data.pageviews.value !== 'undefined' ? data.pageviews.value : data.pageviews
|
||||
} 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', insertData, 'umami_analytics_insert')
|
||||
|
||||
|
||||
loadUmamiJS()
|
||||
insertData()
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', insertData)
|
||||
} else {
|
||||
setTimeout(insertData, 100)
|
||||
}
|
||||
})()
|
||||
@@ -1,5 +1,5 @@
|
||||
if theme.aside.card_author.enable
|
||||
.card-widget.card-info.is-center
|
||||
.card-widget.card-info.text-center
|
||||
.avatar-img
|
||||
img(src=url_for(theme.avatar.img) onerror=`this.onerror=null;this.src='` + url_for(theme.error_img.flink) + `'` alt="avatar")
|
||||
.author-info-name= config.author
|
||||
|
||||
@@ -12,4 +12,3 @@
|
||||
.toc-content.toc-div-class(class=tocExpandClass style="display:none")!=toc(page.origin, {list_number: tocNumber})
|
||||
else
|
||||
.toc-content(class=tocExpandClass)!=toc(page.content, {list_number: tocNumber})
|
||||
|
||||
@@ -5,10 +5,10 @@ if theme.aside.card_tags.enable
|
||||
i.fas.fa-tags
|
||||
span= _p('aside.card_tags')
|
||||
|
||||
- let { limit, orderby, order } = theme.aside.card_tags
|
||||
- let { limit, orderby, order, custom_colors } = theme.aside.card_tags
|
||||
- limit = limit === 0 ? 0 : limit || 40
|
||||
|
||||
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', custom_colors: custom_colors})
|
||||
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'})
|
||||
|
||||
@@ -6,40 +6,39 @@ if theme.aside.card_webinfo.enable
|
||||
.webinfo
|
||||
if theme.aside.card_webinfo.post_count
|
||||
.webinfo-item
|
||||
.item-name= _p('aside.card_webinfo.article_name') + " :"
|
||||
.item-name= `${_p('aside.card_webinfo.article_name')} :`
|
||||
.item-count= site.posts.length
|
||||
if theme.aside.card_webinfo.runtime_date
|
||||
.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))
|
||||
i.fa-solid.fa-spinner.fa-spin
|
||||
if theme.wordcount.enable && theme.wordcount.total_wordcount
|
||||
.webinfo-item
|
||||
.item-name=_p('aside.card_webinfo.site_wordcount') + " :"
|
||||
.item-count=totalcount(site)
|
||||
.item-name= `${_p('aside.card_webinfo.site_wordcount')} :`
|
||||
.item-count= totalcount(site)
|
||||
if theme.umami_analytics.enable && theme.umami_analytics.UV_PV.site_uv
|
||||
.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
|
||||
i.fa-solid.fa-spinner.fa-spin
|
||||
else if theme.busuanzi.site_uv
|
||||
.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
|
||||
i.fa-solid.fa-spinner.fa-spin
|
||||
if theme.umami_analytics.enable && theme.umami_analytics.UV_PV.site_pv
|
||||
.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
|
||||
i.fa-solid.fa-spinner.fa-spin
|
||||
else if theme.busuanzi.site_pv
|
||||
.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
|
||||
i.fa-solid.fa-spinner.fa-spin
|
||||
if theme.aside.card_webinfo.last_push_date
|
||||
.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()))
|
||||
i.fa-solid.fa-spinner.fa-spin
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#aside-content.aside-content
|
||||
//- post
|
||||
if is_post()
|
||||
if globalPageType === 'post'
|
||||
- const tocStyle = page.toc_style_simple
|
||||
- const tocStyleVal = tocStyle === true || tocStyle === false ? tocStyle : theme.toc.style_simple
|
||||
if showToc && tocStyleVal
|
||||
|
||||
@@ -5,7 +5,11 @@ block content
|
||||
if top_img === false
|
||||
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
|
||||
.tag_share
|
||||
if (page.tags.length > 0 && theme.post_meta.post.tags)
|
||||
@@ -29,4 +33,3 @@ block content
|
||||
if page.comments !== false && theme.comments.use
|
||||
- var commentsJsLoad = true
|
||||
!=partial('includes/third-party/comments/index', {}, {cache: true})
|
||||
|
||||
10
package.json
10
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "hexo-theme-butterfly",
|
||||
"version": "5.0.0",
|
||||
"version": "5.5.5-b1",
|
||||
"description": "A Simple and Card UI Design theme for Hexo",
|
||||
"main": "package.json",
|
||||
"scripts": {
|
||||
@@ -15,16 +15,18 @@
|
||||
"hexo-theme-butterfly"
|
||||
],
|
||||
"repository": {
|
||||
"type" : "git",
|
||||
"url" : "https://github.com/jerryc127/hexo-theme-butterfly.git"
|
||||
"type": "git",
|
||||
"url": "https://github.com/jerryc127/hexo-theme-butterfly.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/jerryc127/hexo-theme-butterfly/issues",
|
||||
"email": "my@crazywong.com"
|
||||
},
|
||||
"dependencies": {
|
||||
"hexo-renderer-pug": "^3.0.0",
|
||||
"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/",
|
||||
"author": "Jerry <my@crazywong.com>",
|
||||
|
||||
80
plugins.yml
80
plugins.yml
@@ -1,15 +1,15 @@
|
||||
abcjs_basic_js:
|
||||
name: abcjs
|
||||
file: dist/abcjs-basic-min.js
|
||||
version: 6.4.3
|
||||
version: 6.6.0
|
||||
activate_power_mode:
|
||||
name: butterfly-extsrc
|
||||
file: dist/activate-power-mode.min.js
|
||||
version: 1.1.4
|
||||
version: 1.1.6
|
||||
algolia_search:
|
||||
name: algoliasearch
|
||||
file: dist/algoliasearch-lite.umd.js
|
||||
version: 5.7.0
|
||||
file: dist/lite/builds/browser.umd.js
|
||||
version: 5.47.0
|
||||
aplayer_css:
|
||||
name: aplayer
|
||||
file: dist/APlayer.min.css
|
||||
@@ -33,65 +33,69 @@ blueimp_md5:
|
||||
canvas_fluttering_ribbon:
|
||||
name: butterfly-extsrc
|
||||
file: dist/canvas-fluttering-ribbon.min.js
|
||||
version: 1.1.4
|
||||
version: 1.1.6
|
||||
canvas_nest:
|
||||
name: butterfly-extsrc
|
||||
file: dist/canvas-nest.min.js
|
||||
version: 1.1.4
|
||||
version: 1.1.6
|
||||
canvas_ribbon:
|
||||
name: butterfly-extsrc
|
||||
file: dist/canvas-ribbon.min.js
|
||||
version: 1.1.4
|
||||
version: 1.1.6
|
||||
chartjs:
|
||||
name: chart.js
|
||||
file: dist/chart.umd.js
|
||||
version: 4.5.1
|
||||
clickShowText:
|
||||
name: butterfly-extsrc
|
||||
file: dist/click-show-text.min.js
|
||||
version: 1.1.4
|
||||
version: 1.1.6
|
||||
click_heart:
|
||||
name: butterfly-extsrc
|
||||
file: dist/click-heart.min.js
|
||||
version: 1.1.4
|
||||
version: 1.1.6
|
||||
disqusjs:
|
||||
name: disqusjs
|
||||
file: dist/browser/disqusjs.es2015.umd.min.js
|
||||
version: 3.0.2
|
||||
version: 3.2.1
|
||||
disqusjs_css:
|
||||
name: disqusjs
|
||||
file: dist/browser/styles/disqusjs.css
|
||||
version: 3.0.2
|
||||
version: 3.2.1
|
||||
docsearch_css:
|
||||
name: '@docsearch/css'
|
||||
other_name: docsearch-css
|
||||
file: dist/style.css
|
||||
version: 3.6.2
|
||||
version: 4.5.3
|
||||
docsearch_js:
|
||||
name: '@docsearch/js'
|
||||
other_name: docsearch-js
|
||||
file: dist/umd/index.js
|
||||
version: 3.6.2
|
||||
version: 4.5.3
|
||||
egjs_infinitegrid:
|
||||
name: '@egjs/infinitegrid'
|
||||
other_name: egjs-infinitegrid
|
||||
file: dist/infinitegrid.min.js
|
||||
version: 4.12.0
|
||||
version: 4.13.0
|
||||
fancybox:
|
||||
name: '@fancyapps/ui'
|
||||
file: dist/fancybox/fancybox.umd.js
|
||||
version: 5.0.36
|
||||
version: 6.1.9
|
||||
other_name: fancyapps-ui
|
||||
fancybox_css:
|
||||
name: '@fancyapps/ui'
|
||||
file: dist/fancybox/fancybox.css
|
||||
version: 5.0.36
|
||||
version: 6.1.9
|
||||
other_name: fancyapps-ui
|
||||
fireworks:
|
||||
name: butterfly-extsrc
|
||||
file: dist/fireworks.min.js
|
||||
version: 1.1.4
|
||||
version: 1.1.6
|
||||
fontawesome:
|
||||
name: '@fortawesome/fontawesome-free'
|
||||
file: css/all.min.css
|
||||
other_name: font-awesome
|
||||
version: 6.6.0
|
||||
version: 7.1.0
|
||||
gitalk:
|
||||
name: gitalk
|
||||
file: dist/gitalk.min.js
|
||||
@@ -104,28 +108,24 @@ instantpage:
|
||||
name: instant.page
|
||||
file: instantpage.js
|
||||
version: 5.2.0
|
||||
instantsearch:
|
||||
name: instantsearch.js
|
||||
file: dist/instantsearch.production.min.js
|
||||
version: 4.74.2
|
||||
katex:
|
||||
name: katex
|
||||
file: dist/katex.min.css
|
||||
other_name: KaTeX
|
||||
version: 0.16.11
|
||||
version: 0.16.28
|
||||
katex_copytex:
|
||||
name: katex
|
||||
file: dist/contrib/copy-tex.min.js
|
||||
other_name: KaTeX
|
||||
version: 0.16.11
|
||||
version: 0.16.28
|
||||
lazyload:
|
||||
name: vanilla-lazyload
|
||||
file: dist/lazyload.iife.min.js
|
||||
version: 19.1.3
|
||||
mathjax:
|
||||
name: mathjax
|
||||
file: es5/tex-mml-chtml.js
|
||||
version: 3.2.2
|
||||
file: tex-mml-chtml.js
|
||||
version: 4.1.0
|
||||
medium_zoom:
|
||||
name: medium-zoom
|
||||
file: dist/medium-zoom.min.js
|
||||
@@ -133,11 +133,11 @@ medium_zoom:
|
||||
mermaid:
|
||||
name: mermaid
|
||||
file: dist/mermaid.min.js
|
||||
version: 11.2.1
|
||||
version: 11.12.2
|
||||
meting_js:
|
||||
name: butterfly-extsrc
|
||||
file: metingjs/dist/Meting.min.js
|
||||
version: 1.1.4
|
||||
version: 1.1.6
|
||||
pace_default_css:
|
||||
name: pace-js
|
||||
other_name: pace
|
||||
@@ -148,10 +148,6 @@ pace_js:
|
||||
other_name: pace
|
||||
file: pace.min.js
|
||||
version: 1.2.4
|
||||
pangu:
|
||||
name: pangu
|
||||
file: dist/browser/pangu.min.js
|
||||
version: 4.0.7
|
||||
pjax:
|
||||
name: pjax
|
||||
file: pjax.min.js
|
||||
@@ -160,25 +156,25 @@ prismjs_autoloader:
|
||||
name: prismjs
|
||||
file: plugins/autoloader/prism-autoloader.min.js
|
||||
other_name: prism
|
||||
version: 1.29.0
|
||||
version: 1.30.0
|
||||
prismjs_js:
|
||||
name: prismjs
|
||||
file: prism.js
|
||||
other_name: prism
|
||||
version: 1.29.0
|
||||
version: 1.30.0
|
||||
prismjs_lineNumber_js:
|
||||
name: prismjs
|
||||
file: plugins/line-numbers/prism-line-numbers.min.js
|
||||
other_name: prism
|
||||
version: 1.29.0
|
||||
version: 1.30.0
|
||||
sharejs:
|
||||
name: butterfly-extsrc
|
||||
file: sharejs/dist/js/social-share.min.js
|
||||
version: 1.1.4
|
||||
version: 1.1.6
|
||||
sharejs_css:
|
||||
name: butterfly-extsrc
|
||||
file: sharejs/dist/css/share.min.css
|
||||
version: 1.1.4
|
||||
version: 1.1.6
|
||||
snackbar:
|
||||
name: node-snackbar
|
||||
file: dist/snackbar.min.js
|
||||
@@ -190,22 +186,22 @@ snackbar_css:
|
||||
twikoo:
|
||||
name: twikoo
|
||||
file: dist/twikoo.all.min.js
|
||||
version: 1.6.39
|
||||
version: 1.6.44
|
||||
typed:
|
||||
name: typed.js
|
||||
file: dist/typed.umd.js
|
||||
version: 2.1.0
|
||||
version: 3.0.0
|
||||
valine:
|
||||
name: valine
|
||||
file: dist/Valine.min.js
|
||||
version: 1.5.2
|
||||
version: 1.5.3
|
||||
waline_css:
|
||||
name: '@waline/client'
|
||||
file: dist/waline.css
|
||||
other_name: waline
|
||||
version: 3.3.2
|
||||
version: 3.8.0
|
||||
waline_js:
|
||||
name: '@waline/client'
|
||||
file: dist/waline.js
|
||||
other_name: waline
|
||||
version: 3.3.2
|
||||
version: 3.8.0
|
||||
|
||||
601
scripts/common/default_config.js
Normal file
601
scripts/common/default_config.js
Normal file
@@ -0,0 +1,601 @@
|
||||
// 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,
|
||||
custom_colors: null,
|
||||
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'
|
||||
},
|
||||
open_in_new_tab: true,
|
||||
zoom_pan: true
|
||||
},
|
||||
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
|
||||
}
|
||||
}
|
||||
37
scripts/common/postDesc.js
Normal file
37
scripts/common/postDesc.js
Normal file
@@ -0,0 +1,37 @@
|
||||
'use strict'
|
||||
|
||||
const { stripHTML, truncate } = require('hexo-util')
|
||||
|
||||
// Truncates the given content to a specified length, removing HTML tags and replacing newlines with spaces.
|
||||
const truncateContent = (content, length, encrypt = false) => {
|
||||
if (!content || encrypt) return ''
|
||||
return truncate(stripHTML(content).replace(/\n/g, ' '), { length })
|
||||
}
|
||||
|
||||
// Generates a post description based on the provided data and theme configuration.
|
||||
const postDesc = (data, hexo) => {
|
||||
const { description, content, postDesc, encrypt } = data
|
||||
|
||||
if (postDesc) return postDesc
|
||||
|
||||
const { length, method } = hexo.theme.config.index_post_content
|
||||
|
||||
if (method === false) return
|
||||
|
||||
let result
|
||||
switch (method) {
|
||||
case 1:
|
||||
result = description
|
||||
break
|
||||
case 2:
|
||||
result = description || truncateContent(content, length, encrypt)
|
||||
break
|
||||
default:
|
||||
result = truncateContent(content, length, encrypt)
|
||||
}
|
||||
|
||||
data.postDesc = result
|
||||
return result
|
||||
}
|
||||
|
||||
module.exports = { truncateContent, postDesc }
|
||||
@@ -46,45 +46,43 @@ hexo.extend.filter.register('before_generate', () => {
|
||||
}
|
||||
|
||||
const createCDNLink = (data, type, cond = '') => {
|
||||
Object.keys(data).forEach(key => {
|
||||
let { name, version, file, other_name } = data[key]
|
||||
const cdnjs_name = other_name || name
|
||||
const cdnjs_file = file.replace(/^[lib|dist]*\/|browser\//g, '')
|
||||
const min_cdnjs_file = minFile(cdnjs_file)
|
||||
return Object.keys(data).reduce((result, key) => {
|
||||
let { name, version, file, other_name: otherName } = data[key]
|
||||
const cdnjsName = otherName || name
|
||||
const cdnjsFile = file.replace(/^[lib|dist]*\/|browser\//g, '')
|
||||
const minCdnjsFile = minFile(cdnjsFile)
|
||||
if (cond === 'internal') file = `source/${file}`
|
||||
const min_file = minFile(file)
|
||||
const minFilePath = minFile(file)
|
||||
const verType = CDN.version ? (type === 'local' ? `?v=${version}` : `@${version}`) : ''
|
||||
|
||||
const value = {
|
||||
version,
|
||||
name,
|
||||
file,
|
||||
cdnjs_file,
|
||||
min_file,
|
||||
min_cdnjs_file,
|
||||
cdnjs_name
|
||||
cdnjs_file: cdnjsFile,
|
||||
min_file: minFilePath,
|
||||
min_cdnjs_file: minCdnjsFile,
|
||||
cdnjs_name: cdnjsName
|
||||
}
|
||||
|
||||
const cdnSource = {
|
||||
local: cond === 'internal' ? `${cdnjs_file + verType}` : `/pluginsSrc/${name}/${file + verType}`,
|
||||
jsdelivr: `https://cdn.jsdelivr.net/npm/${name}${verType}/${min_file}`,
|
||||
local: cond === 'internal' ? `${cdnjsFile + verType}` : `/pluginsSrc/${name}/${file + verType}`,
|
||||
jsdelivr: `https://cdn.jsdelivr.net/npm/${name}${verType}/${minFilePath}`,
|
||||
unpkg: `https://unpkg.com/${name}${verType}/${file}`,
|
||||
cdnjs: `https://cdnjs.cloudflare.com/ajax/libs/${cdnjs_name}/${version}/${min_cdnjs_file}`,
|
||||
cdnjs: `https://cdnjs.cloudflare.com/ajax/libs/${cdnjsName}/${version}/${minCdnjsFile}`,
|
||||
custom: (CDN.custom_format || '').replace(/\$\{(.+?)\}/g, (match, $1) => value[$1])
|
||||
}
|
||||
|
||||
data[key] = cdnSource[type]
|
||||
})
|
||||
|
||||
if (cond === 'internal') data.main_css = 'css/index.css' + (CDN.version ? `?v=${version}` : '')
|
||||
return data
|
||||
result[key] = cdnSource[type]
|
||||
return result
|
||||
}, cond === 'internal' ? { main_css: 'css/index.css' + (CDN.version ? `?v=${version}` : '') } : {})
|
||||
}
|
||||
|
||||
// delete null value
|
||||
const deleteNullValue = obj => {
|
||||
if (!obj) return
|
||||
if (!obj) return {}
|
||||
for (const i in obj) {
|
||||
obj[i] === null && delete obj[i]
|
||||
if (obj[i] === null) delete obj[i]
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
@@ -1,17 +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
|
||||
|
||||
// 確保 use 是一個陣列
|
||||
use = Array.isArray(use) ? use : use.split(',')
|
||||
|
||||
// 將每個項目轉換為小寫並將首字母大寫
|
||||
themeConfig.comments.use = use.map(item =>
|
||||
item.trim().toLowerCase().replace(/\b[a-z]/g, s => s.toUpperCase())
|
||||
)
|
||||
})
|
||||
@@ -1,20 +1,86 @@
|
||||
hexo.extend.filter.register('before_generate', () => {
|
||||
// Get first two digits of the Hexo version number
|
||||
const { version, log, locals } = hexo
|
||||
const hexoVer = version.replace(/(^.*\..*)\..*/, '$1')
|
||||
const { deepMerge } = require('hexo-util')
|
||||
const path = require('path')
|
||||
|
||||
if (hexoVer < 5.3) {
|
||||
// Cache default config to avoid repeated file reads
|
||||
let cachedDefaultConfig = null
|
||||
|
||||
/**
|
||||
* Check Hexo version and configuration
|
||||
*/
|
||||
function checkHexoEnvironment (hexo) {
|
||||
const { version, log, locals } = hexo
|
||||
|
||||
const [major, minor] = version.split('.').map(Number)
|
||||
const requiredMajor = 5
|
||||
const requiredMinor = 3
|
||||
|
||||
if (major < requiredMajor || (major === requiredMajor && minor < requiredMinor)) {
|
||||
log.error('Please update Hexo to V5.3.0 or higher!')
|
||||
log.error('請把 Hexo 升級到 V5.3.0 或更高的版本!')
|
||||
process.exit(-1)
|
||||
throw new Error('Hexo version too old')
|
||||
}
|
||||
|
||||
// Check for deprecated configuration file
|
||||
if (locals.get) {
|
||||
const data = locals.get('data')
|
||||
if (data && data.butterfly) {
|
||||
log.error("'butterfly.yml' is deprecated. Please use '_config.butterfly.yml'")
|
||||
log.error("'butterfly.yml' 已經棄用,請使用 '_config.butterfly.yml'")
|
||||
process.exit(-1)
|
||||
throw new Error('Deprecated configuration file')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
|
||||
@@ -1,573 +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: 'zh_TW'
|
||||
},
|
||||
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
|
||||
},
|
||||
daovoice: {
|
||||
app_id: 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'
|
||||
}
|
||||
},
|
||||
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)
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Butterfly
|
||||
* lazyload
|
||||
* replace src to data-lazy-src
|
||||
* Lazyload filter
|
||||
* Replace src with data-lazy-src for lazy loading
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
@@ -9,8 +9,21 @@
|
||||
const urlFor = require('hexo-util').url_for.bind(hexo)
|
||||
|
||||
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'
|
||||
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 => {
|
||||
|
||||
@@ -1,35 +1,68 @@
|
||||
/**
|
||||
* Butterfly
|
||||
* ramdom cover
|
||||
* Random cover for posts
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
hexo.extend.filter.register('before_post_render', data => {
|
||||
const imgTestReg = /\.(png|jpe?g|gif|svg|webp)(\?.*)?$/i
|
||||
let { cover: coverVal, top_img: topImg } = data
|
||||
hexo.extend.generator.register('post', locals => {
|
||||
const imgTestReg = /\.(png|jpe?g|gif|svg|webp|avif)(\?.*)?$/i
|
||||
const { post_asset_folder: postAssetFolder } = hexo.config
|
||||
const { cover: { default_cover: defaultCover } } = hexo.theme.config
|
||||
|
||||
// Add path to top_img and cover if post_asset_folder is enabled
|
||||
if (hexo.config.post_asset_folder) {
|
||||
if (topImg && topImg.indexOf('/') === -1 && imgTestReg.test(topImg)) data.top_img = `${data.path}${topImg}`
|
||||
if (coverVal && coverVal.indexOf('/') === -1 && imgTestReg.test(coverVal)) data.cover = `${data.path}${coverVal}`
|
||||
function * createCoverGenerator () {
|
||||
if (!defaultCover) {
|
||||
while (true) yield false
|
||||
}
|
||||
if (!Array.isArray(defaultCover)) {
|
||||
while (true) yield defaultCover
|
||||
}
|
||||
|
||||
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]
|
||||
const coverCount = defaultCover.length
|
||||
if (coverCount === 1) {
|
||||
while (true) yield defaultCover[0]
|
||||
}
|
||||
|
||||
const maxHistory = Math.min(3, coverCount - 1)
|
||||
const history = []
|
||||
|
||||
while (true) {
|
||||
let index
|
||||
do {
|
||||
index = Math.floor(Math.random() * coverCount)
|
||||
} while (history.includes(index))
|
||||
|
||||
history.push(index)
|
||||
if (history.length > maxHistory) history.shift()
|
||||
|
||||
yield defaultCover[index]
|
||||
}
|
||||
}
|
||||
|
||||
const coverGenerator = createCoverGenerator()
|
||||
|
||||
const handleImg = data => {
|
||||
let { cover: coverVal, top_img: topImg, pagination_cover: paginationCover } = data
|
||||
|
||||
// Add path to top_img and cover if post_asset_folder is enabled
|
||||
if (postAssetFolder) {
|
||||
if (topImg && topImg.indexOf('/') === -1 && imgTestReg.test(topImg)) {
|
||||
data.top_img = `${data.path}${topImg}`
|
||||
}
|
||||
if (coverVal && coverVal.indexOf('/') === -1 && imgTestReg.test(coverVal)) {
|
||||
data.cover = `${data.path}${coverVal}`
|
||||
}
|
||||
if (paginationCover && paginationCover.indexOf('/') === -1 && imgTestReg.test(paginationCover)) {
|
||||
data.pagination_cover = `${data.path}${paginationCover}`
|
||||
}
|
||||
}
|
||||
|
||||
if (coverVal === false) return data
|
||||
|
||||
// If cover is not set, use random cover
|
||||
if (!coverVal) {
|
||||
const randomCover = randomCoverFn()
|
||||
const randomCover = coverGenerator.next().value
|
||||
data.cover = randomCover
|
||||
coverVal = randomCover // update coverVal
|
||||
coverVal = randomCover
|
||||
}
|
||||
|
||||
if (coverVal && (coverVal.indexOf('//') !== -1 || imgTestReg.test(coverVal))) {
|
||||
@@ -37,4 +70,21 @@ hexo.extend.filter.register('before_post_render', data => {
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,77 +1,113 @@
|
||||
'use strict'
|
||||
|
||||
hexo.extend.helper.register('aside_archives', function (options = {}) {
|
||||
const { config, page, site, url_for, _p } = this
|
||||
const archiveDir = config.archive_dir
|
||||
const { timezone } = config
|
||||
const lang = toMomentLocale(page.lang || page.language || config.language)
|
||||
const type = options.type || 'monthly'
|
||||
const format = options.format || (type === 'monthly' ? 'MMMM YYYY' : 'YYYY')
|
||||
const showCount = Object.prototype.hasOwnProperty.call(options, 'show_count') ? options.show_count : true
|
||||
const order = options.order || -1
|
||||
const limit = options.limit
|
||||
const compareFunc = type === 'monthly'
|
||||
? (yearA, monthA, yearB, monthB) => yearA === yearB && monthA === monthB
|
||||
: (yearA, monthA, yearB, monthB) => yearA === yearB
|
||||
const { config, page, site, url_for: urlFor, _p } = this
|
||||
const { archive_dir: archiveDir, timezone, language } = config
|
||||
|
||||
const posts = site.posts.sort('date', order)
|
||||
if (!posts.length) return ''
|
||||
// Destructure and set default options with object destructuring
|
||||
const {
|
||||
type = 'monthly',
|
||||
format = type === 'monthly' ? 'MMMM YYYY' : 'YYYY',
|
||||
show_count: showCount = true,
|
||||
order = -1,
|
||||
limit,
|
||||
transform
|
||||
} = options
|
||||
|
||||
const data = []
|
||||
posts.forEach(post => {
|
||||
let date = post.date.clone()
|
||||
if (timezone) date = date.tz(timezone)
|
||||
// Optimize locale handling
|
||||
const lang = toMomentLocale(page.lang || page.language || language)
|
||||
|
||||
// Early return if no posts
|
||||
if (!site.posts.length) return ''
|
||||
|
||||
const archives = new Map()
|
||||
site.posts.forEach(post => {
|
||||
const date = post.date
|
||||
const year = date.year()
|
||||
const month = date.month() + 1
|
||||
const key = type === 'yearly' ? year : `${year}-${month}`
|
||||
|
||||
if (!data.length || !compareFunc(data[data.length - 1].year, data[data.length - 1].month, year, month)) {
|
||||
if (lang) date = date.locale(lang)
|
||||
data.push({ name: date.format(format), year, month, count: 1 })
|
||||
if (archives.has(key)) {
|
||||
archives.get(key).count++
|
||||
} else {
|
||||
data[data.length - 1].count++
|
||||
archives.set(key, {
|
||||
year,
|
||||
month,
|
||||
count: 1,
|
||||
date // Store date object for later formatting
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const link = item => {
|
||||
const data = Array.from(archives.values()).sort((a, b) => {
|
||||
if (order === -1) {
|
||||
return b.year - a.year || b.month - a.month
|
||||
}
|
||||
return a.year - b.year || a.month - b.month
|
||||
})
|
||||
|
||||
// Format names after aggregation
|
||||
data.forEach(item => {
|
||||
let date = item.date.clone()
|
||||
if (timezone) date = date.tz(timezone)
|
||||
if (lang) date = date.locale(lang)
|
||||
item.name = date.format(format)
|
||||
delete item.date // Clean up
|
||||
})
|
||||
|
||||
// Create link generator function
|
||||
const createArchiveLink = item => {
|
||||
let url = `${archiveDir}/${item.year}/`
|
||||
if (type === 'monthly') {
|
||||
url += item.month < 10 ? `0${item.month}/` : `${item.month}/`
|
||||
}
|
||||
return url_for(url)
|
||||
return urlFor(url)
|
||||
}
|
||||
|
||||
const len = data.length
|
||||
const limitLength = limit === 0 ? len : Math.min(len, limit)
|
||||
// Limit results efficiently
|
||||
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">
|
||||
<i class="fas fa-archive"></i>
|
||||
<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>
|
||||
<ul class="card-archive-list">
|
||||
`
|
||||
|
||||
for (let i = 0; i < limitLength; i++) {
|
||||
const item = data[i]
|
||||
result += `
|
||||
// Use map for generating list items, join for performance
|
||||
const archiveList = `
|
||||
<ul class="card-archive-list">
|
||||
${limitedData
|
||||
.map(
|
||||
item => `
|
||||
<li class="card-archive-list-item">
|
||||
<a class="card-archive-list-link" href="${link(item)}">
|
||||
<span class="card-archive-list-date">${options.transform ? options.transform(item.name) : item.name}</span>
|
||||
<a class="card-archive-list-link" href="${createArchiveLink(item)}">
|
||||
<span class="card-archive-list-date">
|
||||
${transform ? transform(item.name) : item.name}
|
||||
</span>
|
||||
${showCount ? `<span class="card-archive-list-count">${item.count}</span>` : ''}
|
||||
</a>
|
||||
</li>
|
||||
`
|
||||
}
|
||||
)
|
||||
.join('')}
|
||||
</ul>
|
||||
`
|
||||
|
||||
result += '</ul>'
|
||||
return result
|
||||
return archiveHeader + archiveList
|
||||
})
|
||||
|
||||
const toMomentLocale = function (lang) {
|
||||
if (!lang || lang === 'en' || lang === 'default') {
|
||||
return 'en'
|
||||
}
|
||||
// Improved locale conversion function
|
||||
const toMomentLocale = lang => {
|
||||
if (!lang || ['en', 'default'].includes(lang)) return 'en'
|
||||
return lang.toLowerCase().replace('_', '-')
|
||||
}
|
||||
|
||||
@@ -19,15 +19,33 @@ hexo.extend.helper.register('aside_categories', function (categories, options =
|
||||
const expandClass = isExpand && options.expand === true ? 'expand' : ''
|
||||
const buttonLabel = this._p('aside.more_button')
|
||||
|
||||
const prepareQuery = parent => {
|
||||
const query = parent ? { parent } : { parent: { $exists: false } }
|
||||
return categories.find(query).sort(orderby, order).filter(cat => cat.length)
|
||||
const categoryMap = new Map()
|
||||
categories.forEach(cat => {
|
||||
if (cat.length) {
|
||||
const parentId = cat.parent || 'root'
|
||||
if (!categoryMap.has(parentId)) {
|
||||
categoryMap.set(parentId, [])
|
||||
}
|
||||
categoryMap.get(parentId).push(cat)
|
||||
}
|
||||
})
|
||||
|
||||
const sortFn = (a, b) => {
|
||||
const valA = a[orderby]
|
||||
const valB = b[orderby]
|
||||
if (valA < valB) return -order
|
||||
if (valA > valB) return order
|
||||
return 0
|
||||
}
|
||||
|
||||
const hierarchicalList = (remaining, level = 0, parent) => {
|
||||
for (const list of categoryMap.values()) {
|
||||
list.sort(sortFn)
|
||||
}
|
||||
|
||||
const hierarchicalList = (remaining, level = 0, parentId = 'root') => {
|
||||
let result = ''
|
||||
if (remaining > 0) {
|
||||
prepareQuery(parent).forEach(cat => {
|
||||
if (remaining > 0 && categoryMap.has(parentId)) {
|
||||
categoryMap.get(parentId).forEach(cat => {
|
||||
if (remaining > 0) {
|
||||
remaining -= 1
|
||||
let child = ''
|
||||
@@ -37,7 +55,8 @@ hexo.extend.helper.register('aside_categories', function (categories, options =
|
||||
remaining = childList.remaining
|
||||
}
|
||||
|
||||
const parentClass = isExpand && !parent && child ? 'parent' : ''
|
||||
const isTopLevel = parentId === 'root'
|
||||
const parentClass = isExpand && isTopLevel && child ? 'parent' : ''
|
||||
result += `<li class="card-category-list-item ${parentClass}">`
|
||||
result += `<a class="card-category-list-link" href="${this.url_for(cat.path)}">`
|
||||
result += `<span class="card-category-list-name">${cat.name}</span>`
|
||||
@@ -46,7 +65,7 @@ hexo.extend.helper.register('aside_categories', function (categories, options =
|
||||
result += `<span class="card-category-list-count">${cat.length}</span>`
|
||||
}
|
||||
|
||||
if (isExpand && !parent && child) {
|
||||
if (isExpand && isTopLevel && child) {
|
||||
result += `<i class="fas fa-caret-left ${expandClass}"></i>`
|
||||
}
|
||||
|
||||
|
||||
@@ -8,28 +8,29 @@ hexo.extend.helper.register('getArchiveLength', function () {
|
||||
// Archives Page
|
||||
if (!year) return posts.length
|
||||
|
||||
// Function to generate a unique key based on the granularity
|
||||
const getKey = (post, type) => {
|
||||
const date = post.date.clone()
|
||||
const y = date.year()
|
||||
const m = date.month() + 1
|
||||
const d = date.date()
|
||||
if (type === 'year') return `${y}`
|
||||
if (type === 'month') return `${y}-${m}`
|
||||
if (type === 'day') return `${y}-${m}-${d}`
|
||||
}
|
||||
|
||||
// Create a map to count posts per period
|
||||
const mapData = this.fragment_cache('createArchiveObj', () => {
|
||||
const map = new Map()
|
||||
posts.forEach(post => {
|
||||
const keyYear = getKey(post, 'year')
|
||||
const keyMonth = getKey(post, 'month')
|
||||
const keyDay = getKey(post, 'day')
|
||||
const date = post.date
|
||||
const y = date.year()
|
||||
const m = date.month() + 1
|
||||
const d = date.date()
|
||||
|
||||
if (yearly) map.set(keyYear, (map.get(keyYear) || 0) + 1)
|
||||
if (monthly) map.set(keyMonth, (map.get(keyMonth) || 0) + 1)
|
||||
if (daily) map.set(keyDay, (map.get(keyDay) || 0) + 1)
|
||||
if (yearly) {
|
||||
const keyYear = `${y}`
|
||||
map.set(keyYear, (map.get(keyYear) || 0) + 1)
|
||||
}
|
||||
|
||||
if (monthly) {
|
||||
const keyMonth = `${y}-${m}`
|
||||
map.set(keyMonth, (map.get(keyMonth) || 0) + 1)
|
||||
}
|
||||
|
||||
if (daily) {
|
||||
const keyDay = `${y}-${m}-${d}`
|
||||
map.set(keyDay, (map.get(keyDay) || 0) + 1)
|
||||
}
|
||||
})
|
||||
return map
|
||||
})
|
||||
|
||||
@@ -4,9 +4,9 @@ hexo.extend.helper.register('inject_head_js', function () {
|
||||
const { darkmode, aside, pjax } = this.theme
|
||||
const start = darkmode.start || 6
|
||||
const end = darkmode.end || 18
|
||||
const { theme_color } = hexo.theme.config
|
||||
const themeColorLight = theme_color && theme_color.enable ? theme_color.meta_theme_color_light : '#ffffff'
|
||||
const themeColorDark = theme_color && theme_color.enable ? theme_color.meta_theme_color_dark : '#0d0d0d'
|
||||
const { theme_color: themeColor } = hexo.theme.config
|
||||
const themeColorLight = themeColor && themeColor.enable ? themeColor.meta_theme_color_light : '#ffffff'
|
||||
const themeColorDark = themeColor && themeColor.enable ? themeColor.meta_theme_color_dark : '#0d0d0d'
|
||||
|
||||
const createCustomJs = () => `
|
||||
const saveToLocal = {
|
||||
@@ -55,7 +55,6 @@ hexo.extend.helper.register('inject_head_js', function () {
|
||||
if (!${pjax.enable} && key.startsWith('pjax')) return
|
||||
const globalFn = parent.globalFn || {}
|
||||
globalFn[key] = globalFn[key] || {}
|
||||
if (name && globalFn[key][name]) return
|
||||
globalFn[key][name || Object.keys(globalFn[key]).length] = fn
|
||||
parent.globalFn = globalFn
|
||||
}
|
||||
|
||||
@@ -1,42 +1,85 @@
|
||||
'use strict'
|
||||
|
||||
const { stripHTML, prettyUrls, truncate } = require('hexo-util')
|
||||
const { truncateContent, postDesc } = require('../common/postDesc')
|
||||
const { prettyUrls } = require('hexo-util')
|
||||
const crypto = require('crypto')
|
||||
const moment = require('moment-timezone')
|
||||
|
||||
hexo.extend.helper.register('truncate', (content, length) => {
|
||||
return truncate(stripHTML(content), { length, separator: ' ' }).replace(/\n/g, ' ')
|
||||
const absoluteUrlPattern = /^(?:[a-z][a-z\d+.-]*:)?\/\//i
|
||||
const relativeUrlPattern = /^(\.\/|\.\.\/|\/|[^/]+\/).*$/
|
||||
const colorPattern = /^(#|rgb|rgba|hsl|hsla)/i
|
||||
const simpleFilePattern = /\.(png|jpg|jpeg|gif|bmp|webp|svg|tiff)$/i
|
||||
const archiveRegex = /\/archives\//
|
||||
|
||||
hexo.extend.helper.register('truncate', truncateContent)
|
||||
|
||||
hexo.extend.helper.register('postDesc', data => {
|
||||
return postDesc(data, hexo)
|
||||
})
|
||||
|
||||
hexo.extend.helper.register('cloudTags', function (options = {}) {
|
||||
const env = this
|
||||
let { source, minfontsize, maxfontsize, limit, unit = 'px', orderby, order } = options
|
||||
let { source, minfontsize, maxfontsize, limit, unit = 'px', orderby, order, page = 'tags', custom_colors } = options
|
||||
|
||||
if (limit > 0) {
|
||||
source = source.limit(limit)
|
||||
}
|
||||
|
||||
const sizes = [...new Set(source.map(tag => tag.length).sort((a, b) => a - b))]
|
||||
const sizeMap = new Map(sizes.map((size, index) => [size, index]))
|
||||
const length = sizes.length - 1
|
||||
|
||||
const getRandomColor = () => {
|
||||
const randomColor = () => Math.floor(Math.random() * 201)
|
||||
const r = randomColor()
|
||||
const g = randomColor()
|
||||
const b = randomColor()
|
||||
const r = Math.floor(Math.random() * 201)
|
||||
const g = Math.floor(Math.random() * 201)
|
||||
const b = Math.floor(Math.random() * 201)
|
||||
return `rgb(${Math.max(r, 50)}, ${Math.max(g, 50)}, ${Math.max(b, 50)})`
|
||||
}
|
||||
|
||||
const generateStyle = (size, unit) =>
|
||||
`font-size: ${parseFloat(size.toFixed(2)) + unit}; color: ${getRandomColor()};`
|
||||
const normalizeColors = input => {
|
||||
if (!input) return null
|
||||
if (typeof input === 'string') {
|
||||
const color = input.trim()
|
||||
return color ? [color] : null
|
||||
}
|
||||
if (Array.isArray(input)) {
|
||||
const result = []
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
const value = input[i]
|
||||
if (value === null || value === undefined) continue
|
||||
const color = String(value).trim()
|
||||
if (!color) continue
|
||||
result.push(color)
|
||||
}
|
||||
return result.length ? result : null
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const length = sizes.length - 1
|
||||
const result = source.sort(orderby, order).map(tag => {
|
||||
const ratio = length ? sizes.indexOf(tag.length) / length : 0
|
||||
const userColors = normalizeColors(custom_colors)
|
||||
|
||||
const resolveColorClass = (idx) => `tag-color-${idx % userColors.length}`
|
||||
|
||||
const generateStyle = (size, unit, page, color) => {
|
||||
const colorStyle = page === 'tags' ? `background-color: ${color};` : `color: ${color};`
|
||||
return `font-size: ${parseFloat(size.toFixed(2))}${unit}; ${colorStyle}`
|
||||
}
|
||||
|
||||
return source.sort(orderby, order).map((tag, idx) => {
|
||||
const ratio = length ? sizeMap.get(tag.length) / length : 0
|
||||
const size = minfontsize + ((maxfontsize - minfontsize) * ratio)
|
||||
const style = generateStyle(size, unit)
|
||||
|
||||
if (userColors && userColors.length) {
|
||||
const colorClass = resolveColorClass(idx)
|
||||
const color = userColors[idx % userColors.length]
|
||||
const style = generateStyle(size, unit, page, color)
|
||||
return `<a href="${env.url_for(tag.path)}" class="tag-cloud-item ${colorClass}" style="${style}">${tag.name}</a>`
|
||||
}
|
||||
|
||||
const color = getRandomColor()
|
||||
const style = generateStyle(size, unit, page, color)
|
||||
return `<a href="${env.url_for(tag.path)}" style="${style}">${tag.name}</a>`
|
||||
}).join('')
|
||||
|
||||
return result
|
||||
})
|
||||
|
||||
hexo.extend.helper.register('urlNoIndex', function (url = null, trailingIndex = false, trailingHtml = false) {
|
||||
@@ -44,7 +87,7 @@ hexo.extend.helper.register('urlNoIndex', function (url = null, trailingIndex =
|
||||
})
|
||||
|
||||
hexo.extend.helper.register('md5', function (path) {
|
||||
return crypto.createHash('md5').update(decodeURI(this.url_for(path))).digest('hex')
|
||||
return crypto.createHash('md5').update(decodeURI(this.url_for(path, { relative: false }))).digest('hex')
|
||||
})
|
||||
|
||||
hexo.extend.helper.register('injectHtml', data => {
|
||||
@@ -61,14 +104,14 @@ hexo.extend.helper.register('findArchivesTitle', function (page, menu, date) {
|
||||
const defaultTitle = this._p('page.archives')
|
||||
if (!menu) return defaultTitle
|
||||
|
||||
const loop = (m) => {
|
||||
const loop = m => {
|
||||
for (const key in m) {
|
||||
if (typeof m[key] === 'object') {
|
||||
const result = loop(m[key])
|
||||
if (result) return result
|
||||
}
|
||||
|
||||
if (/\/archives\//.test(m[key])) {
|
||||
if (archiveRegex.test(m[key])) {
|
||||
return key
|
||||
}
|
||||
}
|
||||
@@ -77,18 +120,74 @@ hexo.extend.helper.register('findArchivesTitle', function (page, menu, date) {
|
||||
return loop(menu) || defaultTitle
|
||||
})
|
||||
|
||||
hexo.extend.helper.register('getBgPath', path => {
|
||||
hexo.extend.helper.register('getBgPath', function (path) {
|
||||
if (!path) return ''
|
||||
|
||||
const absoluteUrlPattern = /^(?:[a-z][a-z\d+.-]*:)?\/\//i
|
||||
const relativeUrlPattern = /^(\.\/|\.\.\/|\/|[^/]+\/).*$/
|
||||
const colorPattern = /^(#|rgb|rgba|hsl|hsla|linear-gradient|radial-gradient)/i
|
||||
|
||||
if (colorPattern.test(path)) {
|
||||
return `background-color: ${path};`
|
||||
} else if (absoluteUrlPattern.test(path) || relativeUrlPattern.test(path)) {
|
||||
return `background-image: url(${path});`
|
||||
} else if (absoluteUrlPattern.test(path) || relativeUrlPattern.test(path) || simpleFilePattern.test(path)) {
|
||||
return `background-image: url(${this.url_for(path)});`
|
||||
} else {
|
||||
return `background: ${path};`
|
||||
}
|
||||
})
|
||||
|
||||
hexo.extend.helper.register('shuoshuoFN', (data, page) => {
|
||||
const { limit } = page
|
||||
|
||||
// Shallow copy to avoid mutating original data
|
||||
let processedData = data.map(item => ({ ...item }))
|
||||
|
||||
// Check if limit.value is a valid date
|
||||
const isValidDate = date => !isNaN(Date.parse(date))
|
||||
|
||||
// order by date
|
||||
processedData.sort((a, b) => Date.parse(b.date) - Date.parse(a.date))
|
||||
|
||||
// Apply number limit or time limit conditionally
|
||||
if (limit && limit.type === 'num' && limit.value > 0) {
|
||||
processedData = processedData.slice(0, limit.value)
|
||||
} else if (limit && limit.type === 'date' && isValidDate(limit.value)) {
|
||||
const limitDate = Date.parse(limit.value)
|
||||
processedData = processedData.filter(item => Date.parse(item.date) >= limitDate)
|
||||
}
|
||||
|
||||
// This is a hack method, because hexo treats time as UTC time
|
||||
// so you need to manually convert the time zone
|
||||
processedData.forEach(item => {
|
||||
const utcDate = moment.utc(item.date).format('YYYY-MM-DD HH:mm:ss')
|
||||
item.date = moment.tz(utcDate, hexo.config.timezone).format('YYYY-MM-DD HH:mm:ss')
|
||||
// markdown
|
||||
item.content = hexo.render.renderSync({ text: item.content, engine: 'markdown' })
|
||||
})
|
||||
|
||||
return processedData
|
||||
})
|
||||
|
||||
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')
|
||||
})
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable camelcase */
|
||||
/**
|
||||
* Butterfly
|
||||
* Related Posts
|
||||
@@ -6,35 +7,42 @@
|
||||
|
||||
'use strict'
|
||||
|
||||
hexo.extend.helper.register('related_posts', function (currentPost, allPosts) {
|
||||
let relatedPosts = []
|
||||
const { postDesc } = require('../common/postDesc')
|
||||
|
||||
hexo.extend.helper.register('related_posts', function (currentPost) {
|
||||
const relatedPosts = new Map()
|
||||
const tagsData = currentPost.tags
|
||||
tagsData.length && tagsData.forEach(function (tag) {
|
||||
allPosts.forEach(function (post) {
|
||||
if (currentPost.path !== post.path && isTagRelated(tag.name, post.tags)) {
|
||||
const relatedPost = {
|
||||
|
||||
if (!tagsData || !tagsData.length) return ''
|
||||
|
||||
tagsData.forEach(tag => {
|
||||
const posts = tag.posts
|
||||
posts.forEach(post => {
|
||||
if (currentPost.path === post.path) return
|
||||
|
||||
if (relatedPosts.has(post.path)) {
|
||||
relatedPosts.get(post.path).weight += 1
|
||||
} else {
|
||||
const getPostDesc = post.postDesc || postDesc(post, hexo)
|
||||
relatedPosts.set(post.path, {
|
||||
title: post.title,
|
||||
path: post.path,
|
||||
cover: post.cover,
|
||||
cover_type: post.cover_type,
|
||||
weight: 1,
|
||||
updated: post.updated,
|
||||
created: post.date
|
||||
}
|
||||
const index = findItem(relatedPosts, 'path', post.path)
|
||||
if (index !== -1) {
|
||||
relatedPosts[index].weight += 1
|
||||
} else {
|
||||
relatedPosts.push(relatedPost)
|
||||
}
|
||||
created: post.date,
|
||||
postDesc: getPostDesc,
|
||||
random: Math.random()
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
if (relatedPosts.length === 0) {
|
||||
if (relatedPosts.size === 0) {
|
||||
return ''
|
||||
}
|
||||
let result = ''
|
||||
|
||||
const hexoConfig = hexo.config
|
||||
const config = hexo.theme.config
|
||||
|
||||
@@ -42,44 +50,42 @@ hexo.extend.helper.register('related_posts', function (currentPost, allPosts) {
|
||||
const dateType = config.related_post.date_type || 'created'
|
||||
const headlineLang = this._p('post.recommend')
|
||||
|
||||
relatedPosts = relatedPosts.sort(compare('weight'))
|
||||
const relatedPostsList = Array.from(relatedPosts.values()).sort((a, b) => {
|
||||
if (b.weight !== a.weight) {
|
||||
return b.weight - a.weight
|
||||
}
|
||||
return b.random - a.random
|
||||
})
|
||||
|
||||
if (relatedPosts.length > 0) {
|
||||
result += '<div class="relatedPosts">'
|
||||
let result = '<div class="relatedPosts">'
|
||||
result += `<div class="headline"><i class="fas fa-thumbs-up fa-fw"></i><span>${headlineLang}</span></div>`
|
||||
result += '<div class="relatedPosts-list">'
|
||||
|
||||
for (let i = 0; i < Math.min(relatedPosts.length, limitNum); i++) {
|
||||
const cover = relatedPosts[i].cover || 'var(--default-bg-color)'
|
||||
const title = this.escape_html(relatedPosts[i].title)
|
||||
result += `<a href="${this.url_for(relatedPosts[i].path)}" title="${title}">`
|
||||
if (relatedPosts[i].cover_type === 'img') {
|
||||
result += `<img class="cover" src="${this.url_for(cover)}" alt="cover">`
|
||||
for (let i = 0; i < Math.min(relatedPostsList.length, limitNum); i++) {
|
||||
let { cover, title, path, cover_type, created, updated, postDesc } = relatedPostsList[i]
|
||||
const { escape_html, url_for, date } = this
|
||||
cover = cover || 'var(--default-bg-color)'
|
||||
title = escape_html(title)
|
||||
const className = postDesc ? 'pagination-related' : 'pagination-related no-desc'
|
||||
result += `<a class="${className}" href="${url_for(path)}" title="${title}">`
|
||||
if (cover_type === 'img') {
|
||||
result += `<img class="cover" src="${url_for(cover)}" alt="cover">`
|
||||
} else {
|
||||
result += `<div class="cover" style="background: ${cover}"></div>`
|
||||
}
|
||||
if (dateType === 'created') {
|
||||
result += `<div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> ${this.date(relatedPosts[i].created, hexoConfig.date_format)}</div>`
|
||||
result += `<div class="info text-center"><div class="info-1"><div class="info-item-1"><i class="far fa-calendar-alt fa-fw"></i> ${date(created, hexoConfig.date_format)}</div>`
|
||||
} else {
|
||||
result += `<div class="content is-center"><div class="date"><i class="fas fa-history fa-fw"></i> ${this.date(relatedPosts[i].updated, hexoConfig.date_format)}</div>`
|
||||
result += `<div class="info text-center"><div class="info-1"><div class="info-item-1"><i class="fas fa-history fa-fw"></i> ${date(updated, hexoConfig.date_format)}</div>`
|
||||
}
|
||||
result += `<div class="info-item-2">${title}</div></div>`
|
||||
|
||||
if (postDesc) {
|
||||
result += `<div class="info-2"><div class="info-item-1">${postDesc}</div></div>`
|
||||
}
|
||||
result += `<div class="title">${title}</div>`
|
||||
result += '</div></a>'
|
||||
}
|
||||
|
||||
result += '</div></div>'
|
||||
return result
|
||||
}
|
||||
})
|
||||
|
||||
function isTagRelated (tagName, tags) {
|
||||
return tags.some(tag => tag.name === tagName)
|
||||
}
|
||||
|
||||
function findItem (arrayToSearch, attr, val) {
|
||||
return arrayToSearch.findIndex(item => item[attr] === val)
|
||||
}
|
||||
|
||||
function compare (attr) {
|
||||
return (a, b) => b[attr] - a[attr]
|
||||
}
|
||||
|
||||
49
scripts/tag/chartjs.js
Normal file
49
scripts/tag/chartjs.js
Normal file
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Butterfly
|
||||
* chartjs
|
||||
* https://www.chartjs.org/
|
||||
* {% chartjs [width, abreast, chartId] %}
|
||||
* <!-- chart -->
|
||||
* <!-- endchart -->
|
||||
* <!-- desc -->
|
||||
* <!-- enddesc -->
|
||||
* {% endchartjs %}
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
const { escapeHTML } = require('hexo-util')
|
||||
|
||||
const chartjs = (args, content) => {
|
||||
if (!content) return
|
||||
|
||||
const chartRegex = /<!--\s*chart\s*-->\n([\w\W\s\S]*?)<!--\s*endchart\s*-->/
|
||||
const descRegex = /<!--\s*desc\s*-->\n([\w\W\s\S]*?)<!--\s*enddesc\s*-->/
|
||||
const selfConfig = args.join(' ').trim()
|
||||
|
||||
const [width = '', layout = false, chartId = ''] = selfConfig.split(',').map(s => s.trim())
|
||||
|
||||
const chartMatch = content.match(chartRegex)
|
||||
const descMatch = content.match(descRegex)
|
||||
|
||||
if (!chartMatch) {
|
||||
hexo.log.warn('chartjs tag: chart content is required!')
|
||||
return
|
||||
}
|
||||
|
||||
const chartConfig = chartMatch && chartMatch[1] ? chartMatch[1] : ''
|
||||
const descContent = descMatch && descMatch[1] ? descMatch[1] : ''
|
||||
|
||||
const renderedDesc = descContent ? hexo.render.renderSync({ text: descContent, engine: 'markdown' }).trim() : ''
|
||||
|
||||
const descDOM = renderedDesc ? `<div class="chartjs-desc">${renderedDesc}</div>` : ''
|
||||
const abreastClass = layout ? ' chartjs-abreast' : ''
|
||||
const widthStyle = width ? `data-width="${width}%"` : ''
|
||||
|
||||
return `<div class="chartjs-container${abreastClass}" data-chartjs-id="${chartId}" ${widthStyle}>
|
||||
<pre class="chartjs-src" hidden>${escapeHTML(chartConfig)}</pre>
|
||||
${descDOM}
|
||||
</div>`
|
||||
}
|
||||
|
||||
hexo.extend.tag.register('chartjs', chartjs, { ends: true })
|
||||
@@ -3,62 +3,74 @@
|
||||
* galleryGroup and gallery
|
||||
* {% galleryGroup [name] [descr] [url] [img] %}
|
||||
*
|
||||
* {% gallery [button],%}
|
||||
* {% gallery url,[url],[button]%}
|
||||
* {% gallery [button],[limit],[firstLimit] %}
|
||||
* {% gallery url,[url],[button] %}
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
const urlFor = require('hexo-util').url_for.bind(hexo)
|
||||
|
||||
const gallery = (args, content) => {
|
||||
args = args.join(' ').split(',')
|
||||
let button = false
|
||||
let type = 'data'
|
||||
let dataStr = ''
|
||||
const DEFAULT_LIMIT = 10
|
||||
const DEFAULT_FIRST_LIMIT = 10
|
||||
const IMAGE_REGEX = /!\[(.*?)\]\(([^\s]*)\s*(?:["'](.*?)["']?)?\s*\)/g
|
||||
|
||||
if (args[0] === 'url') {
|
||||
[type, dataStr, button] = args // url,[link],[lazyload]
|
||||
dataStr = urlFor(dataStr)
|
||||
} else {
|
||||
[button] = args // [lazyload]
|
||||
const regex = /!\[(.*?)\]\(([^\s]*)\s*(?:["'](.*?)["']?)?\s*\)/g
|
||||
let m
|
||||
const arr = []
|
||||
while ((m = regex.exec(content)) !== null) {
|
||||
if (m.index === regex.lastIndex) {
|
||||
regex.lastIndex++
|
||||
// Helper functions
|
||||
const parseGalleryArgs = args => {
|
||||
const [type, ...rest] = args.join(' ').split(',').map(arg => arg.trim())
|
||||
return {
|
||||
isUrl: type === 'url',
|
||||
params: type === 'url' ? rest : [type, ...rest]
|
||||
}
|
||||
arr.push({
|
||||
url: m[2],
|
||||
alt: m[1],
|
||||
title: m[3]
|
||||
}
|
||||
|
||||
const parseImageContent = content => {
|
||||
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>`
|
||||
}
|
||||
|
||||
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 [name, descr, url, img] = args
|
||||
const imgUrl = urlFor(img)
|
||||
const urlLink = urlFor(url)
|
||||
const [name = '', descr = '', url = '', img = ''] = args.map(arg => arg.trim())
|
||||
|
||||
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>
|
||||
<div class="gallery-group-name">${name}</div>
|
||||
<p>${descr}</p>
|
||||
<a href='${urlLink}'></a>
|
||||
<a href='${urlFor(url)}'></a>
|
||||
</figcaption>
|
||||
</figure>
|
||||
`
|
||||
</figure>`
|
||||
}
|
||||
|
||||
// Register tags
|
||||
hexo.extend.tag.register('gallery', gallery, { ends: true })
|
||||
hexo.extend.tag.register('galleryGroup', galleryGroup)
|
||||
|
||||
@@ -16,48 +16,35 @@
|
||||
|
||||
'use strict'
|
||||
|
||||
const parseArgs = args => {
|
||||
return args.join(' ').split(',')
|
||||
}
|
||||
const parseArgs = args => args.join(' ').split(',')
|
||||
|
||||
const generateStyle = (bg, color) => {
|
||||
let style = 'style="'
|
||||
if (bg) {
|
||||
style += `background-color: ${bg};`
|
||||
}
|
||||
if (color) {
|
||||
style += `color: ${color}`
|
||||
}
|
||||
if (bg) style += `background-color: ${bg};`
|
||||
if (color) style += `color: ${color}`
|
||||
style += '"'
|
||||
return style
|
||||
}
|
||||
|
||||
const hideInline = args => {
|
||||
const [content, display = 'Click', bg = false, color = false] = parseArgs(args)
|
||||
const group = generateStyle(bg, color)
|
||||
|
||||
return `<span class="hide-inline"><button type="button" class="hide-button" ${group}>${display}
|
||||
</button><span class="hide-content">${content}</span></span>`
|
||||
const 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>`
|
||||
}
|
||||
|
||||
const hideBlock = (args, content) => {
|
||||
const [display = 'Click', bg = false, color = false] = parseArgs(args)
|
||||
const group = generateStyle(bg, color)
|
||||
|
||||
return `<div class="hide-block"><button type="button" class="hide-button" ${group}>${display}
|
||||
</button><div class="hide-content">${hexo.render.renderSync({ text: content, engine: 'markdown' })}</div></div>`
|
||||
const style = generateStyle(bg, color)
|
||||
const renderedContent = hexo.render.renderSync({ text: content, engine: 'markdown' })
|
||||
return `<div class="hide-block"><button type="button" class="hide-button" ${style}>${display}</button><div class="hide-content">${renderedContent}</div></div>`
|
||||
}
|
||||
|
||||
const hideToggle = (args, content) => {
|
||||
const [display, bg = false, color = false] = parseArgs(args)
|
||||
const group = generateStyle(bg, color)
|
||||
let border = ''
|
||||
|
||||
if (bg) {
|
||||
border = `style="border: 1px solid ${bg}"`
|
||||
}
|
||||
|
||||
return `<details class="toggle" ${border}><summary class="toggle-button" ${group}>${display}</summary><div class="toggle-content">${hexo.render.renderSync({ text: content, engine: 'markdown' })}</div></details>`
|
||||
const style = generateStyle(bg, color)
|
||||
const border = bg ? `style="border: 1px solid ${bg}"` : ''
|
||||
const renderedContent = hexo.render.renderSync({ text: content, engine: 'markdown' })
|
||||
return `<details class="toggle" ${border}><summary class="toggle-button" ${style}>${display}</summary><div class="toggle-content">${renderedContent}</div></details>`
|
||||
}
|
||||
|
||||
hexo.extend.tag.register('hideInline', hideInline)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user