首次提交

master
李亚楠 7 months ago
commit a61d2e1a21
  1. 7
      .github/dependabot.yml
  2. 8
      .gitignore
  3. 5
      .idea/.gitignore
  4. 8
      .idea/modules.xml
  5. 12
      .idea/个人博客.iml
  6. 1001
      _config.butterfly.yml
  7. 0
      _config.landscape.yml
  8. 136
      _config.yml
  9. 6242
      package-lock.json
  10. 36
      package.json
  11. 5
      readme.md
  12. 4
      scaffolds/draft.md
  13. 4
      scaffolds/page.md
  14. 9
      scaffolds/post.md
  15. 7
      source/_data/link.yml
  16. 7
      source/_data/widget.yml
  17. 65
      source/_posts/nasbase/NAS使用DDNS.md
  18. 67
      source/_posts/nasbase/NAS双网口改桥接模式.md
  19. 46
      source/_posts/nasbase/Traefik将acme.json 分割成证书.md
  20. BIN
      source/_posts/nasbase/img.png
  21. 11
      source/_posts/nasbase/外网访问NAS.md
  22. 26
      source/_posts/nasbase/将花生壳域名的服务商改为DNSPod.md
  23. 58
      source/_posts/nasbase/找不到群晖NAS的解决办法.md
  24. 239
      source/_posts/nasserver/NAS中安装Traefik.md
  25. 225
      source/_posts/nasserver/使用frp进行内网穿透.md
  26. 153
      source/_posts/nasserver/威联通NAS安装旁路由.md
  27. 195
      source/_posts/nasserver/群晖NAS安装旁路由.md
  28. 35
      source/_posts/network/什么是DDNS.md
  29. 27
      source/_posts/network/什么是DNS.md
  30. 4
      source/about/index.md
  31. 4
      source/contact/index.md
  32. 207
      source/html/bsrt.md
  33. 6
      source/link/index.md
  34. 6
      source/tags/index.md
  35. 0
      themes/.gitkeep
  36. 13
      themes/butterfly/.github/FUNDING.yml
  37. 82
      themes/butterfly/.github/ISSUE_TEMPLATE/bug_report.yml
  38. 18
      themes/butterfly/.github/ISSUE_TEMPLATE/config.yml
  39. 14
      themes/butterfly/.github/ISSUE_TEMPLATE/feature_request.yml
  40. 19
      themes/butterfly/.github/workflows/publish.yml
  41. 19
      themes/butterfly/.github/workflows/stale.yml
  42. 202
      themes/butterfly/LICENSE
  43. 111
      themes/butterfly/README.md
  44. 111
      themes/butterfly/README_CN.md
  45. 994
      themes/butterfly/_config.yml
  46. 123
      themes/butterfly/languages/default.yml
  47. 123
      themes/butterfly/languages/en.yml
  48. 124
      themes/butterfly/languages/zh-CN.yml
  49. 124
      themes/butterfly/languages/zh-TW.yml
  50. 8
      themes/butterfly/layout/archive.pug
  51. 14
      themes/butterfly/layout/category.pug
  52. 12
      themes/butterfly/layout/includes/404.pug
  53. 65
      themes/butterfly/layout/includes/additional-js.pug
  54. 17
      themes/butterfly/layout/includes/footer.pug
  55. 68
      themes/butterfly/layout/includes/head.pug
  56. 14
      themes/butterfly/layout/includes/head/Open_Graph.pug
  57. 28
      themes/butterfly/layout/includes/head/analytics.pug
  58. 132
      themes/butterfly/layout/includes/head/config.pug
  59. 30
      themes/butterfly/layout/includes/head/config_site.pug
  60. 9
      themes/butterfly/layout/includes/head/google_adsense.pug
  61. 35
      themes/butterfly/layout/includes/head/preconnect.pug
  62. 11
      themes/butterfly/layout/includes/head/pwa.pug
  63. 3
      themes/butterfly/layout/includes/head/site_verification.pug
  64. 52
      themes/butterfly/layout/includes/header/index.pug
  65. 27
      themes/butterfly/layout/includes/header/menu_item.pug
  66. 21
      themes/butterfly/layout/includes/header/nav.pug
  67. 144
      themes/butterfly/layout/includes/header/post-info.pug
  68. 4
      themes/butterfly/layout/includes/header/social.pug
  69. 47
      themes/butterfly/layout/includes/layout.pug
  70. 33
      themes/butterfly/layout/includes/loading/fullpage-loading.pug
  71. 4
      themes/butterfly/layout/includes/loading/index.pug
  72. 11
      themes/butterfly/layout/includes/loading/pace.pug
  73. 23
      themes/butterfly/layout/includes/mixins/article-sort.pug
  74. 129
      themes/butterfly/layout/includes/mixins/post-ui.pug
  75. 1
      themes/butterfly/layout/includes/page/categories.pug
  76. 2
      themes/butterfly/layout/includes/page/default-page.pug
  77. 82
      themes/butterfly/layout/includes/page/flink.pug
  78. 2
      themes/butterfly/layout/includes/page/tags.pug
  79. 41
      themes/butterfly/layout/includes/pagination.pug
  80. 23
      themes/butterfly/layout/includes/post/post-copyright.pug
  81. 13
      themes/butterfly/layout/includes/post/reward.pug
  82. 61
      themes/butterfly/layout/includes/rightside.pug
  83. 18
      themes/butterfly/layout/includes/sidebar.pug
  84. 15
      themes/butterfly/layout/includes/third-party/abcjs/abcjs.pug
  85. 6
      themes/butterfly/layout/includes/third-party/abcjs/index.pug
  86. 3
      themes/butterfly/layout/includes/third-party/aplayer.pug
  87. 35
      themes/butterfly/layout/includes/third-party/card-post-count/artalk.pug
  88. 25
      themes/butterfly/layout/includes/third-party/card-post-count/disqus.pug
  89. 18
      themes/butterfly/layout/includes/third-party/card-post-count/fb.pug
  90. 16
      themes/butterfly/layout/includes/third-party/card-post-count/index.pug
  91. 18
      themes/butterfly/layout/includes/third-party/card-post-count/remark42.pug
  92. 37
      themes/butterfly/layout/includes/third-party/card-post-count/twikoo.pug
  93. 20
      themes/butterfly/layout/includes/third-party/card-post-count/valine.pug
  94. 21
      themes/butterfly/layout/includes/third-party/card-post-count/waline.pug
  95. 50
      themes/butterfly/layout/includes/third-party/chat/chatra.pug
  96. 45
      themes/butterfly/layout/includes/third-party/chat/crisp.pug
  97. 40
      themes/butterfly/layout/includes/third-party/chat/daovoice.pug
  98. 10
      themes/butterfly/layout/includes/third-party/chat/index.pug
  99. 44
      themes/butterfly/layout/includes/third-party/chat/messenger.pug
  100. 45
      themes/butterfly/layout/includes/third-party/chat/tidio.pug
  101. Some files were not shown because too many files have changed in this diff Show More

@ -0,0 +1,7 @@
version: 2
updates:
- package-ecosystem: npm
directory: "/"
schedule:
interval: daily
open-pull-requests-limit: 20

8
.gitignore vendored

@ -0,0 +1,8 @@
.DS_Store
Thumbs.db
db.json
*.log
node_modules/
public/
.deploy*/
_multiconfig.yml

5
.idea/.gitignore vendored

@ -0,0 +1,5 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/个人博客.iml" filepath="$PROJECT_DIR$/.idea/个人博客.iml" />
</modules>
</component>
</project>

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/temp" />
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/tmp" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

File diff suppressed because it is too large Load Diff

@ -0,0 +1,136 @@
# Hexo Configuration
## Docs: https://hexo.io/docs/configuration.html
## Source: https://github.com/hexojs/hexo/
# Site
title: 牧尘的NAS小站
subtitle: '最好用的NAS使用技巧'
description: 'NAS折腾不完全指南,定期分享一些好玩的东西,不定期记录生活。'
keywords:
author: 牧尘
language: zh-CN
timezone: ''
# URL
## Set your site url here. For example, if you use GitHub Page, set url as 'https://username.github.io/project'
url: https://www.dreamlyn.cn
permalink: :abbrlink.html
permalink_defaults:
abbrlink:
alg: crc32 #support crc16(default) and crc32
rep: dec #support dec(default) and hex
pretty_urls:
trailing_index: true # Set to false to remove trailing 'index.html' from permalinks
trailing_html: true # Set to false to remove trailing '.html' from permalinks
# Directory
source_dir: source
public_dir: public
tag_dir: tags
archive_dir: archives
category_dir: categories
code_dir: downloads/code
i18n_dir: :lang
skip_render:
# Writing
new_post_name: :title.md # File name of new posts
default_layout: post
titlecase: false # Transform title into titlecase
external_link:
enable: true # Open external links in new tab
field: site # Apply to the whole site
exclude: ''
filename_case: 0
render_drafts: false
post_asset_folder: true
relative_link: false
future: true
syntax_highlighter: highlight.js
highlight:
line_number: true
auto_detect: true
tab_replace: ''
wrap: true
hljs: false
prismjs:
preprocess: true
line_number: true
tab_replace: ''
# Home page setting
# path: Root path for your blogs index page. (default = '')
# per_page: Posts displayed per page. (0 = disable pagination)
# order_by: Posts order. (Order by date descending by default)
index_generator:
path: ''
per_page: 10
order_by: -date
# Category & Tag
default_category: uncategorized
category_map:
网络基础: network
NAS基础: nasbase
NAS服务: nasservice
tag_map:
网络: network
NAS技术: nas
反向代理: rproxy
# Metadata elements
## https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta
meta_generator: true
# Date / Time format
## Hexo uses Moment.js to parse and display date
## You can customize the date format as defined in
## http://momentjs.com/docs/#/displaying/format/
date_format: YYYY-MM-DD
time_format: HH:mm:ss
## updated_option supports 'mtime', 'date', 'empty'
updated_option: 'mtime'
# Pagination
## Set per_page to 0 to disable pagination
per_page: 10
pagination_dir: page
# Include / Exclude file(s)
## include:/exclude: options only apply to the 'source/' folder
include:
exclude:
ignore:
# Extensions
## Plugins: https://hexo.io/plugins/
## Themes: https://hexo.io/themes/
theme: butterfly
# Deployment
## Docs: https://hexo.io/docs/one-command-deployment
deploy:
type: ali-oss
region: oss-cn-beijing
accessKeyId: LTAI4Fswd2FDvYJybC6MbpJH
accessKeySecret: XcX2iMXRhKBIgc5Ev7jMKOPWvf9VcE
bucket: template-blog
# 站点地图
sitemap:
path: sitemap.xml
baidusitemap:
path: baidusitemap.xml
# 订阅
feed:
enable: true
type: atom
path: atom.xml
limit: 20
baidu_url_submit:
count: 3 ## 比如3,代表提交最新的三个链接
host: www.dreamlyn.cn ## 在百度站长平台中注册的域名
token: 8qrbejM1ER8SkGIH ## 请注意这是您的秘钥, 请不要发布在公众仓库里!
path: baidu_urls.txt ## 文本文档的地址, 新链接会保存在此文本文档里
#重定向
alias:
bsrt/: html/bsrt.html
nass/: https://outline.dreamlyn.cn:8443/share/19b1d3c1-9926-46da-9bbe-511d5caf88f3

6242
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -0,0 +1,36 @@
{
"name": "hexo-site",
"version": "0.0.0",
"private": true,
"scripts": {
"build": "hexo generate",
"clean": "hexo clean",
"deploy": "hexo deploy",
"server": "hexo server"
},
"hexo": {
"version": "7.1.1"
},
"dependencies": {
"hexo": "^7.0.0",
"hexo-abbrlink": "^2.2.1",
"hexo-baidu-url-submit": "^0.0.6",
"hexo-deployer-ali-oss": "^1.0.0",
"hexo-generator-alias": "^1.0.0",
"hexo-generator-archive": "^2.0.0",
"hexo-generator-baidu-sitemap": "^0.1.9",
"hexo-generator-category": "^2.0.0",
"hexo-generator-feed": "^3.0.0",
"hexo-generator-index": "^3.0.0",
"hexo-generator-search": "^2.4.3",
"hexo-generator-sitemap": "^3.0.1",
"hexo-generator-tag": "^2.0.0",
"hexo-renderer-ejs": "^2.0.0",
"hexo-renderer-marked": "^6.0.0",
"hexo-renderer-pug": "^3.0.0",
"hexo-renderer-stylus": "^3.0.0",
"hexo-server": "^3.0.0",
"hexo-theme-landscape": "^1.0.0",
"hexo-wordcount": "^6.0.1"
}
}

@ -0,0 +1,5 @@
hexo new page --path about/me "About me"
hexo new --path about/me "About me"
# 部署命令
hexo g && hexo d

@ -0,0 +1,4 @@
---
title: {{ title }}
tags:
---

@ -0,0 +1,4 @@
---
title: {{ title }}
date: {{ date }}
---

@ -0,0 +1,9 @@
---
title: {{ title }}
date: {{ date }}
tags:
categories:
keywords:
description:
cover:
---

@ -0,0 +1,7 @@
- class_name: 友情鏈接
class_desc:
link_list:
- name: 我不是咕咕鸽
link: https://blog.laoda.de
avatar: https://blog.laoda.de/upload/guguge.webp
descr: 服务器折腾不完全指南,定期分享一些好玩的东西,不定期记录生活。

@ -0,0 +1,7 @@
right:
- class_name:
id_name:
name: dsdf
icon: fas fa-heartbeat
order:
html: '<script type="text/javascript" id="clstr_globe" src="//clustrmaps.com/globe.js?d=5V2tOKp8qAdRM-i8eu7ETTO9ugt5uKbbG-U7Yj8uMl8"></script>'

@ -0,0 +1,65 @@
---
title: NAS使用DDNS
tags:
- 网络
- NAS技术
categories: NAS基础
abbrlink: 28082
date: 2023-08-22 11:02:22
keywords:
description:
---
>群晖自带的DDNS无法使用泛域名,我在NAS使用过程中需要把域名的所有子域名都通过DDNS指向本机,所有在这里采用装第三方DDNS服务的方法来使用DDNS。
DDNS其实就是动态的调整DNS服务器中的A记录,实现的前提就是域名服务商提供API来修改域名的A记录,我们在需要使用DDNS的地方通过脚本来获取可能动态会变动的公网IP,然后通过API告诉域名服务商修改A记录。
<img src="https://img.dreamlyn.cn:8443/i/2024/02/23/103721.webp">
因此,实现DDNS需要做两件事:第一,获取DNSPod的API Token;第二,在本地用程序获取公网IP地址并使用DNSPod API更新A记录。
# 获取DNSPod Token
在DNSPOD的控制台中,如下图所示点击API密钥中。
<img src="https://img.dreamlyn.cn:8443/i/2024/02/23/104153.webp">
点击DNSPod Token并创建密钥,记录下ID和Token。
<img src="https://img.dreamlyn.cn:8443/i/2024/02/23/104220.webp">
# 动态更新DNS
我在威联通上通过docker-compose来实现DDNS。
在nas上合适的位置创建目录,并创建如下文件。
<img src="https://img.dreamlyn.cn:8443/i/2024/02/23/104303.webp">
其中docker-compose文件如下:
```
---
version: "3"
services:
ddns-go:
image: jeessy/ddns-go
container_name: ddns-go
environment:
- PUID=1000
- PGID=1000
- TZ=Asia/Shanghai
network_mode: "host"
restart: unless-stopped
```
在ddns-go目录下执行命令docker-compose up -d命令来启动ddns-go,使用docker-compose logs -f来查看启动日志,如果启动没有问题,那么就可以使用浏览器访问http://192.168.31.206:9876来访问。ddns-go的配置界面如下:DNS服务商选择Dnspod;ID和Token设置为上面记录的值;Domains设置为*.dreamlyn.cn。
<img src="https://img.dreamlyn.cn:8443/i/2024/02/23/104421.webp">
啰嗦一句,最开始的时候,我是将DDNS的域名设置为home.dreamlyn.cn,并将*.dreamlyn.cn的cname设置为home.dreamlyn.cn。但这样设置的话,后面在使用traefik进行DNS Challenge时会存在问题,不得已只好将DDNS直接设置为*.dreamlyn.cn。
在其他配置中,设置登录用户名、密码并保存。
<img src="https://img.dreamlyn.cn:8443/i/2024/02/23/104524.webp">
查看右侧的日志并登录DNSPod的后台看*.dreamlyn.cn的A记录是否变更,如变更,则说明DDNS安装成功。
至此,我们便打通了从 外网到NAS的线路,也就是说访问*.dreamlyn.cn:880时会映射到NAS的80端口,访问*.dreamlyn.cn:8443则会映射到NAS的443端口。
<img src="https://img.dreamlyn.cn:8443/i/2024/02/23/104611.webp">

@ -0,0 +1,67 @@
---
title: NAS双网口改桥接模式
tags:
- 网络
- NAS技术
categories: NAS基础
abbrlink: 3026110258
date: 2024-02-22 11:02:22
keywords:
description:
---
>当我们有两台设备需要上网(其中一台是群晖NAS),但是旁边只有一个网口时,可以采用将NAS双网口改成桥接模式来实现两台设备的网络连接。
我们将群晖的两个网络接口分别命名为接口1和接口2,具体的连接方式为,原有的网口接NAS的网络接口1,NAS的网络接口2连接另外一台设备。
# 打开Open vSwitch功能
进入群晖系统–>控制面板–>网络–>选中“局域网”–>点击“管理”,在下拉菜单中选择“Open vSwitch 设置”
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/205037.webp">
在弹出界面中选中“启用”
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/205222.webp">
# 删除ovs_eth绑定网口
开启群晖的SSH并登录,使用sudo -i获取root权限。通过ifconfig可以看到ovs_eth0和ovs_eth1,这是群晖两个默认的网桥,对应连接的接口是eth0和eth1,要确定一下哪个是连接路由器的,哪个是连接电脑的。 (以下以ovs_eth0连接路由器,ovs_eth1连接电脑为例)
<img src="https://img.dreamlyn.cn:8443/i/2024/02/26/091604.webp" >
随后输入命令删除ovs_eth1
```
ovs-vsctl del-br ovs_eth1
```
# 将eth1加入ovs_eth0网桥
使用下面的命令添加ETH1
```
ovs-vsctl add-port ovs_eth0 eth1
```
随后输入命令查看网络状态
```
ovs-vsctl show
```
<img src="https://img.dreamlyn.cn:8443/i/2024/02/26/092650.webp">
# 设置开机自动桥接
上面的的步骤配置完后,群晖重启之后还是会恢复默认网桥设置的,所以我们还得要加一个开机的计划任务让它可以每次开机自动绑定网桥。
按下图所示添加用户自定义计划脚本
<img src="https://img.dreamlyn.cn:8443/i/2024/02/26/093157.webp" >
账号选root,名称随意。
<img src="https://img.dreamlyn.cn:8443/i/2024/02/26/093452.webp" >
在任务设置里面输入刚才的命令并点击确定
<img src="https://img.dreamlyn.cn:8443/i/2024/02/26/093556.webp" >
命令如下
```
ovs-vsctl del-br ovs_eth1
ovs-vsctl add-port ovs_eth0 eth1
```

@ -0,0 +1,46 @@
---
title: Traefik将acme.json 分割成证书
tags:
- 网络
categories: NAS基础
abbrlink: 60950
date: 2023-06-22 11:02:22
keywords:
description:
---
我们在使用Let’s Encrypt进行自动证书获取时,是将TLS信息存放到acme.json中的。但是总有那么些原因要用到证书,比如我在同一个域名下,有另外一个服务没有使用traefik代理,这时候就需要将acme.json分割成证书。
在此,使用docker-compose安装certdump来对acme.json进行分割。
在traefik目录下,创建certdump.yml文件。
<img src="https://img.dreamlyn.cn:8443/i/2024/02/23/101307.webp">
文件内容如下:
```
---
version: "3"
services:
certdumper:
image: humenius/traefik-certs-dumper:latest
container_name: certdumper
environment:
- PUID=1000
- PGID=1000
- TZ=Asia/Shanghai
volumes:
- ./configs:/traefik:ro
- ./output:/output:rw
networks:
default:
external:
name: docker_default
```
此时在traefik目录下执行如下命令:
```
# 创建docker网络
docker network create -d bridge --attachable=true docker_default
# 启动容器,分割acme.json
docker-compose -f certdump.yml up -d
```
容器启动后,将会在output文件夹下导出证书。

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

@ -0,0 +1,11 @@
---
title: 外网访问NAS
tags:
- 网络
- NAS技术
categories: NAS基础
abbrlink: 4218199689
date: 2023-08-22 11:02:22
keywords:
description:
---

@ -0,0 +1,26 @@
---
title: 将花生壳域名的服务商改为DNSPod
tags:
- 网络
categories: NAS基础
abbrlink: 40925
date: 2022-03-21 11:02:22
keywords:
description:
---
>在服务搭建过程中,需要使用域名服务商的API,并通过API来管理域名记录,而花生壳并不提供相应的功能,因而对域名服务商进行调整,下面给出调整过程。
# 在花生壳管理端修改域名的DNS
将NS管理中的DNS修改为ninety.dnspod.net和baron.dnspod.net
注意:修改完之后,花生壳中设定的域名解析将完全失效(包括DDNS)
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/115913.webp">
# 在DNSPod中管理域名
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/115914.webp">
如图,在面板中点击添加域名,输入域名后一路下一步即可。
# 为什么要修改域名服务商
因为我在搭建服务的过程中,需要使用traefik,traefik有个很好用的东西叫letsencrypt,他可以采用tlsChallenge、httpChallenge和dnsChallenge三种方式来自动申请HTTPS证书。
但是因为我的很多服务是在家中搭建,运营商并不开放80和443端口,导致tlsChallenge和httpChallenge两种方式无法使用 ,只能使用dnsChallenge。
我们知道dnsChallenge的验证方式,需要给域名添加一条txt记录来验证域名所有权,如果想完全自动得去申请tls证书,那么一定要使用域名服务商的API,而花生壳并不提供相应的api,导致我不得不更换服务商。

@ -0,0 +1,58 @@
---
title: 找不到群晖NAS的解决办法
tags:
- 网络
- NAS技术
categories: NAS基础
abbrlink: 2241717723
date: 2023-02-22 11:02:22
keywords:
description:
---
# 一、无法通过 Web Assistant (find.synology.cn)找到NAS
<img src="https://img.dreamlyn.cn:8443/i/2024/02/23/154737.webp">
请先尝试以下每种方法,然后再通过 Web Assistant 搜索Synology NAS :
- 确保Synology NAS和计算机已正确连接到 Internet。
- 确保Synology NAS和计算机位于同一局域网和子网中。
- 使用其他浏览器(如 Chrome 或 Firefox)。
- 如果 DSM 仍可访问,请进入DSM>控制面板>信息中心>设备分析>共享网络位置,并勾选允许此 DiskStation 在 Web Assistant (find.synology.com) 中显示复选框。
- 如果您在设置Synology NAS时更改了服务器名称,则可能无法找到Synology NAS。如果 DSM 仍可访问,请进入DSM >控制面板>网络>常规以将服务器名称更改为SynologyNAS ,然后再试一次。
- 如果上述方法无效,请尝试使用 Synology Assistant 搜索Synology NAS 。
# 二、无法通过 [Synology Assistant](https://template-mine.oss-cn-beijing.aliyuncs.com/NAS%E4%B8%8B%E8%BD%BD/synology-assistant-7.0.3-50049.exe) (群晖助手)找到NAS
<img src="https://img.dreamlyn.cn:8443/i/2024/02/23/154909.webp">
- 在再次通过 Synology Assistant 搜索Synology NAS之前,请尝试以下每种方法:
- 确保Synology NAS和计算机位于同一局域网和子网中。
- 调整防火墙设置以允许 Synology Assistant 通过 DSM 和Windows防火墙。
- 暂时禁用或删除计算机上的防病毒软件。
- 使用其他以太网电缆连接Synology NAS ,以检查电缆是否有缺陷。
- 如果Synology NAS具有多个网络端口,请将以太网电缆连接到其他端口以检查端口是否有故障。
- 在另一台计算机上运行 Synology Assistant 并搜索您的Synology NAS。
- 如果您使用的是 Synology Assistant 7.0-50044 或更高版本,并且您尝试查找的Synology NAS运行的是 DSM 6.2.3 及更低版本或 DSM UC 3.0.1 及更低版本,请单击 Synology Assistant 中的齿轮图标并勾选以启用允许与不支持密码加密的设备兼容。 2
- 按住Synology NAS背面的 RESET 按钮四秒钟(您会听到哔声)以重置Synology NAS设置。
- 重置Synology NAS后,关闭计算机上的 WiFi 连接,并使用以太网电缆将Synology NAS直接连接到计算机,而无需通过网络交换机或路由器。将计算机的有线 LAN 接口设置为 DHCP,然后再次使用 Synology Assistant 搜索Synology NAS 。
# 三、如果在尝试上述方法后 Synology Assistant 仍无法找到Synology NAS
- 关闭Synology NAS,卸下硬盘,重新启动Synology NAS,然后再次尝试搜索设备。
- 即使未安装任何硬盘也无法找到Synology NAS ,这可能是由于硬件故障。

@ -0,0 +1,239 @@
---
title: NAS中安装Traefik
tags:
- 网络
- NAS技术
- 反向代理
categories: NAS服务
description: NAS中安装Traefik作反向代理
abbrlink: 2636364671
date: 2023-02-30 11:02:22
keywords:
---
>我在家里的NAS上使用docker搭建了许多服务,这些服务都使用traefik进行代理,当这些服务需要使用HTTPS时,我们可以使用Traefik的Let’s Encrypt来自动获取证书,本篇文章主要介绍Traefik的安装以及如何使用traefik来自动获取证书。
# 安装Traefik
我在NAS上的服务大部分都是采用docker-compose的方式进行安装,而traefik的反向代理对docker原生支持,所以我采用traefik作为所有服务的入口。当我们使用不同的域名,比如:gitea.dreamlyn.cn或者给blog.dreamlyn.cn来访问网站时,traefik会根据不同的域名,将访问代理到不同的docker容器。有兴趣深入了解traefik的可以参考[中文文档](https://www.traefik.tech/)或者[官方文档](https://doc.traefik.io/traefik/v2.9/)。
<img src="https://img.dreamlyn.cn:8443/i/2024/02/23/150652.webp">
traefik反向代理
对于traefik本身的安装,我也是采用了docker-compose的方式。
在nas上合适的位置创建目录,并创建如下文件。
<img src="https://img.dreamlyn.cn:8443/i/2024/02/23/150751.webp">
其中docker-compose文件如下:
```
---
version: "3"
services:
traefik:
image: traefik:latest
container_name: traefik
environment:
- PUID=1000
- PGID=1000
- TZ=Asia/Shanghai
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./configs/traefik.yml:/etc/traefik/traefik.yml
- ./configs/conf.d:/configs/conf.d:rw
ports:
- "80:80"
- "443:443"
labels:
- "traefik.enable=true"
#设置http
- "traefik.http.routers.traefik.entrypoints=web"
- "traefik.http.routers.traefik.service=traefik"
- "traefik.http.routers.traefik.rule=Host(`traefik.dreamlyn.cn`)"
#设定端口号,traefik本身有个dashboard,端口为8080
- "traefik.http.services.traefik.loadbalancer.server.port=8080"
restart: always
networks:
default:
external:
name: docker_default
```
traefik配置文件如下:
```
entryPoints:
web:
address: :80
websecure:
address: :443
# 两种Traefik的配置方式,
providers:
#docker配置方式
#可以在使用docker-compose安装wordpress的时候直接配置好wordpress的相关配置,而不需要修改traefik的任何东西。
docker:
exposedByDefault: false
defaultRule: "Host(`{{trimPrefix `/` .Name}}.dreamlyn.cn`)"
#file配置方式,反向代理非docker时使用,配置文件统一放在/configs/conf.d目录里
file:
watch: true
directory: /configs/conf.d
api:
debug: true
dashboard: true
insecure: true
#log:
# level: DEBUG
```
此时在traefik目录下执行如下命令:
```
# 创建docker网络,如果已经有docker_default网络,则不需要。
docker network create -d bridge --attachable=true docker_default
# 启动traefik
docker-compose up -d
# 查看启动日志
docker-compose logs -f
```
如果启动没有问题,那么就可以使用http://traefik.dreamlyn.cn:880来访问traefik的dashboard了。
# 使用DNS Challenge
当我们的服务需要使用HTTPS时,我们可以使用traefik的Let’s Encrypt来自动获取证书。Traefik有三种方式来使用Let’s Encrypt:TLS Challenge、HTTP Challenge和DNS Challenge。
其中TLS Challenge和HTTP Challenge两种方式较为简单,但是需要服务的访问端口为80或者443,而我们在家中的网络一般都是屏蔽了80和443端口的,所以需要使用较为复杂的DNS Challenge。
```
Let's Encrypt是一种自动获取HTTPS证书的方式,也就是我们设置好之后,不需要再去手动申请证书,然后各种配置了。
```
使用DNS Challenge有几个前置条件:
1. 确保域名的服务商在Traefik支持的域名服务商列表中(不在列表中时,请参考这篇文章对域名服务商进行修改)。
2. 确保使用DNS Challenge申请的域名使用的是A记录,并且正确解析到traefik服务上。(在NAS折腾记录:打通网络篇,我将DDNS的主机域名直接设置为了*.dreamlyn.cn就是这个原因)
<img src="https://img.dreamlyn.cn:8443/i/2024/02/23/151024.webp">
那么接下来就根据上面的原理一步步实现DNS Challenge。
## DNS设置
DNS Challenge自动获取HTTPS证书的一个过程是验证域名的归属权,这个过程是Traefik通过调用域名服务商的API自动进行的(Traefik支持的域名服务商参考[这里](https://doc.traefik.io/traefik/https/acme/#providers)),而Traefik调用API的时候,是需要SecretId和SecretKey的,接下来具体操作。
到 https://doc.traefik.io/traefik/https/acme/#providers 查看Traefik是否支持自己所购买的域名服务商,如果不支持也没关系,参考[这篇文章](https://www.dreamlyn.cn/40925.html),将域名服务商修改为DNSPOD(被腾讯收购后改为了Tencent Cloud DNS)。
在DNSPOD的控制台中,如下图所示点击API密钥中。
<img src="https://img.dreamlyn.cn:8443/i/2024/02/23/151239.webp">
新建密钥。
<img src="https://img.dreamlyn.cn:8443/i/2024/02/23/151305.webp">
## 修改docker-compose.yml
如图,在相应位置创建acme.json。需要使用命令`chmod 600 acme.json`将acme.json文件权限调整为600
<img src="https://img.dreamlyn.cn:8443/i/2024/02/23/151412.webp">
修改docker-compose.yml,确保下图红框部分正确配置。
<img src="https://img.dreamlyn.cn:8443/i/2024/02/23/151443.webp">
最终代码如下:
```
---
version: "2.1"
services:
traefik:
image: traefik
container_name: traefik
environment:
- PUID=1000
- PGID=1000
- TZ=Asia/Shanghai
# 因为家中的服务端口不是443,在使用Let’s Encrypt时只能用DNS Challenge的方式
# 前期如果不需要HTTPS,可以忽略
- "TENCENTCLOUD_SECRET_ID=XXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
- "TENCENTCLOUD_SECRET_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxx"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./configs/traefik.yml:/etc/traefik/traefik.yml
# 如果不用HTTPS,可以忽略acem.json
# 用的话,需要使用 chmod 600 acme.json 命令修改文件的权限
- ./configs/acme.json:/configs/acme.json:rw
- ./configs/conf.d:/configs/conf.d:rw
- ./configs/certs:/configs/certs
ports:
- "80:80"
- "443:443"
labels:
- "traefik.enable=true"
#设置http
- "traefik.http.routers.traefik.entrypoints=web"
- "traefik.http.routers.traefik.service=traefik"
- "traefik.http.routers.traefik.rule=Host(`traefik.dreamlyn.cn`)"
#设定端口号,traefik本身有个dashboard,端口为8080
- "traefik.http.services.traefik.loadbalancer.server.port=8080"
restart: always
networks:
default:
external:
name: docker_default
```
## 修改traefik.yml
修改traefik.yml文件,确保红框部分的正确配置。
<img src="https://img.dreamlyn.cn:8443/i/2024/02/23/151535.webp">
最终代码如下:
```
entryPoints:
web:
address: :80
websecure:
address: :443
# 不使用HTTPS的话,下面可以忽略
http:
tls:
certResolver: letsencrypt
domains:
- main: dreamlyn.cn
sans:
- "*.dreamlyn.cn"
# 两种配置方式,
providers:
#docker配置方式
#比如:可以在使用docker-compose安装wordpress的时候直接配置好wordpress的相关配置,而不需要修改traefik的任何东西。
docker:
exposedByDefault: false
defaultRule: "Host(`{{trimPrefix `/` .Name}}.dreamlyn.cn`)"
#file配置方式,反向代理非docker时使用,比如群晖上的Plex,配置文件统一放在/configs/conf.d目录里
file:
watch: true
directory: /configs/conf.d
#注意,letsencrypt在服务器无法开放80和443端口的情况下无法使用tls和http,暂时不用。
#后来发现DNS的服务商可以修改,故将花生壳的DNS服务商修改为DNSPod,可以使用DNS-01进行自动tls申请
certificatesResolvers:
letsencrypt:
acme:
email: lyn@dreamlyn.cn
storage: /configs/acme.json
dnschallenge:
provider: tencentcloud
api:
debug: true
dashboard: true
insecure: true
#log:
# level: DEBUG
```
## 运行Traefik
在traefik目录下执行如下命令:
```
# 创建docker网络,如果已经有docker_default网络,则不需要。
docker network create -d bridge --attachable=true docker_default
# 启动traefik
docker-compose up -d
# 查看启动日志
docker-compose logs -f
```
如果日志没有问题的话,我们就可以通过http://traefik.dreamlyn.cn:880来访问了。
至此,Traefik的安装已经完成了。

@ -0,0 +1,225 @@
---
title: 使用frp进行内网穿透
tags:
- 网络
- NAS技术
categories: NAS服务
abbrlink: 25460
date: 2023-02-25 11:02:22
keywords:
description:
---
>当我们所搭建的服务并不具备公网IP,无法从外放访问服务时,可以利用frp来进行内网穿透。
# 什么是frp
frp服务是内网穿透服务中的一种,可以理解为花生壳内网穿透的替代品,但是要比花生壳内网穿透快很多。它的大致原理如下,用户访问安装有frps服务的设备,frps能根据与frpc建立的联系,自动打通隧道,使用户的访问映射到内网的客户端。
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/230354.webp">
# 开始使用frp
接下来我们分两种情况介绍具体如何使用:
1. 当我们有一台暴漏公网IP的Centos服务器时。
2. 当我们没有任何可以暴漏公网IP的服务器时。
# 在Centos上搭建frp服务
当我们有一台暴漏公网IP的Centos服务器时,可以在服务器端安装frps服务。
## 下载frp并解压
到GitHub查看最新版本:https://github.com/fatedier/frp/releases
如果访问不了github,我在这儿也给出0.45.0版下载地址。
操作命令如下:
```
# 下载frp可执行包
wget https://github.com/fatedier/frp/releases/download/v0.45.0/frp_0.45.0_linux_amd64.tar.gz
# git无法访问的话,用下面的地址
wget https://minio.dreamlyn.cn:9000/blog/attachment/frp_0.45.0_linux_amd64.tar.gz
# 解压
tar zxf frp_0.45.0_linux_amd64.tar.gz
```
## 配置frps
```
# 进入解压缩后的目录
cd frp_0.45.0_linux_amd64/
# 修改配置
vim frps.ini
```
配置文件如下:
```
[common]
# fpr客户端和frp服务器通信的地址
bind_port = 7000
# 默认http和https监听端口
vhost_http_port = 80
vhost_https_port = 443
# 针对不同的服务使用[]隔开,服务名自己可以随便定,跟客户端能对应上就行。
[ssh]
# 默认使用tcp,监听服务端的2200端口
# 如果服务端接收到2200端口,则跟据[ssh]这个服务名到客户端寻找相应配置。
listen_port = 2200
auth_token = 123456
# [http]服务需要指定type
[http]
type = http
custom_domains = nas.example.com
auth_token = 123456
# remote这个服务时当时我为了远程访问windows桌面设定的,很实用。
[remote]
listen_port = 3389
auth_token = 123456
```
## 启动
```
# 启动frps
./frps -c frps.ini
```
## 设置开机自启
使用vim创建并编辑 /lib/systemd/system/frps.service,设置如下
```
[Unit]
Description=Frp Server Service
After=network.target
[Service]
Type=simple
# 假设fpr存放地址为/usr/local/
ExecStart=/usr/local/frp_0.45.0_linux_amd64/frps -c /usr/local/frp_0.45.0_linux_amd64/frps.ini
KillSignal=SIGQUIT
TimeoutStopSec=5
KillMode=process
PrivateTmp=true
StandardOutput=syslog
StandardError=inherit
[Install]
WantedBy=multi-user.target
```
之后就可以使用如下命令控制frp服务
```
启动服务 systemctl start frps
开机自启动 systemctl enable frps
重启服务 systemctl restart frps
停止服务 systemctl stop frps
查看日志与状态 systemctl status frps
```
# 使用第三方的免费frp服务
当我们没有任何可以暴漏公网IP的服务器时,就需要使用第三方的免费frp服务,如果已经使用centos搭建了frp服务,可以跳过这段直接看客户端的搭建。
先来说下什么是第三方免费frp服务吧,它的存在使我们可以在没有公网IP服务器、并且运营商早已封杀80和443端口的情况下,爆露出我们的服务,笔者这个博客的搭建也是采用的这种形式。
在此推荐使用[SAKURA FRP](https://www.natfrp.com/),隧道限速10M足够使用,每月有5G的免费流量,关键是签到还送流量。
第一步先注册,然后到管理界面点击 服务->隧道列表。
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/230859.webp">
然后点击创建隧道然后选择节点,这里的隧道可以理解为从公网IP到自己本地服务的一条线路。
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/230938.webp">
隧道类型,一般建站的话直接选HTTP/HTTPS,其他的选TCP隧道。
如果是建站使用的话,还需要设置域名cname记录,如下图所示cn-cd-dx-1.natfrp.cloud是我需要设置的cname值(cname值需要到域名管理后台设置)。
注:如果选择国内节点,建站需要先进行备案。
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/231015.webp">
记录配置文件,供后面客户端配置使用
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/231236.webp">
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/231109.webp">
配置文件有两种形式,其中第一种是采用SAKURA FRP官网提供的客户端来连接。第二种采用标准frp客户端进行访问,笔者一直采用第二种形式。
# 使用NAS创建frp客户端
终于轮到NAS登场了,我采用docker-compose的方式安装frp。
在nas上合适的位置创建目录,并创建如下文件。
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/231331.webp">
docker-compose.yml文件如下
```
---
version: "3"
services:
natfrp:
image: oldiy/frpc
container_name: natfrp
environment:
- PUID=1000
- PGID=1000
- TZ=Asia/Shanghai
volumes:
- ./configs:/frp
restart: unless-stopped
networks:
default:
external:
name: docker_default
```
frpc.ini文件如下
```
# 对应Centos搭建的frp服务
[common]
server_addr = 服务器IP地址
server_port = 7000
auth_token = 123456
[ssh]
local_ip = 192.168.31.206
local_port = 22
remote_port = 2200
[http]
type = http
local_ip = 192.168.31.206
local_port = 5000
custom_domains = nas.example.com
[remote]
local_ip = 192.168.31.10
local_port = 3389
remote_port = 3389
```
```
# 对应使用SAKURA FRP搭建的frp服务,也就是上文提示的记录配置文件
[common]
user = xxxxxxxxxxxx
sakura_mode = true
use_recover = true
login_fail_exit = false
protocol = tcp
tcp_mux = true
pool_count = 1
token = xxxxxxxxxxxx
server_addr = cn-cd-dx-1.natfrp.cloud
server_port = 7000
[blog_http]
# id =
type = http
local_ip = 192.168.31.206
local_port = 80
custom_domains = www.example.com
[blog_https]
# id =
type = https
local_ip = 192.168.31.206
local_port = 443
custom_domains = www.example.com
```
启动docker
```
cd natfrp
docker-compose up -d
docker-compose logs -f
```
OK,可以了,使用www.example.com访问你的网站吧!!!
文章有不清楚的可以在评论区留言,我会尽快回复.

@ -0,0 +1,153 @@
---
title: 威联通NAS安装旁路由
tags:
- 网络
- NAS技术
categories: NAS服务
abbrlink: 34692
date: 2022-02-26 11:02:22
keywords:
description:
---
>软路由的安装最好使用虚拟机,使用Docker安装的一个弊端就是无法使用旁路由来做代理。 在此也介绍虚拟机安装的方式
# 下载套件
首先要下载Virtualization Station虚拟化工作站
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/215518.webp">
# 下载OpenWRT
2023-01-06精简版[下载](https://template-mine.oss-cn-beijing.aliyuncs.com/NAS%E4%B8%8B%E8%BD%BD/%E6%97%81%E8%B7%AF%E7%94%B1/bleach-mini-20230106-openwrt-x86-64-generic-squashfs-combined-efi.vmdk)
2024-01-01精简版[下载](https://template-mine.oss-cn-beijing.aliyuncs.com/NAS%E4%B8%8B%E8%BD%BD/%E6%97%81%E8%B7%AF%E7%94%B1/bleachwrt-mini-20240101-x86-64-generic-squashfs-combined-efi.vmdk)
2023-01-06大而全版[下载](https://template-mine.oss-cn-beijing.aliyuncs.com/NAS%E4%B8%8B%E8%BD%BD/%E6%97%81%E8%B7%AF%E7%94%B1/bleach-plus-20230106-openwrt-x86-64-generic-squashfs-combined-efi.vmdk)
2024-01-01大而全版[下载](https://template-mine.oss-cn-beijing.aliyuncs.com/NAS%E4%B8%8B%E8%BD%BD/%E6%97%81%E8%B7%AF%E7%94%B1/bleachwrt-plus-20240101-x86-64-generic-squashfs-combined-efi.vmdk)
# 安装OpenWRT
打开Virtualization Station虚拟化工作站后点击右上角导入→映像转换器。
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/215807.webp">
上方选择直接解压出来的 img 映像文件,下方转换后的保存为止,直接导出到目录即可。
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/215837.webp">
创建虚拟机,名称输入**【openwrt】,此时下方的操作系统会自动转换为【Linux】**
描述这里可填可不填,我个人习惯将使用的 openwrt 简介填在这里,方便下面查看。
磁盘位置选择**【使用现有映像】**,选择之前转换好的的openwrt固件,点击确认。
其他没什么需要修改的,openwrt 对系统要求不高,1核1G足够了。
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/215914.webp">
先不开机,在总览这里点击下方的设置按钮。
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/215945.webp">
【设定】-【磁盘空间】,将磁盘界面改成 SATA,点击下方的**【套用】**
后续如果还想调整,比如加大核心内存等都可以在这里更改。
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/220026.webp">
设置完成,点击开机
等个十几秒,看到小窗口的代码不动了,点击小窗口进入代码页面
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/220057.webp">
# 设置网络
输入命令修改ip地址
```
vi etc/config/network
```
VI编辑器简单实用方式:按 i 键进行修改,修改完后按Esc退出,接着输入命令 :wq ,保存刚才的修改
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/212539.webp">
之后输入service network restart命令重启网络就可以了。
待重启之后,在浏览器输入刚才修改过的IP,就可以进入Openwrt的登陆界面了,OpenWRT的默认账号密码是root:password,进入系统后可以修改密码。
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/212805.webp">
# 配置OpenWRT
安装完成后,要想正常使用还需要再配置一下,找到左侧菜单 网络–>接口,选择LAN的接口进行修改
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/212917.webp">
这里主要设置IPv4地址、IPv4网关和自定义DNS服务器。
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/212953.webp">
同时在下方勾选忽略此接口,来禁用OpenWRT的DHCP功能,保存并应用就可以了。
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/213025.webp">
# OpenWRT常用功能
## 拨号上网
默认情况下,运营商提供的光猫是不支持DDNS和端口转发等功能的,那么我们就需要使用OpenWRT来进行拨号上网,并在里面进行DDNS和端口转发等功能。
首先来看下网络组成情况。
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/213121.webp">
我们需要将光猫改成桥接模式(可以找维修师傅远程处理即可),然后在OpenWRT里进行如下操作:
点击网络–>接口–>LAN–>修改–>物理设置,取消桥接接口选项。
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/213151.webp">
在网络–>接口中添加新接口,如图所示进行设置,点击提交。
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/213304.webp">
修改WAN口,在基础设置中输入拨号账号和密码即可。
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/213741.webp">
PC上网时,将默认网关设置成旁路由的IP地址(我这里是192.168.31.2)。
## 端口转发
点击网络–>防火墙–>端口转发来进行端口转发的设置。
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/213816.webp">
## 设置DDNS
首先我们要了解DDNS的原理。
如图为PC正常上网的流程,PC根据域名到DNS服务器请求IP地址,DNS服务器根据A 记录查找IP地址并返回给PC,PC使用IP地址请求web服务。(当然这些流程对用户来说都是透明的)
了解了这个流程我们再来说DDNS,DDNS其实就是动态的调整DNS服务器中的A记录,实现的前提就是域名服务商提供API来修改域名的A记录,我们在需要使用DDNS的地方通过脚本来获取可能动态会变动的公网IP,然后通过API告诉域名服务商修改A记录。
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/214355.webp">
我以DNSPOD为例进行演示,在DNSPOD的控制台找到域名解析,并添加一条A记录,这里我设置主机记录为home,IP地址先随便填写.
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/214525.webp">
在DNSPOD的控制台点击我的账号并选择我的密钥,查看对应的SecretId和SecretKey .
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/214601.webp">
点击服务–>动态DDNS–>端口转发来进行端口转发的设置,在下方输入框输入DNSPOD并点击添加.
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/214645.webp">
勾选启用,查询主机名填写刚才添加记录的域名,DDNS服务提供商,选择dnspod.cn,域名填写刚才添加记录的域名,用户名和密码对应SecretId和SecretKey,然后点击保存并应用.
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/214726.webp">
当下图红线处出现域名和IP对应关系时,即为配置成功.
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/214756.webp">
## 其他功能
OpenWRT是搭载在Linux系统之上的,里面的服务还有很多,比如frp内网穿透、DNS过滤器、广告过滤大师、Docker等,在此就不再一一列举了,感兴趣的可以在评论区留言共同交流.

@ -0,0 +1,195 @@
---
title: 群晖NAS安装旁路由
tags:
- 网络
- NAS技术
categories: NAS服务
abbrlink: 43311
date: 2022-02-25 11:02:22
keywords:
description:
---
>试想以下这样的场景,当我们了解了DDNS、端口转发等功能,却发现家里的路由器并不支持;或者当家里的路由器对于拦截广告等功能已经不堪重负时,我们应该怎么办?
>最好的方式当然是换一台路由器,但是当我们手边刚好有一台NAS的时候,不妨使用NAS搭建旁路由(软路由)的方式来解决这些问题。
# NAS安装旁路由的两种方式
网上流传比较多的有两种方式:
>通过NAS自带的虚拟机安装
>通过Docker安装。
笔者在当时安装的时候,刚开始是使用Docker的方式,中间也对网络也进行了各种设置,但是让我崩溃的是,安装完后对笔者的某些应用有限制。不得已只好放弃,转而采用虚拟机进行安装。建议在有NAS的时候采用虚拟机的方式安装,而在无法提供虚拟机的时候再采用Docker的方式。
# 安装虚拟机VMM
在这里使用群晖NAS进行安装,其他NAS流程是类似的。
VMM是虚拟机程序Virtual Machine Manager的简称,群晖套件中心就有,打开套件中心,搜索Virtual Machine Manager,第一个就是,点击安装
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/204800.webp">
# 主板开启虚拟化
进入群晖系统–>控制面板–>网络–>选中“局域网”–>点击“管理”,在下拉菜单中选择“Open vSwitch 设置”
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/205037.webp">
在弹出界面中选中“启用”
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/205222.webp">
# 下载OpenWRT
2023-01-06精简版[下载](https://template-mine.oss-cn-beijing.aliyuncs.com/NAS%E4%B8%8B%E8%BD%BD/%E6%97%81%E8%B7%AF%E7%94%B1/bleach-mini-20230106-openwrt-x86-64-generic-squashfs-combined-efi.img.gz)
2024-01-01精简版[下载](https://template-mine.oss-cn-beijing.aliyuncs.com/NAS%E4%B8%8B%E8%BD%BD/%E6%97%81%E8%B7%AF%E7%94%B1/bleachwrt-mini-20240101-x86-64-generic-squashfs-combined-efi.img.gz)
2023-01-06大而全版[下载](https://template-mine.oss-cn-beijing.aliyuncs.com/NAS%E4%B8%8B%E8%BD%BD/%E6%97%81%E8%B7%AF%E7%94%B1/bleach-plus-20230106-openwrt-x86-64-generic-squashfs-combined-efi.img.gz)
2024-01-01大而全版[下载](https://template-mine.oss-cn-beijing.aliyuncs.com/NAS%E4%B8%8B%E8%BD%BD/%E6%97%81%E8%B7%AF%E7%94%B1/bleachwrt-plus-20240101-x86-64-generic-squashfs-combined-efi.img.gz)
# 安装OpenWRT
解压刚才下载的文件得到.img后缀的文件,然后打开群晖内的Virtual Machine Manager,选择映像–>新增–>从PC上传文件并上传刚才解压出来的img文件,之后点击下一步。
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/211106.webp">
勾选存储空间并点击应用
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/211251.webp">
状态处显示良好,表明此次上传没有问题。
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/211532.webp">
上传完映像之后,我们点击网络–>新增,为网络命名为ETH1,勾选连接网线的接口(一般局域网1就是),随后应用。
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/211615.webp">
之后我们点击虚拟机,导入刚才的镜像。
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/211706.webp">
从硬盘映像导入,选择刚才上传的映像,然后点击下一步
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/211748.webp">
选择存储空间后继续下一步
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/211824.webp">
为虚拟机命名,选择CPU和内存容量,继续下一步。
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/211921.webp">
设置虚拟盘大小,其他默认,下一步。
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/212006.webp">
网络选择ETH1并设置,在里面的型号中选择e1000。(不选也可以使用,但网络为半双工的,改为e1000后,网络变成全双工)。
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/212057.webp">
自动启动选择是,其他默认,下一步
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/212134.webp">
设置管理员权限,只勾选admin,下一步
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/212236.webp">
勾选创建后启动虚拟机,随后应用。
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/212347.webp">
至此,虚拟机已经安装成功了。但是openwrt的默认ip地址是192.168.1.1,而我家里的路由器IP地址是192.168.31.1,需要将openwrt的IP地址设置成192.168.31.X,在此我设置成192.168.31.2(需要和主路由一个网段)。
如下图,选择旁路由后点击连接,在弹出的标签页进入openwrt的终端。
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/212432.webp">
# 设置网络
输入命令修改ip地址
```
vi etc/config/network
```
VI编辑器简单实用方式:按 i 键进行修改,修改完后按Esc退出,接着输入命令 :wq ,保存刚才的修改
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/212539.webp">
之后输入service network restart命令重启网络就可以了。
待重启之后,在浏览器输入刚才修改过的IP,就可以进入Openwrt的登陆界面了,OpenWRT的默认账号密码是root:password,进入系统后可以修改密码。
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/212805.webp">
# 配置OpenWRT
安装完成后,要想正常使用还需要再配置一下,找到左侧菜单 网络–>接口,选择LAN的接口进行修改
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/212917.webp">
这里主要设置IPv4地址、IPv4网关和自定义DNS服务器。
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/212953.webp">
同时在下方勾选忽略此接口,来禁用OpenWRT的DHCP功能,保存并应用就可以了。
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/213025.webp">
# OpenWRT常用功能
## 拨号上网
默认情况下,运营商提供的光猫是不支持DDNS和端口转发等功能的,那么我们就需要使用OpenWRT来进行拨号上网,并在里面进行DDNS和端口转发等功能。
首先来看下网络组成情况。
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/213121.webp">
我们需要将光猫改成桥接模式(可以找维修师傅远程处理即可),然后在OpenWRT里进行如下操作:
点击网络–>接口–>LAN–>修改–>物理设置,取消桥接接口选项。
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/213151.webp">
在网络–>接口中添加新接口,如图所示进行设置,点击提交。
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/213304.webp">
修改WAN口,在基础设置中输入拨号账号和密码即可。
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/213741.webp">
PC上网时,将默认网关设置成旁路由的IP地址(我这里是192.168.31.2)。
## 端口转发
点击网络–>防火墙–>端口转发来进行端口转发的设置。
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/213816.webp">
## 设置DDNS
首先我们要了解DDNS的原理。
如图为PC正常上网的流程,PC根据域名到DNS服务器请求IP地址,DNS服务器根据A 记录查找IP地址并返回给PC,PC使用IP地址请求web服务。(当然这些流程对用户来说都是透明的)
了解了这个流程我们再来说DDNS,DDNS其实就是动态的调整DNS服务器中的A记录,实现的前提就是域名服务商提供API来修改域名的A记录,我们在需要使用DDNS的地方通过脚本来获取可能动态会变动的公网IP,然后通过API告诉域名服务商修改A记录。
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/214355.webp">
我以DNSPOD为例进行演示,在DNSPOD的控制台找到域名解析,并添加一条A记录,这里我设置主机记录为home,IP地址先随便填写.
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/214525.webp">
在DNSPOD的控制台点击我的账号并选择我的密钥,查看对应的SecretId和SecretKey .
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/214601.webp">
点击服务–>动态DDNS–>端口转发来进行端口转发的设置,在下方输入框输入DNSPOD并点击添加.
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/214645.webp">
勾选启用,查询主机名填写刚才添加记录的域名,DDNS服务提供商,选择dnspod.cn,域名填写刚才添加记录的域名,用户名和密码对应SecretId和SecretKey,然后点击保存并应用.
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/214726.webp">
当下图红线处出现域名和IP对应关系时,即为配置成功.
<img src="https://img.dreamlyn.cn:8443/i/2024/02/22/214756.webp">
## 其他功能
OpenWRT是搭载在Linux系统之上的,里面的服务还有很多,比如frp内网穿透、DNS过滤器、广告过滤大师、Docker等,在此就不再一一列举了,感兴趣的可以在评论区留言共同交流.

@ -0,0 +1,35 @@
---
title: 什么是DDNS
tags:
- 网络
categories: 网络基础
abbrlink: 42499
date: 2022-02-22 11:02:22
keywords:
description:
---
# 什么是DDNS
动态 DNS(DDNS)是一项在 IP 地址发生变化时可以自动更新 DNS 记录的服务。域名将网络 IP 地址转换为人类可读的名称,便于识别和使用。将名称映射到 IP 地址的信息以表格形式记录在 DNS 服务器上。但是,网络管理员会动态分配 IP 地址并经常更改。每当 IP 地址发生变化时,DDNS 服务都会更新 DNS 服务器记录。借助 DDNS,域名管理变得更容易、更高效。
# 为什么要使用动态DNS
过去,IP 地址是静态的,很少更改。但是,由于互联网规模不断扩大以及服务器、智能传感器和最终用户设备数量大幅增加,IP 地址出现短缺。
# 动态DNS运作机制
组织通常会订阅由 DDNS 提供商提供的动态 DNS(DDNS)服务。提供商还维护用于处理关联域名的 DNS 记录的 DNS 服务器。以下是一般步骤:
1. 向动态 DNS 服务提供商注册域名并配置 DNS 设置
2. 向提供商提供域名的初始 IP 地址
3. 使用更改的 IP 地址在设备或服务器实例上安装动态 DNS 客户端
DDNS 客户端持续监控 IP 地址并检测任何更改。客户端向动态 DNS 提供商发送 DNS 记录更新通知,通知其新的 IP 地址。动态 DNS 提供商修改记录以指向新 IP 地址。
动态 DNS 客户端会继续监控 IP 地址以了解进一步的更改。每当发生新的更改时,该过程都会重复。
# DNS和动态DNS的差异
DNS 服务是一种全球分布式服务,可将人类可读的名称转换为数字 IP 地址。DNS 服务器将域名请求转换为 IP 地址。这些地址控制最终用户在 Web 浏览器中键入域名时将访问哪个服务器。
动态 DNS(DDNS)是 DNS 的扩展,可自动实时更新与域名关联的 IP 地址。它扩展 DNS 的功能。借助 DDNS,即使在动态 IP 地址环境中,组织和个人也可以保持连接和可访问性。
DNS 得到所有 DNS 服务器的普遍支持,在全球范围内用于将域名解析为 IP 地址。
另一方面,DDNS 需要特定 DDNS 提供商的支持。组织必须订阅 DDNS 服务并将其设备或路由器配置为与所选 DDNS 提供商合作。

@ -0,0 +1,27 @@
---
title: 什么是DNS
tags:
- 网络
categories: 网络基础
abbrlink: 27420
date: 2022-01-22 11:02:22
keywords:
description:
---
# DNS 基础知识
Internet 上的所有计算机,从智能手机或笔记本电脑到可提供大量零售网站内容的服务器,均通过使用编号寻找另一方并相互通信。这些编号称为 IP 地址。当我们打开 Web 浏览器并前往一个网站时,不必记住和输入长编号。而是输入域名(如 example.com),然后在正确的位置结束。
# DNS 将流量路由到 Web 应用程序
下图概述了 DNS 服务如何协同工作以将终端用户路由到您的网站或应用程序。
<img src="https://img.dreamlyn.cn:8443/i/2024/02/23/112256.webp">
1. 用户打开 Web 浏览器,在地址栏中输入 www.example.com,然后按 Enter 键。
2. www.example.com 的请求被路由到 DNS 解析程序,这一般由用户的互联网服务提供商 (ISP) 进行管理,例如有线 Internet 服务提供商、DSL 宽带提供商或公司网络。
3. ISP 的 DNS 解析程序将 www.example.com 的请求转发到 DNS 根名称服务器。
4. ISP 的 DNS 解析程序再次转发 www.example.com 的请求,这次转发到 .com 域的一个 TLD 名称服务器。.com 域的名称服务器使用与 example.com 域相关的四个 Amazon Route 53 名称服务器的名称来响应该请求。
5. ISP 的 DNS 解析程序选择一个 Amazon Route 53 名称服务器,并将 www.example.com 的请求转发到该名称服务器。
6. Amazon Route 53 名称服务器在 example.com 托管区域中查找 www.example.com 记录,获得相关值,例如,Web 服务器的 IP 地址 (192.0.2.44),并将 IP 地址返回至 DNS 解析程序。
7. ISP 的 DNS 解析程序最终获得用户需要的 IP 地址。解析程序将此值返回至 Web 浏览器。DNS 解析程序还会将 example.com 的 IP 地址缓存 (存储) 您指定的时长,以便它能够在下次有人浏览 example.com 时更快地作出响应。有关更多信息,请参阅存活期 (TTL)。
8. Web 浏览器将 www.example.com 的请求发送到从 DNS 解析程序中获得的 IP 地址。这是您的内容所处位置,例如,在 Amazon EC2 实例中或配置为网站端点的 Amazon S3 存储桶中运行的 Web 服务器。
9. 192.0.2.44 上的 Web 服务器或其他资源将 www.example.com 的 Web 页面返回到 Web 浏览器,且 Web 浏览器会显示该页面。

@ -0,0 +1,4 @@
---
title: 关于作者
date: 2024-02-22 10:54:11
---

@ -0,0 +1,4 @@
---
title: 留言板
date: 2024-02-23 15:30:52
---

@ -0,0 +1,207 @@
---
title: B站字幕转换
date: 2022-02-22 10:34:50
toc: false
---
{% raw %}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Cache-Control" content="no-cache">
<meta http-equiv="Expires" content="0">
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<!-- import Vue before Element -->
<script src="https://unpkg.com/vue@2/dist/vue.js"></script>
<!-- import JavaScript -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
</head>
<body>
<div id="app">
<div class="tools-upload">
<el-upload action="#" :http-request="requestUpload" :show-file-list="false">
<el-button size="mini" type="primary">上传</el-button>
</el-upload>
<div class="tools-right">
<div class="tool-item">
文件编码:
<el-select size="mini" v-model="fileEncoder" placeholder="文件编码">
<el-option label="GBK" value="GBK"></el-option>
<el-option label="UTF-8" value="UTF-8"></el-option>
<el-option label="GB2312" value="GB2312&quot;"></el-option>
</el-select>
</div>
<div class="tool-item">
换行符:
<el-select size="mini" v-model="newline" placeholder="换行符">
<el-option label="\r\n (Windows)" value="windows"></el-option>
<el-option label="\n (Linux)" value="linux"></el-option>
</el-select>
</div>
</div>
</div>
<el-input type="textarea" placeholder="请上传或输入字幕文件" v-model="originText" rows="15" show-word-limit="">
</el-input>
<div class="tools-btn">
<el-button size="mini" @click="generateSrt">生成SRT文件</el-button>
<el-button size="mini" @click="generateTXT">生成TXT文件</el-button>
<el-button size="mini" @click="clear">清空</el-button>
</div>
<el-input type="textarea" placeholder="等待生成SRT文件" v-model="srtText" rows="15" show-word-limit="">
</el-input>
<el-button type="text" @click="saveSrt">保存到本地</el-button>
<h3>字幕提取方法参考下面的视频</h3>
<div style="position: relative; padding: 30% 50%;">
<iframe src="//player.bilibili.com/player.html?aid=863543343&amp;bvid=BV16G4y1S7Hp&amp;cid=985575449&amp;page=1&amp;as_wide=1&amp;high_quality=1&amp;danmaku=1"
scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"
style="position: absolute; width: 100%; height: 100%; left: 0; top: 0;"></iframe>
</div>
</div>
</body>
<script>
new Vue({
el: '#app',
data: function () {
return {
fileEncoder: "UTF-8",
newline: "windows",
originText: "",
srtText: "",
fileName: ""
}
},
methods: {
// 覆盖默认的上传行为
requestUpload(content) {
let file = content.file
let index = file.name.lastIndexOf('.')
this.fileName = file.name.substring(0, index)
// 新建一个FileReader
const reader = new FileReader()
// 读取文件
reader.readAsText(file, this.fileEncoder)
// 读取完文件之后会回来这里
reader.onload = (e) => {
// 读取文件内容
const fileString = e.target.result
// 接下来可对文件内容进行处理
this.originText = fileString
}
},
generateSrt() {
this.srtText = ""
let originObj = JSON.parse(this.originText)
let body = originObj.body
let lastTo = null
for (let index = 0; index < body.length; index++) {
let line = body[index]
this.srtText += (index + (this.newline == 'windows' ? '\r\n' : '\n'))
let from = line.from
if (lastTo) {
let timeA = (from + '').split(".");
let timeB = (lastTo + '').split("."); //上次的时间
if (parseInt(timeB[0]) >= parseInt(timeA[0])) {
timeA[0] = timeB[0];
if (timeB[1]) {
if (parseInt(timeB[1]) >= parseInt(timeA[1])) {
timeA[1] = timeB[1];
}
}
from = (timeA[1] ? (timeA[0] + '.' + timeA[1]) : timeA[0])
}
}
this.srtText += (this.deelTime(from) + " --> ")
let to = line.to
lastTo = to
this.srtText += (this.deelTime(to) + (this.newline == 'windows' ? '\r\n' : '\n'))
this.srtText += line.content
this.srtText += (this.newline == 'windows' ? '\r\n' : '\n')
this.srtText += (this.newline == 'windows' ? '\r\n' : '\n')
}
},
generateTXT() {
this.srtText = ""
let originObj = JSON.parse(this.originText)
let body = originObj.body
let lastTo = null
for (let index = 0; index < body.length; index++) {
let line = body[index]
let content = line.content
this.srtText += content
this.srtText += (this.newline == 'windows' ? '\r\n' : '\n')
}
},
deelTime(origin) {
let timeA = (origin + '').split(".");
let tranTime = this.sumSecondToTime(timeA[0]);
return tranTime + "," + (timeA.length < 2 ? '000' : timeA[1].padStart(3, '0'));
},
sumSecondToTime(sumSecond) {
if (sumSecond <= 0) {
return "00:00:00";
}
let h = parseInt(sumSecond / 3600);
let m = parseInt((sumSecond - h * 3600) / 60);
let s = sumSecond - h * 3600 - m * 60;
let time = (h + '').padStart(2, '0') + ':' + (m + '').padStart(2, '0') + ':' + (s + '').padStart(2,
'0');
return time;
},
clear() {
this.originText = ""
},
saveSrt() {
// const file = new Blob(this.srtText, {
// type: 'text/plain'
// });
// a.href = URL.createObjectURL(file);
// a.download = name;
var element = document.createElement('a');
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(this.srtText));
if (this.fileName != '') {
element.setAttribute('download', this.fileName + '.srt');
} else {
element.setAttribute('download', 'translated.srt');
}
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
}
})
</script>
<style>
.tools-upload {
margin: 10px 0;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.tools-right {
display: flex;
flex-direction: row;
align-items: center;
font-size: 15px;
}
.tool-item {
margin-left: 10px;
}
.tools-btn {
margin: 10px 0;
}
</style>
</html>
{% endraw %}

@ -0,0 +1,6 @@
---
title: 友情链接
date: 2022-02-22 10:34:50
type: 'link'
random: true
---

@ -0,0 +1,6 @@
---
title: 标签
date: 2022-02-22 09:57:47
type: "tags"
comments: false
---

@ -0,0 +1,13 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
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']

@ -0,0 +1,82 @@
name: Bug report
description: Create a report to help us improve
title: '[Bug]: '
body:
- type: markdown
attributes:
value: |
重要:請依照該模板來提交
Please follow the template to create a new issue
- type: input
id: butterfly-ver
attributes:
label: 使用的 Butterfly 版本? | What version of Butterfly are you use?
description: 檢視主題的 package.json | Check the theme's package.json
validations:
required: true
- type: dropdown
id: modify
attributes:
label: 是否修改过主题文件? || Has the theme files been modified?
options:
- 是 (Yes)
- 不是 (No)
validations:
required: true
- type: dropdown
id: browser
attributes:
label: 使用的瀏覽器? || What browse are you using?
options:
- Chrome
- Edge
- Safari
- Opera
- Other
validations:
required: true
- type: dropdown
id: platform
attributes:
label: 使用的系統? || What operating system are you using?
options:
- Windows
- macOS
- Linux
- Android
- iOS
- Other
validations:
required: true
- type: textarea
id: dependencies
attributes:
label: 依賴插件 | Package dependencies Information
description: 在 Hexo 根目錄下執行`npm ls --depth 0` | Run `npm ls --depth 0` in Hexo root directory
render: Text
validations:
required: true
- type: textarea
id: description
attributes:
label: 問題描述 | Describe the bug
description: 請描述你的問題現象 | A clear and concise description of what the bug is.
placeholder: 請儘量提供截圖來定位問題 | If applicable, add screenshots to help explain your problem
value:
validations:
required: true
- type: input
id: website
attributes:
label: 出現問題網站 | Website
description: 請提供下可復現網站地址 | Please supply a website url which can reproduce problem.
placeholder:
validations:
required: true

@ -0,0 +1,18 @@
blank_issues_enabled: false
contact_links:
- name: Questions about Butterfly
url: https://github.com/jerryc127/hexo-theme-butterfly/discussions
about: 一些使用問題請到 Discussion 詢問。 Please ask questions in Discussion.
- name: Butterfly Q&A
url: https://butterfly.js.org/posts/98d20436/
about: Butterfly Q&A
- name: Telegram
url: https://t.me/bu2fly
about: 'Official Telegram Group'
- name: QQ 群
url: https://jq.qq.com/?_wv=1027&k=KU9105XR
about: '群號 1070540070'

@ -0,0 +1,14 @@
name: Feature request
description: Suggest an idea for this project
title: '[Feature]: '
body:
- type: textarea
id: feature-request
attributes:
label: 想要的功能 | What feature do you want?
description: 請描述你需要的新功能 | A clear and concise description of what the feature is.
placeholder:
value:
validations:
require: true

@ -0,0 +1,19 @@
name: npm publish
on:
release:
types: [created]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
# Setup .npmrc file to publish to npm
- uses: actions/setup-node@v1
with:
node-version: '12.x'
registry-url: 'https://registry.npmjs.org'
- run: npm install
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

@ -0,0 +1,19 @@
name: 'Close stale issues and PRs'
on:
schedule:
- cron: '30 1 * * *'
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v5
with:
days-before-issue-stale: 30
days-before-pr-stale: -1
days-before-close: 7
stale-issue-message: 'This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.'
close-pr-message: 'This issue has not seen any activity since it was marked stale. Closing.'
stale-issue-label: 'Stale'
exempt-issue-labels: 'pinned,bug,enhancement,documentation,Plan'
operations-per-run: 1000

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

@ -0,0 +1,111 @@
<div align="right">
<a title="Chinese" href="/README_CN.md">中文</a>
</div>
# hexo-theme-butterfly
![master version](https://img.shields.io/github/package-json/v/jerryc127/hexo-theme-butterfly/master?color=%231ab1ad&label=master)
![master version](https://img.shields.io/github/package-json/v/jerryc127/hexo-theme-butterfly/dev?label=dev)
![https://img.shields.io/npm/v/hexo-theme-butterfly?color=%09%23bf00ff](https://img.shields.io/npm/v/hexo-theme-butterfly?color=%09%23bf00ff)
![hexo version](https://img.shields.io/badge/hexo-5.3.0+-0e83c)
![license](https://img.shields.io/github/license/jerryc127/hexo-theme-butterfly?color=FF5531)
![](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/theme-butterfly-readme.png)
📢 Demo: [Butterfly](https://butterfly.js.org/) || [CrazyWong](https://blog.crazywong.com/)
📖 Docs: [English](https://butterfly.js.org/en/posts/butterfly-docs-en-get-started/) || [Chinese](https://butterfly.js.org/posts/21cfbf15/)
Based on [hexo-theme-melody](https://github.com/Molunerfinn/hexo-theme-melody) theme.
## 💻 Installation
### GIT
> If you are in Mainland China, you can download in [Gitee](https://gitee.com/immyw/hexo-theme-butterfly.git)
Stable branch [recommend]:
```
git clone -b master https://github.com/jerryc127/hexo-theme-butterfly.git themes/butterfly
```
Dev branch:
```
git clone -b dev https://github.com/jerryc127/hexo-theme-butterfly.git themes/butterfly
```
### NPM
> It supports Hexo 5.0.0 or later
In Hexo site root directory
```powershell
npm i hexo-theme-butterfly
```
## ⚙ Configuration
Set theme in the hexo work folder's root config file `_config.yml`:
> theme: butterfly
If you don't have pug & stylus renderer, try this:
> npm install hexo-renderer-pug hexo-renderer-stylus
## 🎉 Features
- [x] Card UI 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/mac/mac light), 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/messenger)
- [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......
## ✨ Contributors
<a href="https://github.com/jerryc127/hexo-theme-butterfly/graphs/contributors">
<img src="https://contrib.rocks/image?repo=jerryc127/hexo-theme-butterfly" />
</a>
## 📷 Screenshots
![](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-1.jpg)
![](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-2.jpg)
![](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-3.jpg)
![](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-4.jpg)
![](https://cdn.jsdelivr.net/gh/jerryc127/CDN/img/theme-butterfly-readme-homepage-1.png)
![](https://cdn.jsdelivr.net/gh/jerryc127/CDN/img/theme-butterfly-readme-homepage-2.png)

@ -0,0 +1,111 @@
<div align="right">
<a title="English" href="/README.md">English</a>
</div>
# hexo-theme-butterfly
![master version](https://img.shields.io/github/package-json/v/jerryc127/hexo-theme-butterfly/master?color=%231ab1ad&label=master)
![master version](https://img.shields.io/github/package-json/v/jerryc127/hexo-theme-butterfly/dev?label=dev)
![https://img.shields.io/npm/v/hexo-theme-butterfly?color=%09%23bf00ff](https://img.shields.io/npm/v/hexo-theme-butterfly?color=%09%23bf00ff)
![hexo version](https://img.shields.io/badge/hexo-5.3.0+-0e83c)
![license](https://img.shields.io/github/license/jerryc127/hexo-theme-butterfly?color=FF5531)
![](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/theme-butterfly-readme.png)
📢 預覽: [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/)
一款基於[hexo-theme-melody](https://github.com/Molunerfinn/hexo-theme-melody)修改的主題
## 💻 安裝
### Git 安裝
> 本倉庫同時上傳到 [Gitee](https://gitee.com/immyw/hexo-theme-butterfly.git),如果你訪問 Github 緩慢,可從 Gitee 中下載。
在博客根目錄裡安裝穩定版【推薦】
```powershell
git clone -b master https://github.com/jerryc127/hexo-theme-butterfly.git themes/butterfly
```
如果想要安裝比較新的dev分支,可以
```powershell
git clone -b dev https://github.com/jerryc127/hexo-theme-butterfly.git themes/butterfly
```
### npm 安裝
> 此方法只支持Hexo 5.0.0以上版本
在博客根目錄裡
```powershell
npm i hexo-theme-butterfly
```
## ⚙ 應用主題
修改hexo配置文件`_config.yml`,把主題改為`Butterfly`
```
theme: butterfly
```
>如果你沒有pug以及stylus的渲染器,請下載安裝: npm install hexo-renderer-pug hexo-renderer-stylus --save
## 🎉 特色
- [x] 卡片化設計
- [X] 支持二級目錄
- [x] 雙欄設計
- [x] 響應式主題
- [x] 夜間模式
- [x] Pjax
- [x] 文章閲讀模式
- [x] 簡體和繁體轉換
- [X] 電腦和手機都可查看TOC目錄
- [X] 內置多種代碼配色(darker/pale night/light/ocean/mac/mac light),可自定義代碼配色
- [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/messenger)
- [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......
## ✨ 貢獻者
<a href="https://github.com/jerryc127/hexo-theme-butterfly/graphs/contributors">
<img src="https://contrib.rocks/image?repo=jerryc127/hexo-theme-butterfly" />
</a>
## 📷 截圖
![](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-1.jpg)
![](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-2.jpg)
![](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-3.jpg)
![](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-4.jpg)
![](https://cdn.jsdelivr.net/gh/jerryc127/CDN/img/theme-butterfly-readme-homepage-1.png)
![](https://cdn.jsdelivr.net/gh/jerryc127/CDN/img/theme-butterfly-readme-homepage-2.png)

@ -0,0 +1,994 @@
# Navigation bar settings (導航欄設置)
# see https://butterfly.js.org/posts/4aa8abbe/##導航欄設置-Navigation-bar-settings
# --------------------------------------
nav:
logo: # image
display_title: true
fixed: false # fixed navigation bar
# Menu 目錄
menu:
# Home: / || fas fa-home
# Archives: /archives/ || fas fa-archive
# Tags: /tags/ || fas fa-tags
# Categories: /categories/ || fas fa-folder-open
# List||fas fa-list:
# Music: /music/ || fas fa-music
# Movie: /movies/ || fas fa-video
# Link: /link/ || fas fa-link
# About: /about/ || fas fa-heart
# Code Blocks (代碼相關)
# --------------------------------------
highlight_theme: light # darker / pale night / light / ocean / mac / mac light / false
highlight_copy: true # copy button
highlight_lang: true # show the code language
highlight_shrink: false # true: shrink the code blocks / false: expand the code blocks | none: expand code blocks and hide the button
highlight_height_limit: false # unit: px
code_word_wrap: false
# Social Settings (社交圖標設置)
# formal:
# icon: link || the description || color
social:
# fab fa-github: https://github.com/xxxxx || Github || '#24292e'
# fas fa-envelope: mailto:xxxxxx@gmail.com || Email || '#4a7dbe'
# Image (圖片設置)
# --------------------------------------
# Favicon(網站圖標)
favicon: /img/favicon.png
# Avatar (頭像)
avatar:
img: https://i.loli.net/2021/02/24/5O1day2nriDzjSu.png
effect: false
# Disable all banner image
disable_top_img: false
# The banner image of home page
index_img:
# If the banner of page not setting, it will show the top_img
default_top_img:
# The banner image of archive page
archive_img:
# If the banner of tag page not setting, it will show the top_img
# note: tag page, not tags page (子標籤頁面的 top_img)
tag_img:
# The banner image of tag page
# format:
# - tag name: xxxxx
tag_per_img:
# If the banner of category page not setting, it will show the top_img
# note: category page, not categories page (子分類頁面的 top_img)
category_img:
# The banner image of category page
# format:
# - category name: xxxxx
category_per_img:
cover:
# display the cover or not (是否顯示文章封面)
index_enable: true
aside_enable: true
archives_enable: true
# the position of cover in home page (封面顯示的位置)
# left/right/both
position: both
# When cover is not set, the default cover is displayed (當沒有設置cover時,默認的封面顯示)
default_cover:
# - https://i.loli.net/2020/05/01/gkihqEjXxJ5UZ1C.jpg
# Replace Broken Images (替換無法顯示的圖片)
error_img:
flink: /img/friend_404.gif
post_page: /img/404.jpg
# A simple 404 page
error_404:
enable: false
subtitle: 'Page Not Found'
background: https://i.loli.net/2020/05/19/aKOcLiyPl2JQdFD.png
post_meta:
page: # Home Page
date_type: created # created or updated or both 主頁文章日期是創建日或者更新日或都顯示
date_format: date # date/relative 顯示日期還是相對日期
categories: true # true or false 主頁是否顯示分類
tags: false # true or false 主頁是否顯示標籤
label: true # true or false 顯示描述性文字
post:
date_type: both # created or updated or both 文章頁日期是創建日或者更新日或都顯示
date_format: date # date/relative 顯示日期還是相對日期
categories: true # true or false 文章頁是否顯示分類
tags: true # true or false 文章頁是否顯示標籤
label: true # true or false 顯示描述性文字
# Display the article introduction on homepage
# 1: description
# 2: both (if the description exists, it will show description, or show the auto_excerpt)
# 3: auto_excerpt (default)
# false: do not show the article introduction
index_post_content:
method: 3
length: 500 # if you set method to 2 or 3, the length need to config
# anchor
anchor:
# when you scroll, the URL will update according to header id.
auto_update: false
# Click the headline to scroll and update the anchor
click_to_scroll: false
# figcaption (圖片描述文字)
photofigcaption: false
# copy settings
# copyright: Add the copyright information after copied content (複製的內容後面加上版權信息)
copy:
enable: true
copyright:
enable: false
limit_count: 50
# Post
# --------------------------------------
# toc (目錄)
toc:
post: true
page: false
number: true
expand: false
style_simple: false # for post
scroll_percent: true
post_copyright:
enable: true
decode: false
author_href:
license: CC BY-NC-SA 4.0
license_url: https://creativecommons.org/licenses/by-nc-sa/4.0/
# Sponsor/reward
reward:
enable: false
text:
QR_code:
# - img: /img/wechat.jpg
# link:
# text: wechat
# - img: /img/alipay.jpg
# link:
# text: alipay
# Post edit
# Easily browse and edit blog source code online.
post_edit:
enable: false
# url: https://github.com/user-name/repo-name/edit/branch-name/subdirectory-name/
# For example: https://github.com/jerryc127/butterfly.js.org/edit/main/source/
url:
# Related Articles
related_post:
enable: true
limit: 6 # Number of posts displayed
date_type: created # or created or updated 文章日期顯示創建日或者更新日
# post_pagination (分頁)
# value: 1 || 2 || false
# 1: The 'next post' will link to old post
# 2: The 'next post' will link to new post
# false: disable pagination
post_pagination: 1
# Displays outdated notice for a post (文章過期提醒)
noticeOutdate:
enable: false
style: flat # style: simple/flat
limit_day: 500 # When will it be shown
position: top # position: top/bottom
message_prev: It has been
message_next: days since the last update, the content of the article may be outdated.
# Footer Settings
# --------------------------------------
footer:
owner:
enable: true
since: 2020
custom_text:
copyright: true # Copyright of theme and framework
# aside (側邊欄)
# --------------------------------------
aside:
enable: true
hide: false
button: true
mobile: true # display on mobile
position: right # left or right
display:
archive: true
tag: true
category: true
card_author:
enable: true
description:
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 # if set 0 will show all
sort: date # date or updated
sort_order: # Don't modify the setting unless you know how it works
card_categories:
enable: true
limit: 8 # if set 0 will show all
expand: none # none/true/false
sort_order: # Don't modify the setting unless you know how it works
card_tags:
enable: true
limit: 40 # if set 0 will show all
color: false
orderby: random # Order of tags, random/name/length
order: 1 # Sort of order. 1, asc for ascending; -1, desc for descending
sort_order: # Don't modify the setting unless you know how it works
card_archives:
enable: true
type: monthly # yearly or monthly
format: MMMM YYYY # eg: YYYY年MM月
order: -1 # Sort of order. 1, asc for ascending; -1, desc for descending
limit: 8 # if set 0 will show all
sort_order: # Don't modify the setting unless you know how it works
card_webinfo:
enable: true
post_count: true
last_push_date: true
sort_order: # Don't modify the setting unless you know how it works
card_post_series:
enable: true
series_title: false # The title shows the series name
orderBy: 'date' # Order by title or date
order: -1 # Sort of order. 1, asc for ascending; -1, desc for descending
# busuanzi count for PV / UV in site
# 訪問人數
busuanzi:
site_uv: true
site_pv: true
page_pv: true
# Time difference between publish date and now (網頁運行時間)
# Formal: Month/Day/Year Time or Year/Month/Day Time
runtimeshow:
enable: false
publish_date:
# Aside widget - Newest Comments
newest_comments:
enable: false
sort_order: # Don't modify the setting unless you know how it works
limit: 6
storage: 10 # unit: mins, save data to localStorage
avatar: true
# Bottom right button (右下角按鈕)
# --------------------------------------
# Conversion between Traditional and Simplified Chinese (簡繁轉換)
translate:
enable: false
# The text of a button
default:
# the language of website (1 - Traditional Chinese/ 2 - Simplified Chinese)
defaultEncoding: 2
# Time delay
translateDelay: 0
# The text of the button when the language is Simplified Chinese
msgToTraditionalChinese: '繁'
# The text of the button when the language is Traditional Chinese
msgToSimplifiedChinese: '簡'
# Read Mode (閲讀模式)
readmode: true
# dark mode
darkmode:
enable: true
# Toggle Button to switch dark/light mode
button: true
# Switch dark/light mode automatically (自動切換 dark mode和 light mode)
# autoChangeMode: 1 Following System Settings, if the system doesn't support dark mode, it will switch dark mode between 6 pm to 6 am
# autoChangeMode: 2 Switch dark mode between 6 pm to 6 am
# autoChangeMode: false
autoChangeMode: false
# Set the light mode time. The value is between 0 and 24. If not set, the default value is 6 and 18
start: # 8
end: # 22
# show scroll percent in scroll-to-top button
rightside_scroll_percent: false
# Don't modify the following settings unless you know how they work (非必要請不要修改 )
# Choose: readmode,translate,darkmode,hideAside,toc,chat,comment
# Don't repeat 不要重複
rightside_item_order:
enable: false
hide: # readmode,translate,darkmode,hideAside
show: # toc,chat,comment
# Math (數學)
# --------------------------------------
# About the per_page
# if you set it to true, it will load mathjax/katex script in each page (true 表示每一頁都加載js)
# if you set it to false, it will load mathjax/katex script according to your setting (add the 'mathjax: true' in page's front-matter)
# (false 需要時加載,須在使用的 Markdown Front-matter 加上 mathjax: true)
# MathJax
mathjax:
enable: false
per_page: false
# KaTeX
katex:
enable: false
per_page: false
hide_scrollbar: true
# search (搜索)
# see https://butterfly.js.org/posts/ceeb73f/#搜索系統
# --------------------------------------
# Algolia search
algolia_search:
enable: false
hits:
per_page: 6
# Local search
local_search:
enable: false
# Preload the search data when the page loads.
preload: false
# Show top n results per article, show all results by setting to -1
top_n_per_article: 1
# Unescape html strings to the readable one.
unescape: false
CDN:
# Docsearch
docsearch:
enable: false
appId:
apiKey:
indexName:
option:
# Share System (分享)
# --------------------------------------
# Share.js
# https://github.com/overtrue/share.js
sharejs:
enable: true
sites: facebook,twitter,wechat,weibo,qq
# AddToAny
# https://www.addtoany.com/
addtoany:
enable: false
item: facebook,twitter,wechat,sina_weibo,facebook_messenger,email,copy_link
# Comments System
# --------------------------------------
comments:
# Up to two comments system, the first will be shown as default
# Choose: Disqus/Disqusjs/Livere/Gitalk/Valine/Waline/Utterances/Facebook Comments/Twikoo/Giscus/Remark42/Artalk
use: # Valine,Disqus
text: true # Display the comment name next to the button
# lazyload: The comment system will be load when comment element enters the browser's viewport.
# If you set it to true, the comment count will be invalid
lazyload: false
count: false # Display comment count in post's top_img
card_post_count: false # Display comment count in Home Page
# disqus
# https://disqus.com/
disqus:
shortname:
apikey: # For newest comments widget
# Alternative Disqus - Render comments with Disqus API
# DisqusJS 評論系統,可以實現在網路審查地區載入 Disqus 評論列表,兼容原版
# https://github.com/SukkaW/DisqusJS
disqusjs:
shortname:
apikey:
option:
# livere (來必力)
# https://www.livere.com/
livere:
uid:
# gitalk
# https://github.com/gitalk/gitalk
gitalk:
client_id:
client_secret:
repo:
owner:
admin:
option:
# valine
# https://valine.js.org
valine:
appId: # leancloud application app id
appKey: # leancloud application app key
avatar: monsterid # gravatar style https://valine.js.org/#/avatar
serverURLs: # This configuration is suitable for domestic custom domain name users, overseas version will be automatically detected (no need to manually fill in)
bg: # valine background
visitor: false
option:
# waline - A simple comment system with backend support fork from Valine
# https://waline.js.org/
waline:
serverURL: # Waline server address url
bg: # waline background
pageview: false
option:
# utterances
# https://utteranc.es/
utterances:
repo:
# Issue Mapping: pathname/url/title/og:title
issue_term: pathname
# Theme: github-light/github-dark/github-dark-orange/icy-dark/dark-blue/photon-dark
light_theme: github-light
dark_theme: photon-dark
# Facebook Comments Plugin
# https://developers.facebook.com/docs/plugins/comments/
facebook_comments:
app_id:
user_id: # optional
pageSize: 10 # The number of comments to show
order_by: social # social/time/reverse_time
lang: zh_TW # Language en_US/zh_CN/zh_TW and so on
# Twikoo
# https://github.com/imaegoo/twikoo
twikoo:
envId:
region:
visitor: false
option:
# Giscus
# https://giscus.app/
giscus:
repo:
repo_id:
category_id:
theme:
light: light
dark: dark
option:
# Remark42
# https://remark42.com/docs/configuration/frontend/
remark42:
host: # Your Host URL
siteId: # Your Site ID
option:
# Artalk
# https://artalk.js.org/guide/frontend/config.html
artalk:
server:
site:
visitor: false
option:
# Chat Services
# --------------------------------------
# Chat Button [recommend]
# It will create a button in the bottom right corner of website, and hide the origin button
chat_btn: false
# The origin chat button is displayed when scrolling up, and the button is hidden when scrolling down
chat_hide_show: false
# chatra
# https://chatra.io/
chatra:
enable: false
id:
# tidio
# https://www.tidio.com/
tidio:
enable: false
public_key:
# daovoice
# http://dashboard.daovoice.io/app
daovoice:
enable: false
app_id:
# crisp
# https://crisp.chat/en/
crisp:
enable: false
website_id:
# messenger
# https://developers.facebook.com/docs/messenger-platform/discovery/facebook-chat-plugin/
messenger:
enable: false
pageID:
lang: zh_TW # Language en_US/zh_CN/zh_TW and so on
# Analysis
# --------------------------------------
# Baidu Analytics
# https://tongji.baidu.com/web/welcome/login
baidu_analytics:
# Google Analytics
# https://analytics.google.com/analytics/web/
google_analytics:
# Cloudflare Analytics
# https://www.cloudflare.com/zh-tw/web-analytics/
cloudflare_analytics:
# Microsoft Clarity
# https://clarity.microsoft.com/
microsoft_clarity:
# Advertisement
# --------------------------------------
# Google Adsense (谷歌廣告)
google_adsense:
enable: false
auto_ads: true
js: https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js
client:
enable_page_level_ads: true
# Insert ads manually (手動插入廣告)
# ad:
# index:
# aside:
# post:
# Verification (站長驗證)
# --------------------------------------
site_verification:
# - name: google-site-verification
# content: xxxxxx
# - name: baidu-site-verification
# content: xxxxxxx
# Beautify/Effect (美化/效果)
# --------------------------------------
# Theme color for customize
# Notice: color value must in double quotes like "#000" or may cause error!
# theme_color:
# enable: true
# main: "#49B1F5"
# paginator: "#00c4b6"
# button_hover: "#FF7242"
# text_selection: "#00c4b6"
# link_color: "#99a9bf"
# meta_color: "#858585"
# hr_color: "#A4D8FA"
# code_foreground: "#F47466"
# code_background: "rgba(27, 31, 35, .05)"
# toc_color: "#00c4b6"
# blockquote_padding_color: "#49b1f5"
# blockquote_background_color: "#49b1f5"
# scrollbar_color: "#49b1f5"
# meta_theme_color_light: "ffffff"
# meta_theme_color_dark: "#0d0d0d"
# The top_img settings of home page
# default: top img - full screen, site info - middle (默認top_img全屏,site_info在中間)
# The position of site info, eg: 300px/300em/300rem/10% (主頁標題距離頂部距離)
index_site_info_top:
# The height of top_img, eg: 300px/300em/300rem (主頁top_img高度)
index_top_img_height:
# The user interface setting of category and tag page (category和tag頁的UI設置)
# index - same as Homepage UI (index 值代表 UI將與首頁的UI一樣)
# default - same as archives UI 默認跟archives頁面UI一樣
category_ui: # 留空或 index
tag_ui: # 留空或 index
# Stretches the lines so that each line has equal width(文字向兩側對齊,對最後一行無效)
text_align_justify: false
# Website Background (設置網站背景)
# can set it to color or image (可設置圖片 或者 顔色)
# The formal of image: url(http://xxxxxx.com/xxx.jpg)
background:
# Footer Background
footer_bg: false
# Add mask to header or footer (为 header 或 footer 添加黑色半透遮罩)
mask:
header: true
footer: true
# the position of bottom right button/default unit: px (右下角按鈕距離底部的距離/默認單位為px)
rightside_bottom:
# Enter transitions (開啓網頁進入效果)
enter_transitions: true
# Typewriter Effect (打字效果)
# https://github.com/disjukr/activate-power-mode
activate_power_mode:
enable: false
colorful: true # open particle animation (冒光特效)
shake: true # open shake (抖動特效)
mobile: false
# Background effects (背景特效)
# --------------------------------------
# canvas_ribbon (靜止彩帶背景)
# See: https://github.com/hustcc/ribbon.js
canvas_ribbon:
enable: false
size: 150
alpha: 0.6
zIndex: -1
click_to_change: false
mobile: false
# Fluttering Ribbon (動態彩帶)
canvas_fluttering_ribbon:
enable: false
mobile: false
# canvas_nest
# https://github.com/hustcc/canvas-nest.js
canvas_nest:
enable: false
color: '0,0,255' #color of lines, default: '0,0,0'; RGB values: (R,G,B).(note: use ',' to separate.)
opacity: 0.7 # the opacity of line (0~1), default: 0.5.
zIndex: -1 # z-index property of the background, default: -1.
count: 99 # the number of lines, default: 99.
mobile: false
# Mouse click effects: fireworks (鼠標點擊效果: 煙火特效)
fireworks:
enable: false
zIndex: 9999 # -1 or 9999
mobile: false
# Mouse click effects: Heart symbol (鼠標點擊效果: 愛心)
click_heart:
enable: false
mobile: false
# Mouse click effects: words (鼠標點擊效果: 文字)
clickShowText:
enable: false
text:
# - I
# - LOVE
# - YOU
fontSize: 15px
random: false
mobile: false
# Default display mode (網站默認的顯示模式)
# light (default) / dark
display_mode: light
# Beautify (美化頁面顯示)
beautify:
enable: false
field: post # site/post
title-prefix-icon: # '\f0c1'
title-prefix-icon-color: # '#F47466'
# Global font settings
# Don't modify the following settings unless you know how they work (非必要不要修改)
font:
global-font-size:
code-font-size:
font-family:
code-font-family:
# Font settings for the site title and site subtitle
# 左上角網站名字 主頁居中網站名字
blog_title_font:
font_link:
font-family:
# The setting of divider icon (水平分隔線圖標設置)
hr_icon:
enable: true
icon: # the unicode value of Font Awesome icon, such as '\3423'
icon-top:
# the subtitle on homepage (主頁subtitle)
subtitle:
enable: false
# Typewriter Effect (打字效果)
effect: true
# Customize typed.js (配置typed.js)
# https://github.com/mattboldt/typed.js/#customization
typed_option:
# source 調用第三方服務
# source: false 關閉調用
# source: 1 調用一言網的一句話(簡體) https://hitokoto.cn/
# source: 2 調用一句網(簡體) https://yijuzhan.com/
# source: 3 調用今日詩詞(簡體) https://www.jinrishici.com/
# subtitle 會先顯示 source , 再顯示 sub 的內容
source: false
# 如果關閉打字效果,subtitle 只會顯示 sub 的第一行文字
sub:
# Loading Animation (加載動畫)
preloader:
enable: false
# source
# 1. fullpage-loading
# 2. pace (progress bar)
source: 1
# pace theme (see https://codebyzach.github.io/pace/)
pace_css_url:
# wordcount (字數統計)
# see https://butterfly.js.org/posts/ceeb73f/#字數統計
wordcount:
enable: false
post_wordcount: true
min2read: true
total_wordcount: true
# Lightbox (圖片大圖查看模式)
# --------------------------------------
# You can only choose one, or neither (只能選擇一個 或者 兩個都不選)
# medium-zoom
# https://github.com/francoischalifour/medium-zoom
medium_zoom: false
# fancybox
# https://fancyapps.com/fancybox/
fancybox: true
# Tag Plugins settings (標籤外掛)
# --------------------------------------
# series (系列文章)
series:
enable: true
orderBy: 'title' # Order by title or date
order: 1 # Sort of order. 1, asc for ascending; -1, desc for descending
number: true
# abcjs (樂譜渲染)
# See https://github.com/paulrosen/abcjs
abcjs:
enable: false
per_page: true
# mermaid
# see https://github.com/mermaid-js/mermaid
mermaid:
enable: false
# built-in themes: default/forest/dark/neutral
theme:
light: default
dark: dark
# Note (Bootstrap Callout)
note:
# Note tag style values:
# - simple bs-callout old alert style. Default.
# - modern bs-callout new (v2-v3) alert style.
# - flat flat callout style with background, like on Mozilla or StackOverflow.
# - disabled disable all CSS styles import of note tag.
style: flat
icons: true
border_radius: 3
# Offset lighter of background in % for modern and flat styles (modern: -12 | 12; flat: -18 | 6).
# Offset also applied to label tag variables. This option can work with disabled note tag.
light_bg_offset: 0
# other
# --------------------------------------
# Pjax
# It may contain bugs and unstable, give feedback when you find the bugs.
# https://github.com/MoOx/pjax
pjax:
enable: false
exclude:
# - xxxx
# - xxxx
# Inject the css and script (aplayer/meting)
aplayerInject:
enable: false
per_page: true
# Snackbar (Toast Notification 彈窗)
# https://github.com/polonel/SnackBar
# position 彈窗位置
# 可選 top-left / top-center / top-right / bottom-left / bottom-center / bottom-right
snackbar:
enable: false
position: bottom-left
bg_light: '#49b1f5' # The background color of Toast Notification in light mode
bg_dark: '#1f1f1f' # The background color of Toast Notification in dark mode
# https://instant.page/
# prefetch (預加載)
instantpage: false
# https://github.com/vinta/pangu.js
# Insert a space between Chinese character and English character (中英文之間添加空格)
pangu:
enable: false
field: site # site/post
# Lazyload (圖片懶加載)
# https://github.com/verlok/vanilla-lazyload
lazyload:
enable: false
field: site # site/post
placeholder:
blur: false
# PWA
# See https://github.com/JLHwung/hexo-offline
# ---------------
# pwa:
# enable: false
# manifest: /pwa/manifest.json
# apple_touch_icon: /pwa/apple-touch-icon.png
# favicon_32_32: /pwa/32.png
# favicon_16_16: /pwa/16.png
# mask_icon: /pwa/safari-pinned-tab.svg
# Open graph meta tags
# https://developers.facebook.com/docs/sharing/webmasters/
Open_Graph_meta:
enable: true
option:
# twitter_card:
# twitter_image:
# twitter_id:
# twitter_site:
# google_plus:
# fb_admins:
# fb_app_id:
# Add the vendor prefixes to ensure compatibility
css_prefix: true
# Inject
# Insert the code to head (before '</head>' tag) and the bottom (before '</body>' tag)
# 插入代码到头部 </head> 之前 和 底部 </body> 之前
inject:
head:
# - <link rel="stylesheet" href="/xxx.css">
bottom:
# - <script src="xxxx"></script>
# CDN
# Don't modify the following settings unless you know how they work
# 非必要請不要修改
CDN:
# The CDN provider of internal scripts (主題內部 js 的 cdn 配置)
# option: local/jsdelivr/unpkg/cdnjs/custom
# Dev version can only choose. ( dev版的主題只能設置為 local )
internal_provider: local
# The CDN provider of third party scripts (第三方 js 的 cdn 配置)
# option: local/jsdelivr/unpkg/cdnjs/custom
# when set it to local, you need to install hexo-butterfly-extjs
third_party_provider: jsdelivr
# Add version number to url, true or false
version: true
# Custom format
# For example: https://cdn.staticfile.org/${cdnjs_name}/${version}/${min_cdnjs_file}
custom_format:
option:
# abcjs_basic_js:
# activate_power_mode:
# algolia_js:
# algolia_search:
# aplayer_css:
# aplayer_js:
# artalk_css:
# artalk_js:
# blueimp_md5:
# busuanzi:
# canvas_fluttering_ribbon:
# canvas_nest:
# canvas_ribbon:
# click_heart:
# clickShowText:
# disqusjs:
# disqusjs_css:
# docsearch_css:
# docsearch_js:
# egjs_infinitegrid:
# fancybox:
# fancybox_css:
# fireworks:
# fontawesome:
# gitalk:
# gitalk_css:
# giscus:
# instantpage:
# instantsearch:
# katex:
# katex_copytex:
# lazyload:
# local_search:
# main:
# main_css:
# mathjax:
# medium_zoom:
# mermaid:
# meting_js:
# pangu:
# prismjs_autoloader:
# prismjs_js:
# prismjs_lineNumber_js:
# pjax:
# sharejs:
# sharejs_css:
# snackbar:
# snackbar_css:
# translate:
# twikoo:
# typed:
# utils:
# valine:
# waline_css:
# waline_js:

@ -0,0 +1,123 @@
footer:
framework: Framework
theme: Theme
copy:
success: Copy Successful
error: Copy Error
noSupport: Browser Not Supported
page:
articles: Articles
tag: Tag
category: Category
archives: Archives
card_post_count: comments
no_title: Untitled
post:
created: Created
updated: Updated
wordcount: Word Count
min2read: Reading Time
min2read_unit: mins
page_pv: Post Views
comments: Comments
copyright:
author: Author
link: Link
copyright_notice: Copyright Notice
copyright_content: 'All articles in this blog are licensed under <a href="%s">%s</a> unless stating additionally.'
recommend: Related Articles
edit: Edited on
search:
title: Search
load_data: Loading the Database
algolia_search:
input_placeholder: Search for Posts
hits_empty: "We didn't find any results for the search: ${query}."
hits_stats: '${hits} results found in ${time} ms'
local_search:
input_placeholder: Search for Posts
hits_empty: "We didn't find any results for the search: ${query}"
hits_stats: '${hits} results found'
pagination:
prev: Previous
next: Next
comment: Comment
aside:
articles: Articles
tags: Tags
categories: Categories
card_announcement: Announcement
card_categories: Categories
card_tags: Tags
card_archives: Archives
card_recent_post: Recent Post
card_webinfo:
headline: Info
article_name: Article
runtime:
name: Runtime
unit: days
last_push_date:
name: Last Update
site_wordcount: Total Count
site_uv_name: UV
site_pv_name: PV
more_button: View More
card_newest_comments:
headline: Latest Comments
loading_text: loading...
error: Unable to retrieve comments, please check the configuration
zero: No comments
image: image
link: link
code: code
card_toc: Contents
card_post_series: Series
date_suffix:
just: Just now
min: minutes ago
hour: hours ago
day: days ago
month: months ago
donate: Sponsor
share: Share
rightside:
readmode_title: Read Mode
translate_title: Toggle Between Traditional Chinese And Simplified Chinese
night_mode_title: Toggle Between Light And Dark Mode
back_to_top: Back To Top
toc: Table Of Contents
scroll_to_comment: Scroll To Comments
setting: Setting
aside: Toggle between Single-column and Double-column
chat: Chat
copy_copyright:
author: Author
link: Link
source: Source
info: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.
Snackbar:
chs_to_cht: You have switched to Traditional Chinese
cht_to_chs: You have switched to Simplified Chinese
day_to_night: You have switched to Dark Mode
night_to_day: You have switched to Light Mode
loading: Loading...
load_more: Load More
error404: Page Not Found

@ -0,0 +1,123 @@
footer:
framework: Framework
theme: Theme
copy:
success: Copy Successful
error: Copy Error
noSupport: Browser Not Supported
page:
articles: Articles
tag: Tag
category: Category
archives: Archives
card_post_count: comments
no_title: Untitled
post:
created: Created
updated: Updated
wordcount: Word Count
min2read: Reading Time
min2read_unit: mins
page_pv: Post Views
comments: Comments
copyright:
author: Author
link: Link
copyright_notice: Copyright Notice
copyright_content: 'All articles in this blog are licensed under <a href="%s">%s</a> unless stating additionally.'
recommend: Related Articles
edit: Edited on
search:
title: Search
load_data: Loading the Database
algolia_search:
input_placeholder: Search for Posts
hits_empty: "We didn't find any results for the search: ${query}."
hits_stats: '${hits} results found in ${time} ms'
local_search:
input_placeholder: Search for Posts
hits_empty: "We didn't find any results for the search: ${query}"
hits_stats: '${hits} results found'
pagination:
prev: Previous
next: Next
comment: Comment
aside:
articles: Articles
tags: Tags
categories: Categories
card_announcement: Announcement
card_categories: Categories
card_tags: Tags
card_archives: Archives
card_recent_post: Recent Post
card_webinfo:
headline: Info
article_name: Article
runtime:
name: Runtime
unit: days
last_push_date:
name: Last Update
site_wordcount: Total Count
site_uv_name: UV
site_pv_name: PV
more_button: View More
card_newest_comments:
headline: Latest Comments
loading_text: loading...
error: Unable to retrieve comments, please check the configuration
zero: No comments
image: image
link: link
code: code
card_toc: Contents
card_post_series: Series
date_suffix:
just: Just now
min: minutes ago
hour: hours ago
day: days ago
month: months ago
donate: Sponsor
share: Share
rightside:
readmode_title: Read Mode
translate_title: Toggle Between Traditional Chinese And Simplified Chinese
night_mode_title: Toggle Between Light And Dark Mode
back_to_top: Back To Top
toc: Table Of Contents
scroll_to_comment: Scroll To Comments
setting: Setting
aside: Toggle between Single-column and Double-column
chat: Chat
copy_copyright:
author: Author
link: Link
source: Source
info: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.
Snackbar:
chs_to_cht: You have switched to Traditional Chinese
cht_to_chs: You have switched to Simplified Chinese
day_to_night: You have switched to Dark Mode
night_to_day: You have switched to Light Mode
loading: Loading...
load_more: Load More
error404: Page Not Found

@ -0,0 +1,124 @@
footer:
framework: 框架
theme: 主题
copy:
success: 复制成功
error: 复制错误
noSupport: 浏览器不支持
page:
articles: 文章总览
tag: 标签
category: 分类
archives: 归档
card_post_count: 条评论
no_title: 无题
post:
created: 发表于
updated: 更新于
wordcount: 字数总计
min2read: 阅读时长
min2read_unit: 分钟
page_pv: 阅读量
comments: 评论数
copyright:
author: 文章作者
link: 文章链接
copyright_notice: 版权声明
copyright_content: '本博客所有文章除特别声明外,均采用
<a href="%s" target="_blank">%s</a> 许可协议。转载请注明来自 <a href="%s" target="_blank">%s</a>!'
recommend: 相关推荐
edit: 编辑
search:
title: 搜索
load_data: 数据库加载中
algolia_search:
input_placeholder: 搜索文章
hits_empty: '找不到您查询的内容:${query}'
hits_stats: '找到 ${hits} 条结果,用时 ${time} 毫秒'
local_search:
input_placeholder: 搜索文章
hits_empty: '找不到您查询的内容:${query}'
hits_stats: '共找到 ${hits} 篇文章'
pagination:
prev: 上一篇
next: 下一篇
comment: 评论
aside:
articles: 文章
tags: 标签
categories: 分类
card_announcement: 公告
card_categories: 分类
card_tags: 标签
card_archives: 归档
card_recent_post: 最新文章
card_webinfo:
headline: 网站资讯
article_name: 文章数目
runtime:
name: 已运行时间
unit:
last_push_date:
name: 最后更新时间
site_wordcount: 本站总字数
site_uv_name: 本站访客数
site_pv_name: 本站总访问量
more_button: 查看更多
card_newest_comments:
headline: 最新评论
loading_text: 正在加载中...
error: 无法获取评论,请确认相关配置是否正确
zero: 没有评论
image: 图片
link: 链接
code: 代码
card_toc: 目录
card_post_series: 系列文章
date_suffix:
just: 刚刚
min: 分钟前
hour: 小时前
day: 天前
month: 个月前
donate: 赞助
share: 分享
rightside:
readmode_title: 阅读模式
translate_title: 简繁转换
night_mode_title: 浅色和深色模式转换
back_to_top: 回到顶部
toc: 目录
scroll_to_comment: 直达评论
setting: 设置
aside: 单栏和双栏切换
chat: 聊天
copy_copyright:
author: 作者
link: 链接
source: 来源
info: 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
Snackbar:
chs_to_cht: 你已切换为繁体中文
cht_to_chs: 你已切换为简体中文
day_to_night: 你已切换为深色模式
night_to_day: 你已切换为浅色模式
loading: 加载中...
load_more: 加载更多
error404: 页面没有找到

@ -0,0 +1,124 @@
footer:
framework: 框架
theme: 主題
copy:
success: 複製成功
error: 複製錯誤
noSupport: 瀏覽器不支援
page:
articles: 文章總覽
tag: 標籤
category: 分類
archives: 歸檔
card_post_count: 條評論
no_title: 無標題
post:
created: 發表於
updated: 更新於
wordcount: 字數總計
min2read: 閱讀時長
min2read_unit: 分鐘
page_pv: 閱讀量
comments: 評論數
copyright:
author: 文章作者
link: 文章連結
copyright_notice: 版權聲明
copyright_content: '本部落格所有文章除特別聲明外,均採用
<a href="%s" target="_blank">%s</a> 許可協議。轉載請註明來自 <a href="%s" target="_blank">%s</a>!'
recommend: 相關推薦
edit: 編輯
search:
title: 搜尋
load_data: 資料庫載入中
algolia_search:
input_placeholder: 搜尋文章
hits_empty: '找不到您查詢的內容:${query}'
hits_stats: '找到 ${hits} 條結果,用時 ${time} 毫秒'
local_search:
input_placeholder: 搜尋文章
hits_empty: '找不到您查詢的內容:${query}'
hits_stats: '共找到 ${hits} 篇文章'
pagination:
prev: 上一篇
next: 下一篇
comment: 評論
aside:
articles: 文章
tags: 標籤
categories: 分類
card_announcement: 公告
card_categories: 分類
card_tags: 標籤
card_archives: 歸檔
card_recent_post: 最新文章
card_webinfo:
headline: 網站資訊
article_name: 文章數目
runtime:
name: 已執行時間
unit:
last_push_date:
name: 最後更新時間
site_wordcount: 本站總字數
site_uv_name: 本站訪客數
site_pv_name: 本站總訪問量
more_button: 檢視更多
card_newest_comments:
headline: 最新評論
loading_text: 正在載入中...
error: 無法獲取評論,請確認相關配置是否正確
zero: 沒有評論
image: 圖片
link: 連結
code: 程式碼
card_toc: 目錄
card_post_series: 文章系列
date_suffix:
just: 剛剛
min: 分鐘前
hour: 小時前
day: 天前
month: 個月前
donate: 贊助
share: 分享
rightside:
readmode_title: 閱讀模式
translate_title: 簡繁轉換
night_mode_title: 淺色和深色模式轉換
back_to_top: 返回頂部
toc: 目錄
scroll_to_comment: 直達評論
setting: 設定
aside: 單欄和雙欄切換
chat: 聊天
copy_copyright:
author: 作者
link: 連結
source: 來源
info: 著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。
Snackbar:
chs_to_cht: 你已切換為繁體中文
cht_to_chs: 你已切換為簡體中文
day_to_night: 你已切換為深色模式
night_to_day: 你已切換為淺色模式
loading: 載入中...
load_more: 載入更多
error404: 頁面沒有找到

@ -0,0 +1,8 @@
extends includes/layout.pug
block content
include ./includes/mixins/article-sort.pug
#archive
.article-sort-title= `${_p('page.articles')} - ${getArchiveLength()}`
+articleSort(page.posts)
include includes/pagination.pug

@ -0,0 +1,14 @@
extends includes/layout.pug
block content
if theme.category_ui == 'index'
include ./includes/mixins/post-ui.pug
#recent-posts.recent-posts.category_ui
+postUI
include includes/pagination.pug
else
include ./includes/mixins/article-sort.pug
#category
.article-sort-title= _p('page.category') + ' - ' + page.category
+articleSort(page.posts)
include includes/pagination.pug

@ -0,0 +1,12 @@
- var top_img_404 = theme.error_404.background || theme.default_top_img
#body-wrap.error404
include ./header/index.pug
#error-wrap
.error-content
.error-img
img(src=url_for(top_img_404) alt='Page not found')
.error-info
h1.error_title= '404'
.error_subtitle= theme.error_404.subtitle || _p('error404')

@ -0,0 +1,65 @@
div
script(src=url_for(theme.asset.utils))
script(src=url_for(theme.asset.main))
if theme.translate.enable
script(src=url_for(theme.asset.translate))
if theme.medium_zoom
script(src=url_for(theme.asset.medium_zoom))
else if theme.fancybox
script(src=url_for(theme.asset.fancybox))
if theme.instantpage
script(src=url_for(theme.asset.instantpage), type='module')
if theme.lazyload.enable
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 })
if loadSubJs
include ./third-party/subtitle.pug
include ./third-party/math/index.pug
include ./third-party/abcjs/index.pug
if commentsJsLoad
include ./third-party/comments/js.pug
!= partial("includes/third-party/prismjs", {}, { cache: true })
if theme.aside.enable && theme.newest_comments.enable
if theme.pjax.enable
!= partial("includes/third-party/newest-comments/index", {}, { cache: true })
else if (!is_post() && page.aside !== false)
!= partial("includes/third-party/newest-comments/index", {}, { cache: true })
!= fragment_cache('injectBottom', function(){return injectHtml(theme.inject.bottom)})
!= partial("includes/third-party/effect", {}, { cache: true })
!= partial("includes/third-party/chat/index", {}, { cache: true })
if theme.aplayerInject && theme.aplayerInject.enable
if theme.pjax.enable || theme.aplayerInject.per_page
include ./third-party/aplayer.pug
else if page.aplayer
include ./third-party/aplayer.pug
if theme.pjax.enable
!= partial("includes/third-party/pjax", {}, { 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')
!=partial('includes/third-party/search/index', {}, {cache: true})

@ -0,0 +1,17 @@
#footer-wrap
if theme.footer.owner.enable
- var now = new Date()
- var nowYear = now.getFullYear()
if theme.footer.owner.since && theme.footer.owner.since != nowYear
.copyright!= `&copy;${theme.footer.owner.since} - ${nowYear} By ${config.author}`
else
.copyright!= `&copy;${nowYear} By ${config.author}`
if theme.footer.copyright
.framework-info
span= _p('footer.framework') + ' '
a(href='https://hexo.io')= 'Hexo'
span.footer-separator |
span= _p('footer.theme') + ' '
a(href='https://github.com/jerryc127/hexo-theme-butterfly')= 'Butterfly'
if theme.footer.custom_text
.footer_custom_text!=`${theme.footer.custom_text}`

@ -0,0 +1,68 @@
- 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 || ''
- var isSubtitle = config.subtitle ? ' - ' + config.subtitle : ''
- var tabTitle = is_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'
- var themeColorDark = theme.theme_color && theme.theme_color.enable && theme.theme_color.meta_theme_color_dark || '#0d0d0d'
- var themeColor = theme.display_mode === 'dark' ? themeColorDark : themeColorLight
meta(charset='UTF-8')
meta(http-equiv="X-UA-Compatible" content="IE=edge")
meta(name="viewport" content="width=device-width, initial-scale=1.0,viewport-fit=cover")
title= tabTitle
meta(name="author" content=pageAuthor)
meta(name="copyright" content=pageCopyright)
meta(name ="format-detection" content="telephone=no")
meta(name="theme-color" content=themeColor)
//- Open_Graph
include ./head/Open_Graph.pug
!=favicon_tag(theme.favicon || config.favicon)
link(rel="canonical" href=urlNoIndex(null,config.pretty_urls.trailing_index,config.pretty_urls.trailing_html))
//- 預解析
!=partial('includes/head/preconnect', {}, {cache: true})
//- 網站驗證
!=partial('includes/head/site_verification', {}, {cache: true})
//- PWA
if (theme.pwa && theme.pwa.enable)
!=partial('includes/head/pwa', {}, {cache: true})
//- main css
link(rel='stylesheet', href=url_for(theme.asset.main_css))
link(rel='stylesheet', href=url_for(theme.asset.fontawesome))
if (theme.snackbar && theme.snackbar.enable)
link(rel='stylesheet', href=url_for(theme.asset.snackbar_css) media="print" onload="this.media='all'")
if theme.fancybox
link(rel='stylesheet' href=url_for(theme.asset.fancybox_css) media="print" onload="this.media='all'")
//- google_adsense
!=partial('includes/head/google_adsense', {}, {cache: true})
//- analytics
!=partial('includes/head/analytics', {}, {cache: true})
//- font
if theme.blog_title_font && theme.blog_title_font.font_link
link(rel='stylesheet' href=url_for(theme.blog_title_font.font_link) media="print" onload="this.media='all'")
//- global config
!=partial('includes/head/config', {}, {cache: true})
include ./head/config_site.pug
!=fragment_cache('injectHeadJs', function(){return inject_head_js()})
!=fragment_cache('injectHead', function(){return injectHtml(theme.inject.head)})

@ -0,0 +1,14 @@
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',
image: coverVal ? full_url_for(coverVal) : '',
fb_admins: theme.facebook_comments.user_id || '',
fb_app_id: theme.facebook_comments.app_id || '',
}, theme.Open_Graph_meta.option)
-
!= open_graph(ogOption)
else
meta(name="description" content=page_description())

@ -0,0 +1,28 @@
if theme.baidu_analytics
script.
var _hmt = _hmt || [];
(function() {
var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?!{theme.baidu_analytics}";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
if theme.google_analytics
script(async src=`https://www.googletagmanager.com/gtag/js?id=${theme.google_analytics}`)
script.
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '!{theme.google_analytics}');
if theme.cloudflare_analytics
script(defer data-pjax src='https://static.cloudflareinsights.com/beacon.min.js' data-cf-beacon=`{"token": "${theme.cloudflare_analytics}"}`)
if theme.microsoft_clarity
script.
(function(c,l,a,r,i,t,y){
c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};
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}");

@ -0,0 +1,132 @@
-
let algolia = 'undefined';
let env = process.env;
if (theme.algolia_search.enable) {
algolia = JSON.stringify({
appId: env.ALGOLIA_APP_ID || config.algolia.appId || config.algolia.applicationID,
apiKey: env.ALGOLIA_API_KEY || config.algolia.apiKey,
indexName: env.ALGOLIA_INDEX_NAME || config.algolia.indexName,
hits: theme.algolia_search.hits,
// search languages
languages: {
input_placeholder: _p("search.algolia_search.input_placeholder"),
hits_empty: _p("search.algolia_search.hits_empty"),
hits_stats: _p("search.algolia_search.hits_stats"),
}
})
}
let localSearch = 'undefined';
if (theme.local_search && theme.local_search.enable) {
localSearch = JSON.stringify({
path: theme.local_search.CDN ? theme.local_search.CDN : config.root + config.search.path,
preload: theme.local_search.preload,
top_n_per_article: theme.local_search.top_n_per_article,
unescape: theme.local_search.unescape,
languages: {
// search languages
hits_empty: _p("search.local_search.hits_empty"),
hits_stats: _p("search.local_search.hits_stats"),
}
})
}
let translate = 'undefined';
if (theme.translate && theme.translate.enable){
translate = JSON.stringify({
defaultEncoding: theme.translate.defaultEncoding,
translateDelay: theme.translate.translateDelay,
msgToTraditionalChinese: theme.translate.msgToTraditionalChinese,
msgToSimplifiedChinese: theme.translate.msgToSimplifiedChinese
})
}
let copyright = 'undefined';
if (theme.copy.enable && theme.copy.copyright.enable){
copyright = JSON.stringify({
limitCount: theme.copy.copyright.limit_count,
languages: {
author: _p("copy_copyright.author") + ': ' + config.author,
link: _p("copy_copyright.link") + ': ',
source: _p("copy_copyright.source") + ': ' + config.title,
info: _p("copy_copyright.info")
}
})
}
let Snackbar = 'undefined';
if (theme.snackbar && theme.snackbar.enable) {
Snackbar = JSON.stringify({
chs_to_cht: _p("Snackbar.chs_to_cht"),
cht_to_chs: _p("Snackbar.cht_to_chs"),
day_to_night: _p("Snackbar.day_to_night"),
night_to_day: _p("Snackbar.night_to_day"),
bgLight: theme.snackbar.bg_light,
bgDark: theme.snackbar.bg_dark,
position: theme.snackbar.position,
})
}
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) {
highlight = JSON.stringify({
plugin: syntaxHighlighter ? syntaxHighlighter : config.highlight.enable ? 'highlight.js' : 'prismjs',
highlightCopy: theme.highlight_copy,
highlightLang: theme.highlight_lang,
highlightHeightLimit: theme.highlight_height_limit
})
}
script.
const GLOBAL_CONFIG = {
root: '!{config.root}',
algolia: !{algolia},
localSearch: !{localSearch},
translate: !{translate},
noticeOutdate: !{noticeOutdate},
highlight: !{highlight},
copy: {
success: '!{_p("copy.success")}',
error: '!{_p("copy.error")}',
noSupport: '!{_p("copy.noSupport")}'
},
relativeDate: {
homepage: !{theme.post_meta.page.date_format === 'relative'},
post: !{theme.post_meta.post.date_format === 'relative'}
},
runtime: '!{theme.runtimeshow.enable ? _p("aside.card_webinfo.runtime.unit") : ""}',
dateSuffix: {
just: '!{_p("date_suffix.just")}',
min: '!{_p("date_suffix.min")}',
hour: '!{_p("date_suffix.hour")}',
day: '!{_p("date_suffix.day")}',
month: '!{_p("date_suffix.month")}'
},
copyright: !{copyright},
lightbox: '!{ theme.medium_zoom ? "mediumZoom" : (theme.fancybox ? "fancybox" : "null" )}',
Snackbar: !{Snackbar},
infinitegrid: {
js: '!{url_for(theme.asset.egjs_infinitegrid)}',
buttonText: '!{_p("load_more")}'
},
isPhotoFigcaption: !{theme.photofigcaption},
islazyload: !{theme.lazyload.enable},
isAnchor: !{theme.anchor.auto_update || false},
percent: {
toc: !{theme.toc.scroll_percent},
rightside: !{theme.rightside_scroll_percent},
},
autoDarkmode: !{theme.darkmode.enable && theme.darkmode.autoChangeMode === 1}
}

@ -0,0 +1,30 @@
-
const titleVal = pageTitle.replace(/'/ig,"\\'")
let isHighlightShrink
if (theme.highlight_shrink == 'none') isHighlightShrink = 'undefined'
else if (page.highlight_shrink === true || page.highlight_shrink === false) isHighlightShrink = page.highlight_shrink
else isHighlightShrink = theme.highlight_shrink
var showToc = false
if (theme.aside.enable && page.aside !== false) {
let tocEnable = false
if (is_post()) {
if (theme.toc.post) tocEnable = true
} else if (is_page()) {
if (theme.toc.page) tocEnable = true
}
const pageToc = page.toc === true || page.toc === false ? page.toc : tocEnable
showToc = pageToc && (toc(page.content) !== '' || page.encrypt == true )
}
-
script#config-diff.
var GLOBAL_CONFIG_SITE = {
title: '!{titleVal}',
isPost: !{is_post()},
isHome: !{is_home()},
isHighlightShrink: !{isHighlightShrink},
isToc: !{showToc},
postUpdate: '!{full_date(page.updated)}'
}

@ -0,0 +1,9 @@
if (theme.google_adsense && theme.google_adsense.enable)
script(async src=theme.google_adsense.js)
if theme.google_adsense.auto_ads
script.
(adsbygoogle = window.adsbygoogle || []).push({
google_ad_client: '!{theme.google_adsense.client}',
enable_page_level_ads: '!{theme.google_adsense.enable_page_level_ads}'
});

@ -0,0 +1,35 @@
-
const { internal_provider, third_party_provider, custom_format } = theme.CDN
const providers = {
'jsdelivr': '//cdn.jsdelivr.net',
'cdnjs': '//cdnjs.cloudflare.com',
'unpkg': '//unpkg.com',
'custom': custom_format && custom_format.match(/^((https?:)?(\/\/[^/]+)|([^/]+))(\/|$)/)[1]
}
-
if internal_provider === third_party_provider && internal_provider !== 'local'
link(rel="preconnect" href=providers[internal_provider])
else
if internal_provider !== 'local'
link(rel="preconnect" href=providers[internal_provider])
if third_party_provider !== 'local'
link(rel="preconnect" href=providers[third_party_provider])
if theme.google_analytics
link(rel="preconnect" href="//www.google-analytics.com" crossorigin='')
if theme.baidu_analytics
link(rel="preconnect" href="//hm.baidu.com")
if theme.cloudflare_analytics
link(rel="preconnect" href="//static.cloudflareinsights.com")
if theme.microsoft_clarity
link(rel="preconnect" href="//www.clarity.ms")
if theme.blog_title_font && theme.blog_title_font.font_link && theme.blog_title_font.font_link.indexOf('//fonts.googleapis.com') != -1
link(rel="preconnect" href="//fonts.googleapis.com" crossorigin='')
if !theme.asset.busuanzi && (theme.busuanzi.site_uv || theme.busuanzi.site_pv || theme.busuanzi.page_pv)
link(rel="preconnect" href="//busuanzi.ibruce.info")

@ -0,0 +1,11 @@
link(rel="manifest" href=url_for(theme.pwa.manifest))
if(theme.pwa.theme_color)
meta(name="msapplication-TileColor" content=theme.pwa.theme_color)
if(theme.pwa.apple_touch_icon)
link(rel="apple-touch-icon" sizes="180x180" href=url_for(theme.pwa.apple_touch_icon))
if(theme.pwa.favicon_32_32)
link(rel="icon" type="image/png" sizes="32x32" href=url_for(theme.pwa.favicon_32_32))
if(theme.pwa.favicon_16_16)
link(rel="icon" type="image/png" sizes="16x16" href=url_for(theme.pwa.favicon_16_16))
if(theme.pwa.mask_icon)
link(rel="mask-icon" href=url_for(theme.pwa.mask_icon) color="#5bbad5")

@ -0,0 +1,3 @@
if theme.site_verification
each item in theme.site_verification
meta(name=item.name content=item.content)

@ -0,0 +1,52 @@
if !theme.disable_top_img && page.top_img !== false
if is_post()
- var top_img = page.top_img || page.cover || theme.default_top_img
else if is_page()
- var top_img = page.top_img || theme.default_top_img
else if is_tag()
- var top_img = theme.tag_per_img && theme.tag_per_img[page.tag]
- top_img = top_img ? top_img : (theme.tag_img !== false ? theme.tag_img || theme.default_top_img : false)
else if is_category()
- var top_img = theme.category_per_img && theme.category_per_img[page.category]
- top_img = top_img ? top_img : (theme.category_img !== false ? theme.category_img || theme.default_top_img : false)
else if is_home()
- var top_img = theme.index_img !== false ? theme.index_img || theme.default_top_img : false
else if is_archive()
- var top_img = theme.archive_img !== false ? theme.archive_img || theme.default_top_img : false
else
- var top_img = page.top_img || theme.default_top_img
if top_img !== false
- var imgSource = top_img && isImgOrUrl(top_img) ? `background-image: url('${url_for(top_img)}')` : `background: ${top_img}`
- var bg_img = top_img ? imgSource : ''
- var site_title = page.title || page.tag || page.category || config.title
- var isHomeClass = is_home() ? 'full_page' : 'not-home-page'
- is_post() ? isHomeClass = 'post-bg' : isHomeClass
else
- var isHomeClass = 'not-top-img'
else
- var top_img = false
- var isHomeClass = 'not-top-img'
- const isFixedClass = theme.nav.fixed ? ' fixed' : ''
header#page-header(class=`${isHomeClass+isFixedClass}` style=bg_img)
!=partial('includes/header/nav', {}, {cache: true})
if top_img !== false
if is_post()
include ./post-info.pug
else if is_home()
#site-info
h1#site-title=site_title
if theme.subtitle.enable
- var loadSubJs = true
#site-subtitle
span#subtitle
if(theme.social)
#site_social_icons
!=partial('includes/header/social', {}, {cache: true})
#scroll-down
i.fas.fa-angle-down.scroll-down-effects
else
#page-site-info
h1#site-title=site_title

@ -0,0 +1,27 @@
if theme.menu
.menus_items
each value, label in theme.menu
if typeof value !== 'object'
.menus_item
- const valueArray = value.split('||')
a.site-page(href=url_for(trim(valueArray[0])))
if valueArray[1]
i.fa-fw(class=trim(valueArray[1]))
span=' '+label
else
.menus_item
- const labelArray = label.split('||')
- const hideClass = labelArray[2] && trim(labelArray[2]) === 'hide' ? 'hide' : ''
a.site-page.group(class=`${hideClass}` href='javascript:void(0);')
if labelArray[1]
i.fa-fw(class=trim(labelArray[1]))
span=' '+ trim(labelArray[0])
i.fas.fa-chevron-down
ul.menus_item_child
each val,lab in value
- const valArray = val.split('||')
li
a.site-page.child(href=url_for(trim(valArray[0])))
if valArray[1]
i.fa-fw(class=trim(valArray[1]))
span=' '+ lab

@ -0,0 +1,21 @@
nav#nav
span#blog-info
a(href=url_for('/') title=config.title)
if theme.nav.logo
img.site-icon(src=url_for(theme.nav.logo))
if theme.nav.display_title
span.site-name=config.title
#menus
if (theme.algolia_search.enable || theme.local_search.enable || theme.docsearch.enable)
#search-button
a.site-page.social-icon.search(href="javascript:void(0);")
i.fas.fa-search.fa-fw
span=' '+_p('search.title')
!=partial('includes/header/menu_item', {}, {cache: true})
#toggle-menu
a.site-page(href="javascript:void(0);")
i.fas.fa-bars.fa-fw

@ -0,0 +1,144 @@
- let comments = theme.comments
#post-info
h1.post-title= page.title || _p('no_title')
if theme.post_edit.enable
a.post-edit-link(href=theme.post_edit.url + page.source title=_p('post.edit') target="_blank")
i.fas.fa-pencil-alt
#post-meta
.meta-firstline
if (theme.post_meta.post.date_type)
span.post-meta-date
if (theme.post_meta.post.date_type === 'both')
i.far.fa-calendar-alt.fa-fw.post-meta-icon
span.post-meta-label= _p('post.created')
time.post-meta-date-created(datetime=date_xml(page.date) title=_p('post.created') + ' ' + full_date(page.date))=date(page.date, config.date_format)
span.post-meta-separator |
i.fas.fa-history.fa-fw.post-meta-icon
span.post-meta-label= _p('post.updated')
time.post-meta-date-updated(datetime=date_xml(page.updated) title=_p('post.updated') + ' ' + full_date(page.updated))=date(page.updated, config.date_format)
else
- let data_type_update = theme.post_meta.post.date_type === 'updated'
- let date_type = data_type_update ? 'updated' : 'date'
- let date_icon = data_type_update ? 'fas fa-history' :'far fa-calendar-alt'
- let date_title = data_type_update ? _p('post.updated') : _p('post.created')
i.fa-fw.post-meta-icon(class=date_icon)
span.post-meta-label= date_title
time(datetime=date_xml(page[date_type]) title=date_title + ' ' + full_date(page[date_type]))=date(page[date_type], config.date_format)
if (theme.post_meta.post.categories && page.categories.data.length > 0)
span.post-meta-categories
if (theme.post_meta.post.date_type)
span.post-meta-separator |
each item, index in page.categories.data
i.fas.fa-inbox.fa-fw.post-meta-icon
a(href=url_for(item.path)).post-meta-categories #[=item.name]
if (index < page.categories.data.length - 1)
i.fas.fa-angle-right.post-meta-separator
.meta-secondline
- let postWordcount = theme.wordcount.enable && (theme.wordcount.post_wordcount || theme.wordcount.min2read)
if (postWordcount)
span.post-meta-separator |
span.post-meta-wordcount
if theme.wordcount.post_wordcount
i.far.fa-file-word.fa-fw.post-meta-icon
span.post-meta-label= _p('post.wordcount') + ':'
span.word-count= wordcount(page.content)
if theme.wordcount.min2read
span.post-meta-separator |
if theme.wordcount.min2read
i.far.fa-clock.fa-fw.post-meta-icon
span.post-meta-label= _p('post.min2read') + ':'
span= min2read(page.content, {cn: 350, en: 160}) + _p('post.min2read_unit')
//- for pv and count
mixin pvBlock(parent_id,parent_class,parent_title)
span.post-meta-separator |
span(class=parent_class id=parent_id data-flag-title=page.title)
i.far.fa-eye.fa-fw.post-meta-icon
span.post-meta-label=_p('post.page_pv') + ':'
if block
block
- const commentUse = comments.use
if page.comments !== false && commentUse && !comments.lazyload
if commentUse[0] === 'Valine' && theme.valine.visitor
+pvBlock(url_for(page.path),'leancloud_visitors',page.title)
span.leancloud-visitors-count
i.fa-solid.fa-spinner.fa-spin
else if commentUse[0] === 'Waline' && theme.waline.pageview
+pvBlock('','','')
span.waline-pageview-count(data-path=url_for(page.path))
i.fa-solid.fa-spinner.fa-spin
else if commentUse[0] === 'Twikoo' && theme.twikoo.visitor
+pvBlock('','','')
span#twikoo_visitors
i.fa-solid.fa-spinner.fa-spin
else if commentUse[0] === 'Artalk' && theme.artalk.visitor
+pvBlock('','','')
span#ArtalkPV
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.busuanzi.page_pv
+pvBlock('','post-meta-pv-cv','')
span#busuanzi_value_page_pv
i.fa-solid.fa-spinner.fa-spin
if comments.count && !comments.lazyload && page.comments !== false && comments.use
- var whichCount = comments.use[0]
mixin countBlock
span.post-meta-separator |
span.post-meta-commentcount
i.far.fa-comments.fa-fw.post-meta-icon
span.post-meta-label= _p('post.comments') + ':'
if block
block
case whichCount
when 'Disqus'
+countBlock
a.disqus-comment-count(href=full_url_for(page.path) + '#post-comment')
i.fa-solid.fa-spinner.fa-spin
when 'Disqusjs'
+countBlock
a.disqusjs-comment-count(href=full_url_for(page.path) + '#post-comment')
i.fa-solid.fa-spinner.fa-spin
when 'Valine'
+countBlock
a(href=url_for(page.path) + '#post-comment' itemprop="discussionUrl")
span.valine-comment-count(data-xid=url_for(page.path) itemprop="commentCount")
i.fa-solid.fa-spinner.fa-spin
when 'Waline'
+countBlock
a(href=url_for(page.path) + '#post-comment')
span.waline-comment-count(data-path=url_for(page.path))
i.fa-solid.fa-spinner.fa-spin
when 'Gitalk'
+countBlock
a(href=url_for(page.path) + '#post-comment')
span.gitalk-comment-count
i.fa-solid.fa-spinner.fa-spin
when 'Twikoo'
+countBlock
a(href=url_for(page.path) + '#post-comment')
span#twikoo-count
i.fa-solid.fa-spinner.fa-spin
when 'Facebook Comments'
+countBlock
a(href=url_for(page.path) + '#post-comment')
span.fb-comments-count(data-href=urlNoIndex())
when 'Remark42'
+countBlock
a(href=url_for(page.path) + '#post-comment')
span.remark42__counter(data-url=urlNoIndex())
i.fa-solid.fa-spinner.fa-spin
when 'Artalk'
+countBlock
a(href=url_for(page.path) + '#post-comment')
span.artalk-count
i.fa-solid.fa-spinner.fa-spin

@ -0,0 +1,4 @@
each url, icon in theme.social
a.social-icon(href=url_for(trim(url.split('||')[0])) target="_blank"
title=url.split('||')[1] === undefined ? '' : trim(url.split('||')[1]))
i(class=icon style=url.split('||')[2] === undefined ? '' : `color: ${trim(url.split('||')[2]).replace(/[\'\"]/g, '')};`)

@ -0,0 +1,47 @@
- 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
- var hideAside = !theme.aside.enable || page.aside === false ? 'hide-aside' : ''
- var pageType = is_post() ? 'post' : 'page'
doctype html
html(lang=config.language data-theme=theme.display_mode class=htmlClassHideAside)
head
include ./head.pug
body
if theme.preloader.enable
!=partial('includes/loading/index', {}, {cache: true})
if theme.background
#web_bg
!=partial('includes/sidebar', {}, {cache: true})
if page.type !== '404'
#body-wrap(class=pageType)
include ./header/index.pug
main#content-inner.layout(class=hideAside)
if body
div!= body
else
block content
if theme.aside.enable && page.aside !== false
include widget/index.pug
- var footerBg = theme.footer_bg
if (footerBg)
if (footerBg === true)
- var footer_bg = bg_img
else
- var footer_bg = isImgOrUrl(theme.footer_bg) ? `background-image: url('${url_for(footerBg)}')` : `background: ${footerBg}`
else
- var footer_bg = ''
footer#footer(style=footer_bg)
!=partial('includes/footer', {}, {cache: true})
else
include ./404.pug
include ./rightside.pug
include ./additional-js.pug

@ -0,0 +1,33 @@
#loading-box
.loading-left-bg
.loading-right-bg
.spinner-box
.configure-border-1
.configure-core
.configure-border-2
.configure-core
.loading-word= _p('loading')
script.
(()=>{
const $loadingBox = document.getElementById('loading-box')
const $body = document.body
const preloader = {
endLoading: () => {
$body.style.overflow = ''
$loadingBox.classList.add('loaded')
},
initLoading: () => {
$body.style.overflow = 'hidden'
$loadingBox.classList.remove('loaded')
}
}
preloader.initLoading()
window.addEventListener('load',() => { preloader.endLoading() })
if (!{theme.pjax && theme.pjax.enable}) {
document.addEventListener('pjax:send', () => { preloader.initLoading() })
document.addEventListener('pjax:complete', () => { preloader.endLoading() })
}
})()

@ -0,0 +1,4 @@
if theme.preloader.source === 1
include ./fullpage-loading.pug
else
include ./pace.pug

@ -0,0 +1,11 @@
script.
window.paceOptions = {
restartOnPushState: false
}
document.addEventListener('pjax:send', () => {
Pace.restart()
})
link(rel="stylesheet", href=url_for(theme.preloader.pace_css_url || theme.asset.pace_default_css))
script(src=url_for(theme.asset.pace_js))

@ -0,0 +1,23 @@
mixin articleSort(posts)
.article-sort
- var year
- posts.each(function (article) {
- let tempYear = date(article.date, 'YYYY')
- let no_cover = article.cover === false || !theme.cover.archives_enable ? 'no-article-cover' : ''
- let title = article.title || _p('no_title')
if tempYear !== year
- year = tempYear
.article-sort-item.year= year
.article-sort-item(class=no_cover)
if article.cover && theme.cover.archives_enable
a.article-sort-item-img(href=url_for(article.path) title=title)
if article.cover_type === 'img'
img(src=url_for(article.cover) alt=title onerror=`this.onerror=null;this.src='${url_for(theme.error_img.post_page)}'`)
else
div(style=`background: ${article.cover}`)
.article-sort-item-info
.article-sort-item-time
i.far.fa-calendar-alt
time.post-meta-date-created(datetime=date_xml(article.date) title=_p('post.created') + ' ' + full_date(article.date))= date(article.date, config.date_format)
a.article-sort-item-title(href=url_for(article.path) title=title)= title
- })

@ -0,0 +1,129 @@
mixin postUI(posts)
each article , index in page.posts.data
.recent-post-item
-
let link = article.link || article.path
let title = article.title || _p('no_title')
const position = theme.cover.position
let leftOrRight = position === 'both'
? index%2 == 0 ? 'left' : 'right'
: position === 'left' ? 'left' : 'right'
let post_cover = article.cover
let no_cover = article.cover === false || !theme.cover.index_enable ? 'no-cover' : ''
-
if post_cover && 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)
else
div.post-bg(style=`background: ${post_cover}`)
.recent-post-info(class=no_cover)
a.article-title(href=url_for(link) title=title)
if (is_home() && (article.top || article.sticky > 0))
i.fas.fa-thumbtack.sticky
= title
.article-meta-wrap
if (theme.post_meta.page.date_type)
span.post-meta-date
if (theme.post_meta.page.date_type === 'both')
i.far.fa-calendar-alt
span.article-meta-label=_p('post.created')
time.post-meta-date-created(datetime=date_xml(article.date) title=_p('post.created') + ' ' + full_date(article.date))=date(article.date, config.date_format)
span.article-meta-separator |
i.fas.fa-history
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
- let data_type_updated = theme.post_meta.page.date_type === 'updated'
- let date_type = data_type_updated ? 'updated' : 'date'
- let date_icon = data_type_updated ? 'fas fa-history' :'far fa-calendar-alt'
- let 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)
if (theme.post_meta.page.categories && article.categories.data.length > 0)
span.article-meta
span.article-meta-separator |
i.fas.fa-inbox
each item, index in article.categories.data
a(href=url_for(item.path)).article-meta__categories #[=item.name]
if (index < article.categories.data.length - 1)
i.fas.fa-angle-right.article-meta-link
if (theme.post_meta.page.tags && article.tags.data.length > 0)
span.article-meta.tags
span.article-meta-separator |
i.fas.fa-tag
each item, index in article.tags.data
a(href=url_for(item.path)).article-meta__tags #[=item.name]
if (index < article.tags.data.length - 1)
span.article-meta-link #[='•']
mixin countBlockInIndex
- needLoadCountJs = true
span.article-meta
span.article-meta-separator |
i.fas.fa-comments
if block
block
span.article-meta-label= ' ' + _p('card_post_count')
if theme.comments.card_post_count && theme.comments.use
case theme.comments.use[0]
when 'Disqus'
when 'Disqusjs'
+countBlockInIndex
a.disqus-count(href=full_url_for(link) + '#post-comment')
i.fa-solid.fa-spinner.fa-spin
when 'Valine'
+countBlockInIndex
a(href=url_for(link) + '#post-comment')
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')
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')
i.fa-solid.fa-spinner.fa-spin
when 'Facebook Comments'
+countBlockInIndex
a(href=url_for(link) + '#post-comment')
span.fb-comments-count(data-href=urlNoIndex(article.permalink))
when 'Remark42'
+countBlockInIndex
a(href=url_for(link) + '#post-comment')
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')
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
if article.description
.content!= article.description
else
- const content = strip_html(article.content)
- let expert = content.substring(0, theme.index_post_content.length)
- content.length > theme.index_post_content.length ? expert += ' ...' : ''
.content!= expert
default
- const content = strip_html(article.content)
- let expert = content.substring(0, theme.index_post_content.length)
- content.length > theme.index_post_content.length ? expert += ' ...' : ''
.content!= expert
if theme.ad && theme.ad.index
if (index + 1) % 3 == 0
.recent-post-item.ads-wrap!=theme.ad.index

@ -0,0 +1 @@
.category-lists!= list_categories()

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

@ -0,0 +1,82 @@
#article-container
.flink
- let { content, random, flink_url } = page
- let pageContent = content
if flink_url || random
- const linkData = flink_url ? false : site.data.link || false
script.
(()=>{
const replaceSymbol = (str) => {
return str.replace(/[\p{P}\p{S}]/gu, "-")
}
let result = ""
const add = (str) => {
for(let i = 0; i < str.length; i++){
const replaceClassName = replaceSymbol(str[i].class_name)
const className = str[i].class_name ? `<h2 id="${replaceClassName}"><a href="#${replaceClassName}" class="headerlink" title="${str[i].class_name}"></a>${str[i].class_name}</h2>` : ""
const classDesc = str[i].class_desc ? `<div class="flink-desc">${str[i].class_desc}</div>` : ""
let listResult = ""
const lists = str[i].link_list
if (!{random === true}) {
lists.sort(() => Math.random() - 0.5)
}
for(let j = 0; j < lists.length; j++){
listResult += `
<div class="flink-list-item">
<a href="${lists[j].link}" title="${lists[j].name}" target="_blank">
<div class="flink-item-icon">
<img class="no-lightbox" src="${lists[j].avatar}" onerror='this.onerror=null;this.src="!{url_for(theme.error_img.flink)}"' alt="${lists[j].name}" />
</div>
<div class="flink-item-name">${lists[j].name}</div>
<div class="flink-item-desc" title="${lists[j].descr}">${lists[j].descr}</div>
</a>
</div>`
}
result += `${className}${classDesc} <div class="flink-list">${listResult}</div>`
}
document.querySelector(".flink").insertAdjacentHTML("afterbegin", result)
window.lazyLoadInstance && window.lazyLoadInstance.update()
}
const linkData = !{JSON.stringify(linkData)}
if (!{Boolean(flink_url)}) {
fetch("!{url_for(flink_url)}")
.then(response => response.json())
.then(add)
} else if (linkData) {
add(linkData)
}
})()
else
if site.data.link
- let result = ""
each i in site.data.link
- let className = i.class_name ? markdown(`## ${i.class_name}`) : ""
- let classDesc = i.class_desc ? `<div class="flink-desc">${i.class_desc}</div>` : ""
- let listResult = ""
each j in i.link_list
-
listResult += `
<div class="flink-list-item">
<a href="${j.link}" title="${j.name}" target="_blank">
<div class="flink-item-icon">
<img class="no-lightbox" src="${j.avatar}" onerror='this.onerror=null;this.src="${url_for(theme.error_img.flink)}"' alt="${j.name}" />
</div>
<div class="flink-item-name">${j.name}</div>
<div class="flink-item-desc" title="${j.descr}">${j.descr}</div>
</a>
</div>`
-
- result += `${className}${classDesc} <div class="flink-list">${listResult}</div>`
- pageContent = result + pageContent
!= pageContent

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

@ -0,0 +1,41 @@
-
var options = {
prev_text: '<i class="fas fa-chevron-left fa-fw"></i>',
next_text: '<i class="fas fa-chevron-right fa-fw"></i>',
mid_size: 1,
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'
.prev-post(class=hasPageNext)
a(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(next)
- var hasPagePrev = prev ? 'pull-right' : 'pull-full'
.next-post(class=hasPagePrev)
a(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')
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()
- options.format = 'page/%d/#content-inner'
!=paginator(options)

@ -0,0 +1,23 @@
if theme.post_copyright.enable && page.copyright !== false
- let author = page.copyright_author || config.author
- let authorHref = page.copyright_author_href || theme.post_copyright.author_href || config.url
- let url = page.copyright_url || page.permalink
- let info = page.copyright_info || _p('post.copyright.copyright_content', theme.post_copyright.license_url, theme.post_copyright.license, config.url, config.title)
.post-copyright
.post-copyright__author
span.post-copyright-meta
i.fas.fa-circle-user.fa-fw
= _p('post.copyright.author') + ": "
span.post-copyright-info
a(href=authorHref)=author
.post-copyright__type
span.post-copyright-meta
i.fas.fa-square-arrow-up-right.fa-fw
= _p('post.copyright.link') + ": "
span.post-copyright-info
a(href=url_for(url))= theme.post_copyright.decode ? decodeURI(url) : url
.post-copyright__notice
span.post-copyright-meta
i.fas.fa-circle-exclamation.fa-fw
= _p('post.copyright.copyright_notice') + ": "
span.post-copyright-info!= info

@ -0,0 +1,13 @@
.post-reward
.reward-button
i.fas.fa-qrcode
= theme.reward.text || _p('donate')
.reward-main
ul.reward-all
each item in theme.reward.QR_code
- var clickTo = item.link ? item.link : item.img
li.reward-item
a(href=url_for(clickTo) target='_blank')
img.post-qr-code-img(src=url_for(item.img) alt=item.text)
.post-qr-code-desc=item.text

@ -0,0 +1,61 @@
- const { readmode, translate, darkmode, aside, chat_btn } = theme
mixin rightsideItem(array)
each item in array
case item
when 'readmode'
if is_post() && readmode
button#readmode(type="button" title=_p('rightside.readmode_title'))
i.fas.fa-book-open
when 'translate'
if translate.enable
button#translateLink(type="button" title=_p('rightside.translate_title'))= translate.default
when 'darkmode'
if darkmode.enable && darkmode.button
button#darkmode(type="button" title=_p('rightside.night_mode_title'))
i.fas.fa-adjust
when 'hideAside'
if aside.enable && aside.button && page.aside !== false
button#hide-aside-btn(type="button" title=_p('rightside.aside'))
i.fas.fa-arrows-alt-h
when 'toc'
if showToc
button#mobile-toc-button.close(type="button" title=_p("rightside.toc"))
i.fas.fa-list-ul
when 'chat'
if chat_btn
button#chat-btn(type="button" title=_p("rightside.chat"))
i.fas.fa-sms
when 'comment'
if commentsJsLoad
a#to_comment(href="#post-comment" title=_p("rightside.scroll_to_comment"))
i.fas.fa-comments
#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
+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
+rightsideItem(showArray)
button#go-up(type="button" title=_p("rightside.back_to_top"))
span.scroll-percent
i.fas.fa-arrow-up

@ -0,0 +1,18 @@
#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")
.sidebar-site-data.site-data.is-center
a(href=url_for(config.archive_dir) + '/')
.headline= _p('aside.articles')
.length-num= site.posts.length
a(href=url_for(config.tag_dir) + '/' )
.headline= _p('aside.tags')
.length-num= site.tags.length
a(href=url_for(config.category_dir) + '/')
.headline= _p('aside.categories')
.length-num= site.categories.length
hr.custom-hr
!=partial('includes/header/menu_item', {}, {cache: true})

@ -0,0 +1,15 @@
script.
(() => {
const abcjsInit = () => {
const abcjsFn = () => {
document.querySelectorAll(".abc-music-sheet").forEach(ele => {
ABCJS.renderAbc(ele, ele.innerHTML, {responsive: 'resize'})
})
}
typeof ABCJS === 'object' ? abcjsFn()
: getScript('!{url_for(theme.asset.abcjs_basic_js)}').then(abcjsFn)
}
window.pjax ? abcjsInit() : window.addEventListener('load', abcjsInit)
})()

@ -0,0 +1,6 @@
if theme.abcjs && theme.abcjs.enable
if theme.abcjs.per_page
if is_post() || is_page()
include ./abcjs.pug
else if page.abcjs
include ./abcjs.pug

@ -0,0 +1,3 @@
link(rel='stylesheet' href=url_for(theme.asset.aplayer_css) media="print" onload="this.media='all'")
script(src=url_for(theme.asset.aplayer_js))
script(src=url_for(theme.asset.meting_js))

@ -0,0 +1,35 @@
- const { server, site } = theme.artalk
script.
(() => {
const getArtalkCount = async() => {
try {
const eleGroup = document.querySelectorAll('#recent-posts .artalk-count')
const keyArray = Array.from(eleGroup).map(i => i.getAttribute('data-page-key'))
const headerList = {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Origin': window.location.origin
},
body: new URLSearchParams({
'site_name': '!{site}',
'type':'page_comment',
'page_keys': keyArray
})
}
const res = await fetch('!{server}/api/stat', headerList)
const result = await res.json()
keyArray.forEach((key, index) => {
eleGroup[index].textContent = result.data[key] || 0
})
} catch (err) {
console.error(err)
}
}
window.pjax ? getArtalkCount() : window.addEventListener('load', getArtalkCount)
})()

@ -0,0 +1,25 @@
- const { shortname, apikey } = theme.disqus
script.
(() => {
const getCount = async () => {
try {
const eleGroup = document.querySelectorAll('#recent-posts .disqus-count')
const cleanedLinks = Array.from(eleGroup).map(i => `thread:link=${i.href.replace(/#post-comment$/, '')}`);
const res = await fetch(`https://disqus.com/api/3.0/threads/set.json?forum=!{shortname}&api_key=!{apikey}&${cleanedLinks.join('&')}`,{
method: 'GET'
})
const result = await res.json()
eleGroup.forEach(i => {
const cleanedLink = i.href.replace(/#post-comment$/, '')
const urlData = result.response.find(data => data.link === cleanedLink) || { posts: 0 }
i.textContent = urlData.posts
})
} catch (err) {
console.error(err)
}
}
window.pjax ? getCount() : window.addEventListener('load', getCount)
})()

@ -0,0 +1,18 @@
- const fbSDKVer = 'v16.0'
- const fbSDK = theme.messenger.enable ? `https://connect.facebook.net/${theme.facebook_comments.lang}/sdk/xfbml.customerchat.js#xfbml=1&version=${fbSDKVer}` : `https://connect.facebook.net/${theme.facebook_comments.lang}/sdk.js#xfbml=1&version=${fbSDKVer}`
script.
(()=>{
function loadFBComment () {
if (typeof FB === 'object') FB.XFBML.parse(document.getElementById('recent-posts'))
else {
let ele = document.createElement('script')
ele.setAttribute('src','!{fbSDK}')
ele.setAttribute('async', 'true')
ele.setAttribute('defer', 'true')
ele.setAttribute('crossorigin', 'anonymous')
document.body.appendChild(ele)
}
}
window.pjax ? loadFBComment() : window.addEventListener('load', loadFBComment)
})()

@ -0,0 +1,16 @@
case theme.comments.use[0]
when 'Twikoo'
include ./twikoo.pug
when 'Disqus'
when 'Disqusjs'
include ./disqus.pug
when 'Valine'
include ./valine.pug
when 'Waline'
include ./waline.pug
when 'Facebook Comments'
include ./fb.pug
when 'Remark42'
include ./remark42.pug
when 'Artalk'
include ./artalk.pug

@ -0,0 +1,18 @@
- const { host, siteId, option } = theme.remark42
script.
(()=>{
window.remark_config = Object.assign({
host: '!{host}',
site_id: '!{siteId}',
},!{JSON.stringify(option)})
function getCount () {
const s = document.createElement('script')
s.src = remark_config.host + '/web/counter.js'
s.defer = true
document.head.appendChild(s)
}
window.pjax ? getCount() : window.addEventListener('load', getCount)
})()

@ -0,0 +1,37 @@
script.
(() => {
const getCommentUrl = () => {
const eleGroup = document.querySelectorAll('#recent-posts .article-title')
let urlArray = []
eleGroup.forEach(i=>{
urlArray.push(i.getAttribute('href'))
})
return urlArray
}
const getCount = () => {
const runTwikoo = () => {
twikoo.getCommentsCount({
envId: '!{theme.twikoo.envId}',
region: '!{theme.twikoo.region}',
urls: getCommentUrl(),
includeReply: false
}).then(function (res) {
document.querySelectorAll('#recent-posts .twikoo-count').forEach((item,index) => {
item.textContent = res[index].count
})
}).catch(function (err) {
console.log(err)
})
}
if (typeof twikoo === 'object') {
runTwikoo()
} else {
getScript('!{url_for(theme.asset.twikoo)}').then(runTwikoo)
}
}
window.pjax ? getCount() : window.addEventListener('load', getCount)
})()

@ -0,0 +1,20 @@
script.
(() => {
function loadValine () {
function initValine () {
let initData = {
el: '#vcomment',
appId: '#{theme.valine.appId}',
appKey: '#{theme.valine.appKey}',
serverURLs: '#{theme.valine.serverURLs}'
}
const valine = new Valine(initData)
}
if (typeof Valine === 'function') initValine()
else getScript('!{url_for(theme.asset.valine)}').then(initValine)
}
window.pjax ? loadValine() : window.addEventListener('load', loadValine)
})()

@ -0,0 +1,21 @@
- const serverURL = theme.waline.serverURL.replace(/\/$/, '')
script.
(() => {
async function loadWaline () {
try {
const eleGroup = document.querySelectorAll('#recent-posts .waline-comment-count')
const keyArray = Array.from(eleGroup).map(i => i.getAttribute('data-path'))
const res = await fetch(`!{serverURL}/api/comment?type=count&url=${keyArray}`, { method: 'GET' })
const result = await res.json()
result.data.forEach((count, index) => {
eleGroup[index].textContent = count
})
} catch (err) {
console.error(err)
}
}
window.pjax ? loadWaline() : window.addEventListener('load', loadWaline)
})()

@ -0,0 +1,50 @@
//- https://chatra.io/help/api/
script.
(() => {
const isChatBtn = !{theme.chat_btn}
const isChatHideShow = !{theme.chat_hide_show}
if (isChatBtn) {
const close = () => {
Chatra('minimizeWidget')
Chatra('hide')
}
const open = () => {
Chatra('openChat', true)
Chatra('show')
}
window.ChatraSetup = {
startHidden: true
}
window.chatBtnFn = () => {
const isShow = document.getElementById('chatra').classList.contains('chatra--expanded')
isShow ? close() : open()
}
} 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')
})()

@ -0,0 +1,45 @@
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])
const isChatBtn = !{theme.chat_btn}
const isChatHideShow = !{theme.chat_hide_show}
if (isChatBtn) {
const open = () => {
$crisp.push(["do", "chat:show"])
$crisp.push(["do", "chat:open"])
}
const close = () => {
$crisp.push(["do", "chat:hide"])
}
close()
$crisp.push(["on", "chat:closed", function() {
close()
}])
window.chatBtnFn = () => {
$crisp.is("chat:visible") ? close() : open()
}
} else if (isChatHideShow) {
window.chatBtn = {
hide: () => {
$crisp.push(["do", "chat:hide"])
},
show: () => {
$crisp.push(["do", "chat:show"])
}
}
}
})()

@ -0,0 +1,40 @@
//- 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_btn}
const isChatHideShow = !{theme.chat_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
}
})
}
}
}
})()

@ -0,0 +1,10 @@
if theme.chatra && theme.chatra.enable
include ./chatra.pug
else if theme.tidio && theme.tidio.enable
include ./tidio.pug
else if theme.daovoice && theme.daovoice.enable
include ./daovoice.pug
else if theme.crisp && theme.crisp.enable
include ./crisp.pug
else if theme.messenger && theme.messenger.enable
include ./messenger.pug

@ -0,0 +1,44 @@
- let { pageID, lang } = theme.messenger
- lang = theme.comments.use && theme.comments.use.includes('Facebook Comments') ? theme.facebook_comments.lang : lang
#fb-customer-chat.fb-customerchat(page_id=pageID attribution='biz_inbox')
script.
(() => {
document.getElementById('fb-root') ? '' : document.body.insertAdjacentHTML('afterend', '<div id="fb-root"></div>')
window.fbAsyncInit = function() {
FB.init({
xfbml: true,
version: 'v16.0'
});
};
(function(d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) return;
js = d.createElement(s); js.id = id;
js.src = 'https://connect.facebook.net/!{lang}/sdk/xfbml.customerchat.js';
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));
const isChatBtn = !{theme.chat_btn}
const isChatHideShow = !{theme.chat_hide_show}
if (isChatBtn) {
window.chatBtnFn = () => {
const isShow = document.querySelector('.fb_customer_chat_bounce_in_v2')
isShow ? FB.CustomerChat.hide() : FB.CustomerChat.show()
}
} else if (isChatHideShow) {
window.chatBtn = {
hide: () => {
FB.CustomerChat.hide()
},
show: () => {
FB.CustomerChat.show(false)
}
}
}
})()

@ -0,0 +1,45 @@
script(src=`//code.tidio.co/${theme.tidio.public_key}.js` async)
script.
(() => {
const isChatBtn = !{theme.chat_btn}
const isChatHideShow = !{theme.chat_hide_show}
if (isChatBtn) {
let isShow = false
const close = () => {
window.tidioChatApi.hide()
isShow = false
}
const open = () => {
window.tidioChatApi.open()
window.tidioChatApi.show()
isShow = true
}
const onTidioChatApiReady = () => {
window.tidioChatApi.hide()
window.tidioChatApi.on("close", close)
}
if (window.tidioChatApi) {
window.tidioChatApi.on("ready", onTidioChatApiReady)
} else {
document.addEventListener("tidioChat-ready", onTidioChatApiReady)
}
window.chatBtnFn = () => {
if (!window.tidioChatApi) return
isShow ? close() : open()
}
} else if (isChatHideShow) {
window.chatBtn = {
hide: () => {
window.tidioChatApi && window.tidioChatApi.hide()
},
show: () => {
window.tidioChatApi && window.tidioChatApi.show()
}
}
}
})()

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

Loading…
Cancel
Save