chore: set 2.0 is latest version (#127)

将 2.0 版本的文档设置为最新的文档。同时保留 /2.0.0-SNAPSHOT/

/kind documentation

```release-note
None
```
JohnNiang-patch-1
Ryan Wang 2 years ago committed by GitHub
parent 89fb87557c
commit 5c76928800
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -10,8 +10,8 @@ const config = {
tagline: "Halo 博客系统的文档站点", tagline: "Halo 博客系统的文档站点",
url: "https://docs.halo.run", url: "https://docs.halo.run",
baseUrl: "/", baseUrl: "/",
onBrokenLinks: "throw", onBrokenLinks: "warn",
onBrokenMarkdownLinks: "throw", onBrokenMarkdownLinks: "warn",
favicon: "img/favicon-96x96.png", favicon: "img/favicon-96x96.png",
i18n: { i18n: {
defaultLocale: "zh-Hans", defaultLocale: "zh-Hans",
@ -34,7 +34,7 @@ const config = {
showLastUpdateAuthor: true, showLastUpdateAuthor: true,
remarkPlugins: [math, mermaid], remarkPlugins: [math, mermaid],
rehypePlugins: [katex], rehypePlugins: [katex],
lastVersion: "1.6", lastVersion: "2.0",
versions: { versions: {
current: { current: {
label: "2.0.0-SNAPSHOT", label: "2.0.0-SNAPSHOT",
@ -189,7 +189,7 @@ const config = {
{ {
redirects: [ redirects: [
{ {
to: "/getting-started/install/linux", to: "/1.6/getting-started/install/linux",
from: [ from: [
"/zh/install", "/zh/install",
"/install", "/install",
@ -204,7 +204,7 @@ const config = {
from: ["/zh/install/docker", "/install/docker"], from: ["/zh/install/docker", "/install/docker"],
}, },
{ {
to: "/getting-started/install/other/bt-panel", to: "/1.6/getting-started/install/other/bt-panel",
from: ["/zh/install/bt-panel", "/install/bt-panel"], from: ["/zh/install/bt-panel", "/install/bt-panel"],
}, },
{ {
@ -212,7 +212,7 @@ const config = {
from: ["/zh/install/oneinstack", "/install/oneinstack"], from: ["/zh/install/oneinstack", "/install/oneinstack"],
}, },
{ {
to: "/getting-started/install/other/tencent-cloudbase", to: "/1.6/getting-started/install/other/tencent-cloudbase",
from: [ from: [
"/zh/install/tencent-cloudbase", "/zh/install/tencent-cloudbase",
"/install/tencent-cloudbase", "/install/tencent-cloudbase",
@ -227,7 +227,7 @@ const config = {
from: ["/zh/install/config", "/install/config"], from: ["/zh/install/config", "/install/config"],
}, },
{ {
to: "/getting-started/upgrade", to: "/1.6/getting-started/upgrade",
from: ["/zh/install/upgrade", "/install/upgrade"], from: ["/zh/install/upgrade", "/install/upgrade"],
}, },
{ {

@ -0,0 +1,16 @@
---
title: 关于文档
description: 关于本文档站点的一些说明
---
:::note
此文档使用 [Docusaurus](https://docusaurus.io/) 搭建,感谢 [Docusaurus](https://github.com/facebook/docusaurus) 社区所做的贡献。
:::
## 参与贡献
:::tip
如果你发现文档中有不正确或者需要添加的内容,非常欢迎参与到文档编辑当中。
:::
当前文档的仓库地址为 [halo-dev/docs](https://github.com/halo-dev/docs) ,所以你可以 fork 此仓库,修改之后提交 `Pull request` 等待我们合并即可。

@ -0,0 +1,28 @@
---
title: 问题反馈
description: 问题反馈渠道及指南
---
:::info
如果您在使用过程中遇到了什么问题,您可以通过下面的方式反馈,但请尽量按照要求提出反馈。
:::
## GitHub Issues
链接:<https://github.com/halo-dev/halo/issues>
如果你在使用过程中,遇到了一些 bug 或者需要添加某些新特性,请尽量在 GitHub Issues 进行反馈,这非常有助于我们跟踪解决此问题,您也可以很方便的接收到处理状态。
建议步骤:
1. 在 [Issues 列表](https://github.com/halo-dev/halo/issues) 搜索相关问题,看看是否有其他人已经提到了此问题。
2. 如果当前还没有人遇到您类似的问题,那么请点击右上角的 `New issue` 按钮创建新的 issue。
3. 选择正确的反馈类型。
4. 请尽可能详细的按照模板填写内容。
5. 点击 `Submit new issue` 提交 issue。
## Halo 官方社区
链接:<https://bbs.halo.run>
此平台主要目的用于与其他 Halo 用户进行交流。但如果您对 GitHub 不是很熟悉或者没有账号,您也可以在此平台进行反馈。

@ -0,0 +1,110 @@
---
title: 代码贡献
description: 代码贡献指南
---
欢迎关注并有想法参与 Halo 的开发,以下是关于如何参与到 Halo 项目的指南,仅供参考。
## 发现 Issue
所有的代码尽可能都有依据Issue不是凭空产生。
### 寻找一个 Good First Issue
> 这个步骤非常适合首次贡献者。
在 [halo-dev](https://github.com/halo-dev) 组织下,有非常多的仓库。每个仓库下都有可能包含一些“首次贡献者”友好的 Issue主要是为了给贡献者提供一个友好的体验。 该类 Issue
一般会用 `good-first-issue` 标签标记。标签 `good-first-issue` 表示该 Issue 不需要对 Halo 有深入的理解也能够参与。
请点击:[good-first-issue](https://github.com/issues?q=org%3Ahalo-dev+is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22+no%3Aassignee+)
查看关于 Halo 的 Good First Issue。
### 认领 Issue
若对任何一个 Issue 感兴趣,请尝试在 Issue 进行回复,讨论解决 Issue 的思路。确定后可直接通过 `/assign` 或者 `/assign @GitHub 用户名` 认领这个
Issue。这样可避免两位贡献者在同一个问题上花时间。
## 代码贡献步骤
1. Fork 此仓库
点击 Halo 仓库主页右上角的 `Fork` 按钮即可。
2. Clone 仓库到本地
```bash
git clone https://github.com/{YOUR_USERNAME}/halo --recursive
# 或者 git clone git@github.com:{YOUR_USERNAME}/halo.git --recursive
```
3. 添加主仓库
添加主仓库方便未来同步主仓库最新的 commits 以及创建新的分支。
```bash
git remote add upstream https://github.com/halo-dev/halo.git
# 或者 git remote add upstream git@github.com:halo-dev/halo.git
git fetch upstream master
```
4. 创建新的开发分支
我们需要从主仓库的主分支创建一个新的开发分支。
```bash
git checkout upstream/master
git checkout -b {BRANCH_NAME}
```
5. 提交代码
```bash
git add .
git commit -s -m "Fix a bug king"
git push origin {BRANCH_NAME}
```
6. 合并主分支
在提交 Pull Request 之前,尽量保证当前分支和主分支的代码尽可能同步,这时需要我们手动操作。示例:
```bash
git fetch upstream/master
git merge upstream/master
git push origin {BRANCH_NAME}
```
## Pull Request
进入此阶段说明已经完成了代码的编写,测试和自测,并且准备好接受 Code Review。
### 创建 Pull Request
回到自己的仓库页面,选择 `New pull request` 按钮,创建 `Pull request` 到原仓库的 `master` 分支。
然后等待我们 Review 即可,如有 `Change Request`,再本地修改之后再次提交即可。
提交 Pull Request 的注意事项:
- 提交 Pull Request 请充分自测。
- 每个 Pull Request 尽量只解决一个 Issue特殊情况除外。
- 应尽可能多的添加单元测试,其他测试(集成测试和 E2E 测试)可看情况添加。
- 不论需要解决的 Issue 发生在哪个版本,提交 Pull Request 的时候,请将主仓库的主分支设置为 `master`。例如:即使某个 Bug 于 Halo 1.4.x 被发现,但是提交 Pull Request 仍只针对
`master` 分支,等待 Pull Request 合并之后,我们会通过 `/cherrypick release-1.4` 或者 `/cherry-pick release-1.4` 指令将此 Pull Request
的修改应用到 `release-1.4``release-1.5` 分支上。
### 更新 commits
Code Review 阶段可能需要 Pull Request 作者重新修改代码,请直接在当前分支 commit 并 push 即可,无需关闭并重新提交 Pull Request。示例
```bash
git add .
git commit -s -m "Refactor some code according code review"
git push origin bug/king
```
同时,若已经进入 Code Review 阶段,请不要强制推送 commits 到当前分支。否则 Reviewers 需要从头开始 Code Review。
### 开发规范
请参考 [https://docs.halo.run/developer-guide/core/code-style](https://docs.halo.run/developer-guide/core/code-style)
,请确保所有代码格式化之后再提交。

@ -0,0 +1,30 @@
---
title: 代码风格
description: 代码风格的相关配置说明
---
Halo 添加了 checkstyle 插件,来保证每位提交者代码的风格保持一致,减少无效代码的修改。本篇文章主要讲解如何在 IDEA 中添加 CheckStyle 插件,并引入项目所提供的 checkstyle.xml 配置。
## 安装 CheckStyle-IDEA
- 进入 IDEA 插件市场。
- 搜索 CheckStyle-IDEA点击安装即可。
## 配置 CheckStyle
- 进入 CheckStyle 配置File | Settings | Tools | Checkstyle
- 选择 Checkstyle 版本8.39。
- 在配置文件中点击添加按钮,配置描述可随便填写(推荐 Halo Checks选择 ./config/checkstyle/checkstyle.xml点击下一步和完成
- 勾选刚刚创建的配置文件。
## 配置 Editor
- 进入编辑器配置File | Settings | Editor | Code Style
- 导入 checkstyle.xm 配置:
![image.png](https://halo.run/upload/2020/2/image-0c7a018e73f74634a534fa3ba8806628.png)
- 选择 `./config/checkstyle/checkstyle.xml` 配置文件,点击确定即可。
至此,有关代码风格检查工具和格式化配置已经完成。

@ -0,0 +1,24 @@
---
title: 准备工作
description: 开发环境的准备工作
---
## 环境要求
- [OpenJDK 17 LTS](https://github.com/openjdk/jdk)
- [Node.js 16 LTS](https://nodejs.org)
- [pnpm 7](https://pnpm.io/)
- [IntelliJ IDEA](https://www.jetbrains.com/idea/)
- [Git](https://git-scm.com/)
## 名词解释
### 工作目录
指 Halo 所依赖的工作目录,在 Halo 运行的时候会在系统当前用户目录下产生一个 halo-next 的文件夹,绝对路径为 ~/halo-next。里面通常包含下列目录或文件
1. `db`:存放 H2 Database 的物理文件,如果你使用其他数据库,那么不会存在这个目录。
2. `themes`:里面包含用户所安装的主题。
2. `plugins`:里面包含用户所安装的插件。
5. `attachments`:附件目录。
4. `logs`:运行日志目录。

@ -0,0 +1,105 @@
---
title: 开发环境运行
description: 开发环境运行的指南
---
:::info
在此之前,我们推荐你先阅读[《准备工作》](./prepare),检查本地环境是否满足要求。
:::
## 项目结构说明
目前如果需要完整的运行 Halo总共需要三个部分
1. Halo 主项目([halo-dev/halo](https://github.com/halo-dev/halo))
2. Console 控制台([halo-dev/console](https://github.com/halo-dev/console))
3. 主题
:::info 说明
当前 Halo 主项目并不会将 Console 的构建资源托管到 Git 版本控制,所以在开发环境是需要同时运行 Console 项目的。当然,在我们的最终发布版本的时候会在 CI 中自动构建 Console 到 Halo 主项目。
:::
## 克隆项目
如果你已经 fork 了相关仓库,请将以下命令中的 halo-dev 替换为你的 GitHub 用户名。
```bash
git clone https://github.com/halo-dev/halo
或者使用 ssh 的方式 clone推荐
git clone git@github.com:halo-dev/halo.git
```
```bash
git clone https://github.com/halo-dev/console
或者使用 ssh 的方式 clone推荐
git clone git@github.com:halo-dev/console.git
```
## 运行 Console
```bash
cd path/to/console
```
```bash
pnpm install
```
```bash
pnpm build:packages
```
```bash
pnpm dev
```
最终控制台打印了如下信息即代表运行正常:
```bash
VITE v3.1.6 ready in 638 ms
➜ Local: http://localhost:3000/console/
```
## 运行 Halo
1. 在 IntelliJ IDEA 中打开 Halo 项目,等待 Gradle 初始化和依赖下载完成。
2. 创建本地配置文件
复制 `src/main/resources/application-dev.yaml``application-local.yaml`,修改配置如下:
```yaml
halo:
console:
proxy:
endpoint: http://localhost:3000/
enabled: true
security:
initializer:
super-admin-username: admin
super-admin-password: admin
```
3. 修改 IntelliJ IDEA 的运行配置
将 Active Profiles 改为 `local`,如图所示:
![IntelliJ IDEA Profiles](/img/developer-run/IntelliJ-IDEA-Profiles.png)
4. 点击 IntelliJ IDEA 的运行按钮,等待项目启动完成。
5. 或者使用 Gradle 运行
```bash
# macOS / Linux
./gradlew bootRun --args="--spring.profiles.active=local"
# Windows
gradlew.bat bootRun --args="--spring.profiles.active=local"
```
6. 最终访问 `http://localhost:8090/console` 即可进入控制台。访问 `http://localhost:8090` 即可进入站点首页。

@ -0,0 +1,36 @@
---
title: 系统结构
description: Halo 项目的构成
---
[Halo](https://github.com/halo-dev/halo) 博客系统分为以下四个部分:
| 项目名称 | 简介 |
| :------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------- |
| [halo](https://github.com/halo-dev/halo) | 提供整个系统的服务,采用 [Spring Boot](https://spring.io/) 开发 |
| [halo-admin](https://github.com/halo-dev/halo-admin) | 负责后台管理的渲染,采用 [Vue](https://vuejs.org/) 开发,已集成在 Halo 运行包内,无需独立部署。 |
| [halo-comment](https://github.com/halo-dev/halo-comment) | 评论插件,采用 [Vue](https://vuejs.org/) 开发,在主题中运行方式引入构建好的 `JavaScript` 文件即可 |
| [halo-theme-\*](https://github.com/halo-dev) | 主题项目集,采用 [FreeMarker](https://freemarker.apache.org/) 模板引擎编写,需要包含一些特殊的配置才能够被 halo 所使用 |
## 自定义配置
> 为什么要提前讲自定义配置呢?是因为在这里让大家了解到 `Halo` 的`配置方式`,以及`配置优先级`,不至于未来运行项目的时候不知道如何优雅地修改配置。
`Halo` 配置目录优先级如下(从上到下优先级越来越小,上层的配置将会覆盖下层):
- `Halo` 自定义配置
- file:~/.halo/
- file:~/.halo-dev/
- `Spring Boot` 默认配置
- file:./config/
- file:./
- classpath:/config/
- classpath:/
> 参考: [Application Property Files](https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html#boot-features-external-config-application-property-files)
在开发的时候,希望大家能够在 `~/halo-dev/application.yml` 中进行添加自定义配置。当然后面也会讲到如何用`运行参数` 和 `VM options` 进行控制配置,届时可根据具体情况进行选择。
:::warning
开发的时候,我们不建议直接更改`项目源码`中的所包含的`配置文件`,包括 `application.yml`、`application-dev.yml`、`application-test.yml` 和 `application-user.yml`
:::

@ -0,0 +1,271 @@
---
title: 表单定义
---
在 Halo 2.0,在 Console 端的所有表单我们都使用了 [FormKit](https://github.com/formkit/formkit) 的方案。FormKit 不仅支持使用 Vue 组件的形式来构建表单,同时支持使用 Schema 的形式来构建。因此,我们的 [Setting](https://github.com/halo-dev/halo/blob/87ccd61ae5cd35a38324c30502d4e9c0ced41c6a/src/main/java/run/halo/app/core/extension/Setting.java#L20) 资源中的表单定义,都是使用 FormKit Schema 来定义的,最常用的场景即主题和插件的设置表单定义。当然,如果要在 Halo 2.0 的插件中使用,也可以参考 FormKit 的文档使用 Vue 组件的形式使用,但不需要在插件中引入 FormKit。
此文档将不会介绍 FormKit 的具体使用教程,因为我们已经很好的集成了 FormKit并且使用方式基本无异。此文章将介绍 Halo 2.0 中表单定义的一些规范,以及额外的一些输入组件。
FormKit 相关文档:
- Form Schema:
- <https://formkit.com/essentials/generation>
- <https://formkit.com/advanced/schema>
- FormKit Inputs: <https://formkit.com/inputs>
:::tip
目前不支持 FormKit Pro 中的输入组件,但 Halo 额外提供了部分输入组件,将在下面文档列出。
:::
## Setting 资源定义方式
```yaml title="settings.yaml"
apiVersion: v1alpha1
kind: Setting
metadata:
name: foo-setting
spec:
forms:
- group: group_1
label: 分组 1
formSchema:
- $formkit: radio
name: color_scheme
label: 默认配色
value: system
options:
- label: 跟随系统
value: system
- label: 深色
value: dark
- label: 浅色
value: light
- group: group_2
label: 分组 2
formSchema:
- $formkit: text
name: username
label: 用户名
value: ""
- $formkit: password
name: password
label: 密码
value: ""
```
:::tip
需要注意的是FormKit Schema 本身应该是 JSON 格式的,但目前我们定义一个表单所使用的是 YAML可能在参考 FormKit 写法时需要手动转换一下。
:::
字段说明:
1. `metadata.name`:设置资源的名称,建议以 `-setting` 结尾。
2. `spec.forms`:表单定义,可以定义多个表单,每个表单都有一个 `group` 字段,用于区分不同的表单。
3. `spec.forms[].label`:表单的标题。
4. `spec.forms[].formSchema`:表单的定义,使用 FormKit Schema 来定义。虽然我们使用的 YAML但与 FormKit Schema 完全一致。
## 组件类型
除了 FormKit 官方提供的常用输入组件之外Halo 还额外提供了一些输入组件,这些输入组件可以在 Form Schema 中使用。
### Repeater
#### 描述
一组重复的输入组件,可以用于定义一组数据,最终得到的数据为一个对象的数组,可以方便地让使用者对其进行增加、移除、排序等操作。
#### 示例
```yaml
- $formkit: repeater
name: socials
label: 社交账号
value: []
children:
- $formkit: text
name: name
label: 名称
value: ""
- $formkit: text
name: url
label: 地址
value: ""
```
:::tip
使用 `repeater` 类型时,一定要设置默认值,如果不需要默认有任何元素,可以设置为 `[]`
:::
其中 `name``url` 即数组对象的属性,最终保存表单之后得到的值为以下形式:
```json
{
"socials": [
{
"name": "GitHub",
"url": "https://github.com/halo-dev"
}
]
}
```
UI 效果:
<img src="/img/formkit/formkit-repeater.png" width="50%" />
### Attachment
#### 描述
附件类型的输入框,支持直接调用附件库弹框选择附件。
#### 示例
```yaml
- $formkit: attachment
name: logo
label: Logo
value: ""
```
### Code
#### 描述
代码编辑器的输入组件,集成了 [Codemirror](https://codemirror.net/)。
#### 参数
- `language`:代码语言,目前支持 `yaml` `html` `javascript` `css` `json`
- `height`:代码编辑器的高度。
#### 示例
```yaml
- $formkit: code
name: footer_code
label: 页脚代码注入
value: ""
language: yaml
```
### menuCheckbox
#### 描述
菜单复选框,用于选择系统内的导航菜单。其中选择的值为菜单资源 `metadata.name` 的集合。
#### 示例
```yaml
- $formkit: menuCheckbox
name: menus
label: 菜单
value: []
```
### menuRadio
#### 描述
菜单单选框,用于选择系统内的导航菜单。其中选择的值为菜单资源 `metadata.name`
#### 示例
```yaml
- $formkit: menuRadio
name: menu
label: 菜单
value: ""
```
### postSelect
#### 描述
文章选择器,用于选择系统内的文章。其中选择的值为文章资源 `metadata.name`
#### 示例
```yaml
- $formkit: postSelect
name: post
label: 文章
value: ""
```
### singlePageSelect
#### 描述
单页选择器,用于选择系统内的独立页面。其中选择的值为独立页面资源 `metadata.name`
#### 示例
```yaml
- $formkit: singlePageSelect
name: singlePage
label: 单页
value: ""
```
### categorySelect
#### 描述
文章分类选择器,用于选择系统内的文章分类。其中选择的值为文章分类资源 `metadata.name`
#### 示例
```yaml
- $formkit: categorySelect
name: category
label: 分类
value: ""
```
### categoryCheckbox
#### 描述
文章分类复选框,用于选择系统内的文章分类。其中选择的值为文章分类资源 `metadata.name` 的集合。
#### 示例
```yaml
- $formkit: categoryCheckbox
name: categories
label: 分类
value: []
```
### tagSelect
#### 描述
文章标签选择器,用于选择系统内的文章标签。其中选择的值为文章标签资源 `metadata.name`
#### 示例
```yaml
- $formkit: tagSelect
name: tag
label: 标签
value: ""
```
### tagCheckbox
#### 描述
文章标签复选框,用于选择系统内的文章标签。其中选择的值为文章标签资源 `metadata.name` 的集合。
#### 示例
```yaml
- $formkit: tagCheckbox
name: tags
label: 标签
value: []
```

@ -0,0 +1,6 @@
---
title: 准备工作
description: 插件开发的准备工作
---
WIP

@ -0,0 +1,47 @@
---
title: 常用代码片段
description: 本文档介绍了常用的代码片段,以便于开发者快速上手。
---
## 布局模板
通常情况下,我们需要一个公共模板来定义页面的布局。
```html title="templates/layout.html"
<!DOCTYPE html>
<html lang="en" xmlns:th="https://www.thymeleaf.org" th:fragment="html (head,content)">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2" />
<title th:text="${site.title}"></title>
<link rel="stylesheet" th:href="@{/assets/dist/style.css}" />
<script th:src="@{/assets/dist/main.iife.js}"></script>
<th:block th:if="${head != null}">
<th:block th:replace="${head}" />
</th:block>
</head>
<body>
<section>
<th:block th:replace="${content}" />
</section>
</body>
</html>
```
```html title="templates/index.html"
<!DOCTYPE html>
<html
xmlns:th="https://www.thymeleaf.org"
th:replace="~{modules/layout :: html(head = null,content = ~{::content})}"
>
<th:block th:fragment="content">
<!-- 文章列表 -->
<ul>
<li th:each="post : ${posts.items}">
<a th:href="@{${post.status.permalink}}" th:text="${post.spec.title}"></a>
</li>
</ul>
</th:block>
</html>
```

@ -0,0 +1,89 @@
---
title: 配置文件
description: 关于主题配置文件的文档。
---
目前 Halo 2.0 的主题必须在根目录包含 `theme.yaml`,用于配置主题的基本信息,如主题名称、版本、作者等。
## 格式示例
```yaml title="theme.yaml"
apiVersion: theme.halo.run/v1alpha1
kind: Theme
metadata:
name: theme-foo
spec:
displayName: 示例主题
author:
name: halo-dev
website: https://halo.run
description: 一个示例主题
logo: https://halo.run/logo
website: https://github.com/halo-sigs/theme-foo
repo: https://github.com/halo-sigs/theme-foo.git
settingName: "theme-foo-setting"
configMapName: "theme-foo-configMap"
customTemplates:
post:
- name: 文档
description: 文档类型的文章
screenshot:
file: post_documentation.html
category:
- name: 知识库
description: 知识库类型的分类
screenshot:
file: category_knowledge.html
page:
- name: 关于
description: 关于页面
screenshot:
file: page_about.html
version: 1.0.0
require: 2.0.0
```
## 字段详解
| 字段 | 描述 | 是否必填 |
| ------------------------------- | ----------------------------------------------------------------------------- | -------- |
| `metadata.name` | 主题的唯一标识 | 是 |
| `spec.displayName` | 显示名称 | 是 |
| `spec.author.name` | 作者名称 | 否 |
| `spec.author.website` | 作者网站 | 否 |
| `spec.description` | 主题描述 | 否 |
| `spec.logo` | 主题 Logo | 否 |
| `spec.website` | 主题网站 | 否 |
| `spec.repo` | 主题托管地址 | 否 |
| `spec.settingName` | 设置表单定义的名称,需要同时创建对应的 `settings.yaml` 文件 | 否 |
| `spec.configMapName` | 设置持久化配置的 ConfigMap 名称 | 否 |
| `spec.customTemplates.post` | 文章的自定义模板配置,详细文档可查阅 [模板路由](./template-route-mapping#custom-templates) | 否 |
| `spec.customTemplates.category` | 分类的自定义模板配置,详细文档可查阅 [模板路由](./template-route-mapping#custom-templates) | 否 |
| `spec.customTemplates.page` | 独立页面的自定义模板配置,详细文档可查阅 [模板路由](./template-route-mapping#custom-templates) | 否 |
| `spec.version` | 主题版本 | 是 |
| `spec.require` | 所需 Halo 的运行版本 | 是 |
## 更新配置
由于目前 `theme.yaml` 是持久化存储在数据库中的,不会在修改之后主动更新,所以我们在 Console 的主题页面添加了 `重载主题配置` 的选项。
![重载主题配置](/img/theme/reload-theme-config.png)
## 从 1.x 迁移
为了方便主题开发者从 1.x 迁移,我们提供了工具用于迁移配置文件。
工具仓库地址:<https://github.com/halo-sigs/convert-theme-config-to-next>
```bash
# 1.x 版本主题
cd path/to/theme
npx @halo-dev/convert-theme-config-to-next theme
```
执行完成之后即可看到主题目录下生成了 `theme.2.0.yaml` 文件,重命名为 `theme.yaml` 即可。
:::tip
转换完成之后需要修改 `metadata.name`、`spec.settingName` 和 `spec.configMapName`
:::

@ -0,0 +1,10 @@
---
title: Finder API
description: 本文档介绍 Finder API 的使用方法。
---
import DocCardList from '@theme/DocCardList';
目前在主题模板中获取数据可以使用对应路由提供的 [模板变量](./template-variables),但为了满足在任意位置获取数据的需求,我们提供了 Finder API。
<DocCardList />

@ -0,0 +1,246 @@
---
title: 文章分类
description: 文章分类 - CategoryFinder
---
## getByName(name)
```js
categoryFinder.getByName(name)
```
### 描述
根据 `metadata.name` 获取文章分类。
### 参数
1. `name:string` - 分类的唯一标识 `metadata.name`
### 返回值
[#CategoryVo](#categoryvo)
### 示例
```html
<div th:with="category = ${categoryFinder.getByName('category-foo')}">
<a th:href="@{${category.status.permalink}}" th:text="${category.spec.displayName}"></a>
</div>
```
## getByNames(names)
```js
categoryFinder.getByNames(names)
```
### 描述
根据一组 `metadata.name` 获取文章分类。
### 参数
1. `names:List<string>` - 分类的唯一标识 `metadata.name` 的集合。
### 返回值
List<[#CategoryVo](#categoryvo)>
### 示例
```html
<div th:with="categories = ${categoryFinder.getByNames(['category-foo', 'category-bar'])}">
<a th:each="category : ${categories}" th:href="@{${category.status.permalink}}" th:text="${category.spec.displayName}"></a>
</div>
```
## list(page,size)
```js
categoryFinder.list(page,size)
```
### 描述
根据分页参数获取分类列表。
### 参数
1. `page:int` - 分页页码,从 1 开始
2. `size:int` - 分页条数
### 返回值
[#ListResult<CategoryVo\>](#listresultcategoryvo)
### 示例
```html
<ul th:with="categories = ${categoryFinder.list(1,10)}">
<li th:each="category : ${categories.items}">
<a th:href="@{${category.status.permalink}}" th:text="${category.spec.displayName}"></a>
</li>
</ul>
```
## listAll()
```js
categoryFinder.listAll()
```
### 描述
获取所有文章分类。
### 参数
### 返回值
List<[#CategoryVo](#categoryvo)>
### 示例
```html
<ul th:with="categories = ${categoryFinder.listAll()}">
<li th:each="category : ${categories}">
<a th:href="@{${category.status.permalink}}" th:text="${category.spec.displayName}"></a>
</li>
</ul>
```
## listAsTree()
```js
categoryFinder.listAsTree()
```
### 描述
获取所有文章分类的多层级结构。
### 参数
### 返回值
List<[#CategoryTreeVo](#categorytreevo)>
### 示例
```html
<div th:with="categories = ${categoryFinder.listAsTree()}">
<ul>
<li th:replace="~{modules/category-tree :: single(categories=${categories})}" />
</ul>
</div>
```
```html title="/templates/category-tree.html"
<ul th:fragment="next (categories)">
<li th:fragment="single (categories)" th:each="category : ${categories}">
<a th:href="@{${category.status.permalink}}">
<span th:text="${category.spec.displayName}"> </span>
</a>
<th:block th:if="${not #lists.isEmpty(category.children)}">
<th:block th:replace="~{modules/category-tree :: next (categories=${category.children})}"></th:block>
</th:block>
</li>
</ul>
```
## 类型定义
### CategoryVo
```json title="CategoryVo"
{
"metadata": {
"name": "string",
"labels": {
"additionalProp1": "string"
},
"annotations": {
"additionalProp1": "string"
},
"creationTimestamp": "2022-11-20T13:06:38.512Z",
},
"spec": {
"displayName": "string",
"slug": "string",
"description": "string",
"cover": "string",
"template": "string",
"priority": 0,
"children": [
"string"
]
},
"status": {
"permalink": "string",
"postCount": 0,
"visiblePostCount": 0
},
"postCount": 0
}
```
### ListResult<CategoryVo\>
```json title="ListResult<CategoryVo>"
{
"page": 0,
"size": 0,
"total": 0,
"items": "List<#CategoryVo>",
"first": true,
"last": true,
"hasNext": true,
"hasPrevious": true,
"totalPages": 0
}
```
- [#CategoryVo](#categoryvo)
### CategoryTreeVo
```json title="CategoryTreeVo"
{
"metadata": {
"name": "string",
"labels": {
"additionalProp1": "string"
},
"annotations": {
"additionalProp1": "string"
},
"creationTimestamp": "2022-11-20T14:18:49.230Z",
},
"spec": {
"displayName": "string",
"slug": "string",
"description": "string",
"cover": "string",
"template": "string",
"priority": 0,
"children": [
"string"
]
},
"status": {
"permalink": "string",
"postCount": 0,
"visiblePostCount": 0
},
"children": "List<#CategoryTreeVo>",
"parentName": "string",
"postCount": 0
}
```
- [#CategoryTreeVo](#categorytreevo)

@ -0,0 +1,245 @@
---
title: 评论
description: 评论 - CommentFinder
---
## getByName(name)
```js
commentFinder.getByName(name)
```
### 描述
根据 `metadata.name` 获取评论。
### 参数
1. `name:string` - 评论的唯一标识 `metadata.name`
### 返回值
[#CommentVo](#commentvo)
### 示例
```html
<div th:with="comment = ${commentFinder.getByName('comment-foo')}">
<span th:text="${comment.spec.owner.displayName}"></span>
<div th:text="${comment.spec.content}"></div>
</div>
```
## list(ref,page,size)
```js
commentFinder.list(ref,page,size)
```
### 描述
根据评论的 `metadata.name` 和分页参数获取回复列表。
### 参数
1. `ref:#Ref` - 评论的唯一标识 `metadata.name`
2. `page:int` - 分页页码,从 1 开始
3. `size:int` - 分页条数
- [#Ref](#ref)
### 返回值
[#ListResult<CommentVo\>](#listresultcommentvo)
### 示例
```html
<ul th:with="comments = ${commentFinder.list({ group: 'content.halo.run', version: 'v1alpha1', kind: 'Post', name: 'post-foo' },1,10)}">
<li th:each="comment : ${comments.items}">
<span th:text="${comment.spec.owner.displayName}"></span>
<div th:text="${comment.spec.content}"></div>
</li>
</ul>
```
## listReply(commentName,page,size)
```js
commentFinder.listReply(commentName,page,size)
```
### 描述
根据评论的 `metadata.name` 和分页参数获取回复列表。
### 参数
1. `commentName:string` - 评论的唯一标识 `metadata.name`
1. `page:int` - 分页页码,从 1 开始
2. `size:int` - 分页条数
### 返回值
[#ListResult<ReplyVo\>](#listresultreplyvo)
### 示例
```html
<ul th:with="replies = ${commentFinder.listReply('comment-foo',1,10)}">
<li th:each="reply : ${replies.items}">
<span th:text="${reply.spec.owner.displayName}"></span>
<div th:text="${reply.spec.content}"></div>
</li>
</ul>
```
## 类型定义
### CommentVo
```json title="CommentVo"
{
"metadata": {
"name": "string",
"labels": {
"additionalProp1": "string"
},
"annotations": {
"additionalProp1": "string"
},
"creationTimestamp": "2022-11-20T12:16:19.788Z"
},
"spec": {
"raw": "string",
"content": "string",
"owner": {
"kind": "string",
"name": "string",
"displayName": "string",
"annotations": {
"additionalProp1": "string"
}
},
"userAgent": "string",
"ipAddress": "string",
"priority": 0,
"top": false,
"allowNotification": true,
"approved": false,
"hidden": false,
"subjectRef": {
"group": "string",
"version": "string",
"kind": "string",
"name": "string"
},
"lastReadTime": "2022-11-20T12:16:19.788Z"
},
"status": {
"lastReplyTime": "2022-11-20T12:16:19.788Z",
"replyCount": 0,
"unreadReplyCount": 0,
"hasNewReply": true
},
"owner": {
"kind": "string",
"name": "string",
"displayName": "string",
"avatar": "string",
"email": "string"
}
}
```
### ListResult<CommentVo\>
```json title="ListResult<CommentVo>"
{
"page": 0,
"size": 0,
"total": 0,
"items": "List<#CommentVo>",
"first": true,
"last": true,
"hasNext": true,
"hasPrevious": true,
"totalPages": 0
}
```
- [#CommentVo](#commentvo)
### ReplyVo
```json title="ReplyVo"
{
"metadata": {
"name": "string",
"labels": {
"additionalProp1": "string"
},
"annotations": {
"additionalProp1": "string"
},
"creationTimestamp": "2022-11-20T12:25:32.357Z"
},
"spec": {
"raw": "string",
"content": "string",
"owner": {
"kind": "string",
"name": "string",
"displayName": "string",
"annotations": {
"additionalProp1": "string"
}
},
"userAgent": "string",
"ipAddress": "string",
"priority": 0,
"top": false,
"allowNotification": true,
"approved": false,
"hidden": false,
"commentName": "string",
"quoteReply": "string"
},
"owner": {
"kind": "string",
"name": "string",
"displayName": "string",
"avatar": "string",
"email": "string"
}
}
```
### ListResult<ReplyVo\>
```json title="ListResult<ReplyVo>"
{
"page": 0,
"size": 0,
"total": 0,
"items": "List<#ReplyVo>",
"first": true,
"last": true,
"hasNext": true,
"hasPrevious": true,
"totalPages": 0
}
```
- [#ReplyVo](#replyvo)
### Ref
```json title="Ref"
{
"group": "string",
"kind": "string",
"version": "string",
"name": "string"
}
```

@ -0,0 +1,69 @@
---
title: 作者
description: 作者 - ContributorFinder
---
## getContributor(name)
```js
contributorFinder.getContributor(name)
```
### 描述
根据 `metadata.name` 获取作者。
### 参数
1. `name:string` - 作者的唯一标识 `metadata.name`
### 返回值
[#Contributor](#contributor)
### 示例
```html
<div th:with="contributor = ${contributorFinder.getByName('contributor-foo')}">
<h1 th:text="${contributor.displayName}"></h1>
</div>
```
## getContributors(names)
```js
contributorFinder.getContributors(names)
```
### 描述
根据一组 `metadata.name` 获取作者。
### 参数
1. `names:List<string>` - 作者的唯一标识 `metadata.name` 的集合。
### 返回值
List<[#Contributor](#contributor)>
### 示例
```html
<div th:with="contributors = ${contributorFinder.getByNames(['contributor-foo, 'contributor-bar'])}">
<span th:each="contributor : ${contributors}" th:text="${contributor.displayName}"></span>
</div>
```
## 类型定义
### Contributor
```json title="Contributor"
{
"name": "string",
"displayName": "string",
"avatar": "string",
"bio": "string"
}
```

@ -0,0 +1,145 @@
---
title: 导航菜单
description: 导航菜单 - MenuFinder
---
## getByName(name)
```js
menuFinder.getByName(name)
```
### 描述
根据 `metadata.name` 获取菜单。
### 参数
1. `name:string` - 菜单的唯一标识 `metadata.name`
### 返回值
[#MenuVo](#menuvo)
### 示例
```html
<div th:with="menu = ${menuFinder.getByName('menu-foo')}">
<ul th:with="menuItems = ${menu.menuItems}">
<li th:each="menuItem : ${menuItems}">
<a th:href="@{${menuItem.status.href}}" th:text="${menuItem.spec.displayName}"></a>
</li>
</ul>
</div>
```
## getPrimary()
```js
menuFinder.getPrimary()
```
### 描述
获取主菜单。
### 参数
### 返回值
[#MenuVo](#menuvo)
### 示例
```html
<div th:with="menu = ${menuFinder.getPrimary()}">
<ul th:with="menuItems = ${menu.menuItems}">
<li th:each="menuItem : ${menuItems}">
<a th:href="@{${menuItem.status.href}}" th:text="${menuItem.spec.displayName}"></a>
</li>
</ul>
</div>
```
## 类型定义
### MenuVo
```json title="MenuVo"
{
"metadata": {
"name": "string",
"labels": {
"additionalProp1": "string"
},
"annotations": {
"additionalProp1": "string"
},
"creationTimestamp": "2022-11-20T14:44:58.984Z",
},
"spec": {
"displayName": "string",
"menuItems": [
"string"
]
},
"menuItems": "List<#MenuItemVo>"
}
```
### MenuItemVo
```json title="MenuItemVo"
{
"metadata": {
"name": "string",
"labels": {
"additionalProp1": "string"
},
"annotations": {
"additionalProp1": "string"
},
"creationTimestamp": "2022-11-20T14:44:58.984Z",
},
"spec": {
"displayName": "string",
"href": "string",
"priority": 0,
"children": [
"string"
],
"categoryRef": {
"group": "string",
"version": "string",
"kind": "string",
"name": "string"
},
"tagRef": {
"group": "string",
"version": "string",
"kind": "string",
"name": "string"
},
"postRef": {
"group": "string",
"version": "string",
"kind": "string",
"name": "string"
},
"singlePageRef": {
"group": "string",
"version": "string",
"kind": "string",
"name": "string"
}
},
"status": {
"displayName": "string",
"href": "string"
},
"children": "List<#MenuItemVo>",
"parentName": "string",
}
```

@ -0,0 +1,33 @@
---
title: 插件
description: 插件 - PluginFinder
---
## available(pluginName)
```js
pluginFinder.available(pluginName)
```
### 描述
判断一个插件是否可用,会同时判断插件是否安装和启用。
### 参数
1. `pluginName:string` - 插件的唯一标识 `metadata.name`
### 返回值
`boolean` - 插件是否可用
### 示例
```html
<!-- https://github.com/halo-sigs/plugin-search-widget -->
<li th:if="${pluginFinder.available('PluginSearchWidget')}">
<a href="javascript:SearchWidget.open()" title="搜索">
搜索
</a>
</li>
```

@ -0,0 +1,637 @@
---
title: 文章
description: 文章 - PostFinder
---
## getByName(postName)
```js
postFinder.getByName(postName)
```
### 描述
根据 `metadata.name` 获取文章。
### 参数
1. `postName:string` - 文章的唯一标识 `metadata.name`
### 返回值
[#PostVo](#postvo)
### 示例
```html
<div th:with="post = ${postFinder.getByName('post-foo')}">
<a th:href="@{${post.status.permalink}}" th:text="${post.spec.title}"></a>
</div>
```
## content(postName)
```js
postFinder.content(postName)
```
### 描述
根据文章的 `metadata.name` 单独获取文章内容。
### 参数
1. `postName:string` - 文章的唯一标识 `metadata.name`
### 返回值
[#ContentVo](#contentvo)
### 示例
```html
<div th:with="content = ${postFinder.content('post-foo')}">
<div th:utext="${content.content}"></div>
</div>
```
## cursor(postName)
```js
postFinder.cursor(postName)
```
### 描述
根据文章的 `metadata.name` 获取相邻的文章(上一篇 / 下一篇)。
### 参数
1. `postName:string` - 文章的唯一标识 `metadata.name`
### 返回值
[#NavigationPostVo](#navigationpostvo)
### 示例
```html title="/templates/post.html"
<div
th:with="postCursor = ${postFinder.cursor(post.metadata.name)}"
>
<a
th:if="${postCursor.hasPrevious()}"
th:href="@{${postCursor.previous.status.permalink}}"
>
<span th:text="${postCursor.previous.spec.title}"></span>
</a>
<a
th:if="${postCursor.hasNext()}"
th:href="@{${postCursor.next.status.permalink}}"
>
<span th:text="${postCursor.next.spec.title}"></span>
</a>
</div>
```
## listAll()
```js
postFinder.listAll()
```
### 描述
获取所有文章。
### 参数
### 返回值
List<[#ListedPostVo](#listedpostvo)>
### 示例
```html
<ul th:with="posts = ${postFinder.listAll()}">
<li th:each="post : ${posts}">
<a th:href="@{${post.status.permalink}}" th:text="${post.spec.title}"></a>
</li>
</ul>
```
## list(page,size)
```js
postFinder.list(page,size)
```
### 描述
根据分页参数获取文章列表。
### 参数
1. `page:int` - 分页页码,从 1 开始
2. `size:int` - 分页条数
### 返回值
[#ListResult<ListedPostVo\>](#listresultlistedpostvo)
### 示例
```html
<ul th:with="posts = ${postFinder.list(1,10)}">
<li th:each="post : ${posts.items}">
<a th:href="@{${post.status.permalink}}" th:text="${post.spec.title}"></a>
</li>
</ul>
```
## listByCategory(page,size,categoryName)
```js
postFinder.listByCategory(page,size,categoryName)
```
### 描述
根据分类标识和分页参数获取文章列表。
### 参数
1. `page:int` - 分页页码,从 1 开始
2. `size:int` - 分页条数
3. `categoryName:string` - 文章分类唯一标识 `metadata.name`
### 返回值
[#ListResult<ListedPostVo\>](#listresultlistedpostvo)
### 示例
```html
<ul th:with="posts = ${postFinder.listByCategory(1,10,'category-foo')}">
<li th:each="post : ${posts.items}">
<a th:href="@{${post.status.permalink}}" th:text="${post.spec.title}"></a>
</li>
</ul>
```
## listByTag(page,size,tag)
```js
postFinder.listByTag(page,size,tag)
```
### 描述
根据标签标识和分页参数获取文章列表。
### 参数
1. `page:int` - 分页页码,从 1 开始
2. `size:int` - 分页条数
3. `tag:string` - 文章分类唯一标识 `metadata.name`
### 返回值
[#ListResult<ListedPostVo\>](#listresultlistedpostvo)
### 示例
```html
<ul th:with="posts = ${postFinder.listByTag(1,10,'tag-foo')}">
<li th:each="post : ${posts.items}">
<a th:href="@{${post.status.permalink}}" th:text="${post.spec.title}"></a>
</li>
</ul>
```
## archives(page,size)
```js
postFinder.archives(page,size)
```
### 描述
根据分页参数获取文章归档列表。
### 参数
1. `page:int` - 分页页码,从 1 开始
2. `size:int` - 分页条数
### 返回值
[#ListResult<PostArchiveVo\>](#listresultpostarchivevo)
### 示例
```html
<th:block th:with="archives = ${postFinder.archives(1,10)}">
<th:block th:each="archive : ${archives.items}">
<h1 th:text="${archive.year}"></h1>
<ul>
<th:block th:each="month : ${archive.months}">
<li th:each="post : ${month.posts}">
<a
th:href="@{${post.status.permalink}}"
th:text="${post.spec.title}">
</a>
</li>
</th:block>
</ul>
</th:block>
</th:block>
```
## archives(page,size,year)
```js
postFinder.archives(page,size,year)
```
### 描述
根据年份和分页参数获取文章归档列表。
### 参数
1. `page:int` - 分页页码,从 1 开始
2. `size:int` - 分页条数
2. `year:string` - 年份
### 返回值
[#ListResult<PostArchiveVo\>](#listresultpostarchivevo)
### 示例
```html
<th:block th:with="archives = ${postFinder.archives(1,10,'2022')}">
<th:block th:each="archive : ${archives.items}">
<h1 th:text="${archive.year}"></h1>
<ul>
<th:block th:each="month : ${archive.months}">
<li th:each="post : ${month.posts}">
<a
th:href="@{${post.status.permalink}}"
th:text="${post.spec.title}">
</a>
</li>
</th:block>
</ul>
</th:block>
</th:block>
```
## archives(page,size,year,month)
```js
postFinder.archives(page,size,year,month)
```
### 描述
根据年月和分页参数获取文章归档列表。
### 参数
1. `page:int` - 分页页码,从 1 开始
2. `size:int` - 分页条数
2. `year:string` - 年份
2. `month:string` - 月份
### 返回值
[#ListResult<PostArchiveVo\>](#listresultpostarchivevo)
### 示例
```html
<th:block th:with="archives = ${postFinder.archives(1,10,'2022','11')}">
<th:block th:each="archive : ${archives.items}">
<h1 th:text="${archive.year}"></h1>
<ul>
<th:block th:each="month : ${archive.months}">
<li th:each="post : ${month.posts}">
<a
th:href="@{${post.status.permalink}}"
th:text="${post.spec.title}">
</a>
</li>
</th:block>
</ul>
</th:block>
</th:block>
```
## 类型定义
### CategoryVo
```json title="CategoryVo"
{
"metadata": {
"name": "string",
"labels": {
"additionalProp1": "string"
},
"annotations": {
"additionalProp1": "string"
},
"creationTimestamp": "2022-11-20T13:06:38.512Z",
},
"spec": {
"displayName": "string",
"slug": "string",
"description": "string",
"cover": "string",
"template": "string",
"priority": 0,
"children": [
"string"
]
},
"status": {
"permalink": "string",
"postCount": 0,
"visiblePostCount": 0
},
"postCount": 0
}
```
### TagVo
```json title="TagVo"
{
"metadata": {
"name": "string",
"labels": {
"additionalProp1": "string"
},
"annotations": {
"additionalProp1": "string"
},
"creationTimestamp": "2022-11-20T13:06:38.512Z",
},
"spec": {
"displayName": "string",
"slug": "string",
"color": "#F9fEB1",
"cover": "string"
},
"status": {
"permalink": "string",
"visiblePostCount": 0,
"postCount": 0
},
"postCount": 0
}
```
### PostVo
```json title="PostVo"
{
"metadata": {
"name": "string",
"labels": {
"additionalProp1": "string"
},
"annotations": {
"additionalProp1": "string"
},
"creationTimestamp": "2022-11-20T12:45:43.888Z",
},
"spec": {
"title": "string",
"slug": "string",
"releaseSnapshot": "string",
"headSnapshot": "string",
"baseSnapshot": "string",
"owner": "string",
"template": "string",
"cover": "string",
"deleted": false,
"publish": false,
"publishTime": "2022-11-20T12:45:43.888Z",
"pinned": false,
"allowComment": true,
"visible": "PUBLIC",
"priority": 0,
"excerpt": {
"autoGenerate": true,
"raw": "string"
},
"categories": [
"string"
],
"tags": [
"string"
],
"htmlMetas": [
{
"additionalProp1": "string"
}
]
},
"status": {
"permalink": "string",
"excerpt": "string",
"commentsCount": 0,
"contributors": [
"string"
]
},
"categories": "List<#CategoryVo>",
"tags": "List<#TagVo>",
"contributors": [
{
"name": "string",
"displayName": "string",
"avatar": "string",
"bio": "string"
}
],
"owner": {
"name": "string",
"displayName": "string",
"avatar": "string",
"bio": "string"
},
"stats": {
"visit": 0,
"upvote": 0,
"comment": 0
},
"content": {
"raw": "string",
"content": "string"
}
}
```
- [#CategoryVo](#categoryvo)
- [#TagVo](#tagvo)
### ContentVo
```json title="ContentVo"
{
"raw": "string",
"content": "string"
}
```
### NavigationPostVo
```json title="NavigationPostVo"
{
"previous": "#PostVo",
"current": "#PostVo",
"next": "#PostVo"
}
```
- [#PostVo](#postvo)
### ListedPostVo
```json title="ListedPostVo"
{
"metadata": {
"name": "string",
"labels": {
"additionalProp1": "string"
},
"annotations": {
"additionalProp1": "string"
},
"creationTimestamp": "2022-11-20T13:06:38.505Z",
},
"spec": {
"title": "string",
"slug": "string",
"releaseSnapshot": "string",
"headSnapshot": "string",
"baseSnapshot": "string",
"owner": "string",
"template": "string",
"cover": "string",
"deleted": false,
"publish": false,
"publishTime": "2022-11-20T13:06:38.505Z",
"pinned": false,
"allowComment": true,
"visible": "PUBLIC",
"priority": 0,
"excerpt": {
"autoGenerate": true,
"raw": "string"
},
"categories": [
"string"
],
"tags": [
"string"
],
"htmlMetas": [
{
"additionalProp1": "string"
}
]
},
"status": {
"permalink": "string",
"excerpt": "string",
"inProgress": true,
"commentsCount": 0,
"contributors": [
"string"
]
},
"categories": "List<#CategoryVo>",
"tags": "List<#TagVo>",
"contributors": [
{
"name": "string",
"displayName": "string",
"avatar": "string",
"bio": "string"
}
],
"owner": {
"name": "string",
"displayName": "string",
"avatar": "string",
"bio": "string"
},
"stats": {
"visit": 0,
"upvote": 0,
"comment": 0
}
}
```
- [#CategoryVo](#categoryvo)
- [#TagVo](#tagvo)
### ListResult<ListedPostVo\>
```json title="ListResult<ListedPostVo>"
{
"page": 0,
"size": 0,
"total": 0,
"items": "List<#ListedPostVo>",
"first": true,
"last": true,
"hasNext": true,
"hasPrevious": true,
"totalPages": 0
}
```
- [#ListedPostVo](#listedpostvo)
### PostArchiveVo
```json title="PostArchiveVo"
{
"year": "string",
"months": [
{
"month": "string",
"posts": "#ListedPostVo"
}
]
}
```
- [#ListedPostVo](#listedpostvo)
### ListResult<PostArchiveVo\>
```json title="ListResult<PostArchiveVo>"
{
"page": 0,
"size": 0,
"total": 0,
"items": "List<#PostArchiveVo>",
"first": true,
"last": true,
"hasNext": true,
"hasPrevious": true,
"totalPages": 0
}
```
- [#PostArchiveVo](#postarchivevo)

@ -0,0 +1,260 @@
---
title: 独立页面
description: 独立页面 - SinglePageFinder
---
## getByName(pageName)
```js
singlePageFinder.getByName(pageName)
```
### 描述
根据 `metadata.name` 获取独立页面。
### 参数
1. `pageName:string` - 独立页面的唯一标识 `metadata.name`
### 返回值
[#SinglePageVo](#singlepagevo)
### 示例
```html
<div th:with="singlePage = ${singlePageFinder.getByName('page-foo')}">
<a th:href="@{${singlePage.status.permalink}}" th:text="${singlePage.spec.title}"></a>
</div>
```
## content(pageName)
```js
singlePageFinder.content(pageName)
```
### 描述
根据独立页面的 `metadata.name` 单独获取独立页面内容。
### 参数
1. `pageName:string` - 独立页面的唯一标识 `metadata.name`
### 返回值
[#ContentVo](#contentvo)
### 示例
```html
<div th:with="content = ${singlePageFinder.content('page-foo')}">
<div th:utext="${content.content}"></div>
</div>
```
## list(page,size)
```js
singlePageFinder.list(page,size)
```
### 描述
根据分页参数获取独立页面列表。
### 参数
1. `page:int` - 分页页码,从 1 开始
2. `size:int` - 分页条数
### 返回值
[#ListResult<ListedSinglePageVo\>](#listresultlistedsinglepagevo)
### 示例
```html
<ul th:with="singlePages = ${singlePageFinder.list(1,10)}">
<li th:each="singlePage : ${singlePages.items}">
<a th:href="@{${singlePage.status.permalink}}" th:text="${singlePage.spec.title}"></a>
</li>
</ul>
```
## 类型定义
### SinglePageVo
```json title="SinglePageVo"
{
"metadata": {
"name": "string",
"labels": {
"additionalProp1": "string"
},
"annotations": {
"additionalProp1": "string"
},
"creationTimestamp": "2022-11-20T14:29:44.601Z",
},
"spec": {
"title": "string",
"slug": "string",
"releaseSnapshot": "string",
"headSnapshot": "string",
"baseSnapshot": "string",
"owner": "string",
"template": "string",
"cover": "string",
"deleted": false,
"publish": false,
"publishTime": "2022-11-20T14:29:44.601Z",
"pinned": false,
"allowComment": true,
"visible": "PUBLIC",
"priority": 0,
"excerpt": {
"autoGenerate": true,
"raw": "string"
},
"htmlMetas": [
{
"additionalProp1": "string"
}
]
},
"status": {
"permalink": "string",
"excerpt": "string",
"inProgress": true,
"commentsCount": 0,
"contributors": [
"string"
]
},
"stats": {
"visit": 0,
"upvote": 0,
"comment": 0
},
"contributors": [
{
"name": "string",
"displayName": "string",
"avatar": "string",
"bio": "string"
}
],
"owner": {
"name": "string",
"displayName": "string",
"avatar": "string",
"bio": "string"
},
"content": {
"raw": "string",
"content": "string"
}
}
```
### ListedSinglePageVo
```json title="ListedSinglePageVo"
{
"metadata": {
"name": "string",
"labels": {
"additionalProp1": "string"
},
"annotations": {
"additionalProp1": "string"
},
"creationTimestamp": "2022-11-20T14:31:00.876Z"
},
"spec": {
"title": "string",
"slug": "string",
"releaseSnapshot": "string",
"headSnapshot": "string",
"baseSnapshot": "string",
"owner": "string",
"template": "string",
"cover": "string",
"deleted": false,
"publish": false,
"publishTime": "2022-11-20T14:31:00.876Z",
"pinned": false,
"allowComment": true,
"visible": "PUBLIC",
"priority": 0,
"excerpt": {
"autoGenerate": true,
"raw": "string"
},
"htmlMetas": [
{
"additionalProp1": "string"
}
]
},
"status": {
"permalink": "string",
"excerpt": "string",
"inProgress": true,
"commentsCount": 0,
"contributors": [
"string"
]
},
"stats": {
"visit": 0,
"upvote": 0,
"comment": 0
},
"contributors": [
{
"name": "string",
"displayName": "string",
"avatar": "string",
"bio": "string"
}
],
"owner": {
"name": "string",
"displayName": "string",
"avatar": "string",
"bio": "string"
}
}
```
### ListResult<ListedSinglePageVo\>
```json title="ListResult<ListedSinglePageVo>"
{
"page": 0,
"size": 0,
"total": 0,
"items": "List<#ListedSinglePageVo>",
"first": true,
"last": true,
"hasNext": true,
"hasPrevious": true,
"totalPages": 0
}
```
- [#ListedSinglePageVo](#listedsinglepagevo)
### ContentVo
```json title="ContentVo"
{
"raw": "string",
"content": "string"
}
```

@ -0,0 +1,45 @@
---
title: 站点统计
description: 站点统计 - SiteStatsFinder
---
## getStats()
```js
siteStatsFinder.getStats()
```
### 描述
获取站点的统计信息。
### 参数
### 返回值
[#SiteStatsVo](#sitestatsvo)
### 示例
```html
<ul th:with="stats = ${siteStatsFinder.getStats()}">
<li th:text="${stats.visit}"></li>
<li th:text="${stats.post}"></li>
</ul>
```
## 类型定义
### SiteStatsVo
```json title="SiteStatsVo"
{
"visit": 0,
"upvote": 0,
"comment": 0,
"post": 0,
"category": 0
}
```

@ -0,0 +1,162 @@
---
title: 文章标签
description: 文章标签 - TagFinder
---
## getByName(name)
```js
tagFinder.getByName(name)
```
### 描述
根据 `metadata.name` 获取标签。
### 参数
1. `name:string` - 标签的唯一标识 `metadata.name`
### 返回值
[#TagVo](#tagvo)
### 示例
```html
<div th:with="tag = ${tagFinder.getByName('tag-foo')}">
<a th:href="@{${tag.status.permalink}}" th:text="${tag.spec.displayName}"></a>
</div>
```
## getByNames(names)
```js
tagFinder.getByNames(names)
```
### 描述
根据一组 `metadata.name` 获取标签。
### 参数
1. `names:List<string>` - 标签的唯一标识 `metadata.name` 的集合。
### 返回值
List<[#TagVo](#tagvo)>
### 示例
```html
<div th:with="tags = ${tagFinder.getByNames(['tag-foo', 'tag-bar'])}">
<a th:each="tag : ${tags}" th:href="@{${tag.status.permalink}}" th:text="${tag.spec.displayName}"></a>
</div>
```
## list(page,size)
```js
tagFinder.list(page,size)
```
### 描述
根据分页参数获取标签列表。
### 参数
1. `page:int` - 分页页码,从 1 开始
2. `size:int` - 分页条数
### 返回值
[#ListResult<TagVo\>](#listresulttagvo)
### 示例
```html
<ul th:with="tags = ${tagFinder.list(1,10)}">
<li th:each="tag : ${tags.items}">
<a th:href="@{${tag.status.permalink}}" th:text="${tag.spec.displayName}"></a>
</li>
</ul>
```
## listAll()
```js
tagFinder.listAll()
```
### 描述
获取所有文章标签。
### 参数
### 返回值
List<[#TagVo](#tagvo)>
### 示例
```html
<ul th:with="tags = ${tagFinder.listAll()}">
<li th:each="tag : ${tags}">
<a th:href="@{${tag.status.permalink}}" th:text="${tag.spec.displayName}"></a>
</li>
</ul>
```
## 类型定义
### TagVo
```json title="TagVo"
{
"metadata": {
"name": "string",
"labels": {
"additionalProp1": "string"
},
"annotations": {
"additionalProp1": "string"
},
"creationTimestamp": "2022-11-20T13:06:38.512Z",
},
"spec": {
"displayName": "string",
"slug": "string",
"color": "#F9fEB1",
"cover": "string"
},
"status": {
"permalink": "string",
"visiblePostCount": 0,
"postCount": 0
},
"postCount": 0
}
```
### ListResult<TagVo\>
```json title="ListResult<TagVo>"
{
"page": 0,
"size": 0,
"total": 0,
"items": "List<#TagVo>",
"first": true,
"last": true,
"hasNext": true,
"hasPrevious": true,
"totalPages": 0
}
```
- [#TagVo](#tagvo)

@ -0,0 +1,119 @@
---
title: 主题
description: 主题 - ThemeFinder
---
## activation()
```js
themeFinder.activation()
```
### 描述
获取当前激活的主题。
### 参数
### 返回值
[#ThemeVo](#themevo)
### 示例
```html
<div th:with="theme = ${themeFinder.activation()}">
<h1 th:text="${theme.spec.displayName}"></h1>
<p th:text="${theme.spec.version}"></p>
</div>
```
## getByName(themeName)
```js
themeFinder.getByName(themeName)
```
### 描述
根据主题的唯一标识 `metadata.name` 获取主题。
### 参数
- `themeName:string` - 主题名称
### 返回值
[#ThemeVo](#themevo)
### 示例
```html
<div th:with="theme = ${themeFinder.getByName('theme-foo')}">
<h1 th:text="${theme.spec.displayName}"></h1>
<p th:text="${theme.spec.version}"></p>
</div>
```
## 类型定义
### ThemeVo
```json title="ThemeVo"
{
"metadata": {
"name": "string",
"labels": {
"additionalProp1": "string"
},
"annotations": {
"additionalProp1": "string"
},
"creationTimestamp": "2022-11-20T15:27:15.036Z",
},
"spec": {
"displayName": "string",
"author": {
"name": "string",
"website": "string"
},
"description": "string",
"logo": "string",
"website": "string",
"repo": "string",
"version": "string",
"require": "string",
"settingName": "string",
"configMapName": "string",
"customTemplates": {
"post": [
{
"name": "string",
"description": "string",
"screenshot": "string",
"file": "string"
}
],
"category": [
{
"name": "string",
"description": "string",
"screenshot": "string",
"file": "string"
}
],
"page": [
{
"name": "string",
"description": "string",
"screenshot": "string",
"file": "string"
}
]
}
},
"config": {}
}
```

@ -0,0 +1,104 @@
---
title: 准备工作
description: 主题开发所需的准备工作和基本的项目搭建
---
此文档将讲解 Halo 2.0 主题开发的基本流程,从创建主题项目到最终预览主题效果。
## 搭建开发环境
Halo 在本地开发环境的运行可参考[开发环境运行](../core/run.md),或者使用 [Docker](../../getting-started/install/docker.md) 运行。
:::tip
为了保证在开发时,主题代码可以实时生效,需要注意以下事项:
- 使用 Halo 源码运行时,需要在配置文件中包含如下配置:
```yaml
spring:
thymeleaf:
cache: false
```
- 使用 Docker 运行时,需要添加 `SPRING_THYMELEAF_CACHE=false` 的环境变量。
:::
## 新建一个主题
Halo 的主题存放于工作目录的 `themes` 目录下,即 `~/halo2-dev/themes`,在该目录下新建一个文件夹,例如 `theme-foo`。当前一个最小可被系统加载的主题必须在主题根目录下包含 `theme.yaml` 配置文件。
```yaml title="theme.yaml"
apiVersion: theme.halo.run/v1alpha1
kind: Theme
metadata:
name: theme-foo
spec:
displayName: 示例主题
author:
name: halo-dev
website: https://halo.run
description: 一个示例主题
logo: https://halo.run/logo
website: https://github.com/halo-sigs/theme-foo
repo: https://github.com/halo-sigs/theme-foo.git
settingName: "theme-foo-setting"
configMapName: "theme-foo-configMap"
version: 1.0.0
require: 2.0.0
```
:::info 提示
主题的配置文件详细文档请参考 [配置文件](./config.md)。
:::
:::info 提示
主题项目的目录结构请参考 [主题目录结构](./structure.md)。
:::
## 通过模板创建
目前 Halo 为了让开发者能够尽快搭建主题项目,提供了一些初始模板,开发者可以根据实际需要选择使用。
- [halo-sigs/theme-starter](https://github.com/halo-dev/theme-starter) - 最基础的主题模板,包含了主题的基本目录结构。
- [halo-sigs/theme-vite-starter](https://github.com/halo-dev/theme-vite-starter) - 与 Vite 集成的主题模板,由 Vite 负责资源构建。
- [halo-sigs/theme-modern-starter](https://github.com/halo-dev/theme-modern-starter) - 集成了现代前端技术栈的 Halo 2.0 的主题开发模板。
- [halo-sigs/theme-astro-starter](https://github.com/halo-sigs/theme-astro-starter) - 与 Astro 集成的主题模板,使用 Astro 对模板进行预编译。
:::info 提示
以上 GitHub 都被设置为了模板仓库Template repository点击仓库主页的 `Use this template` 按钮即可通过此模板创建一个新的仓库。
:::
## 创建第一个页面模板
Halo 使用 [Thymeleaf](https://www.thymeleaf.org/) 作为后端模板引擎,后缀为 `.html`,与单纯编写 HTML 一致。在 Halo 的主题中,主题的模板文件存放于 `templates` 目录下,例如 `~/halo2-dev/themes/theme-foo/templates`。为了此文档方便演示,我们先在 `templates` 创建一个首页的模板文件 `index.html`
```html title="templates/index.html"
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title th:text="${site.title}"></title>
</head>
<body>
<h1>Hello World!</h1>
<ul>
<li th:each="post : ${posts.items}">
<a th:href="@{${post.status.permalink}}" th:text="${post.spec.title}"></a>
</li>
</ul>
</body>
</html>
```
## 安装主题
目前我们已经创建好了主题的项目,但并不会直接被 Halo 识别和加载,请按照以下的步骤安装和启用主题:
1. 访问 Console 管理界面,进入主题管理页面。
2. 点击右上角 `切换主题` 按钮,在选择主题弹窗中切换到 `未安装` 页面。
3. 找到我们刚刚创建的主题,点击安装即可。
4. 选择刚刚安装的主题,点击右上角的 `启用` 按钮。
此时 Halo 就已经成功加载并使用了该主题。然后我们访问首页 [http://localhost:8090](http://localhost:8090) 就可以看到我们刚刚编写的 `index.html` 模板渲染后的页面了。

@ -0,0 +1,135 @@
---
title: 设置选项
description: 介绍主题如何定义以及使用设置选项。
---
此文档将讲解如何在主题中定义和使用设置项,如 [表单定义](../form-schema) 中所说,目前 Halo 的 Console 端的所有表单都使用了 [FormKit](https://github.com/formkit/formkit) 的方案。
:::tip
有关 FormKit 定义表单的更多信息,请参考 [表单定义](../form-schema),此文档仅针对主题中的设置项进行讲解。
:::
## 定义表单
在主题中要使用设置项非常简单,只需要在主题根目录提供 `settings.yaml`,然后在 `theme.yaml` 中配置 `spec.settingName``spec.configMapName` 即可,在安装或者初始化主题的时候会自动识别并在 Console 端的主题设置中生成表单。
### 示例
```yaml title="theme-foo/theme.yaml" {14,15}
apiVersion: theme.halo.run/v1alpha1
kind: Theme
metadata:
name: theme-foo
spec:
displayName: 示例主题
author:
name: halo-dev
website: https://halo.run
description: 一个示例主题
logo: https://halo.run/logo
website: https://github.com/halo-sigs/theme-foo
repo: https://github.com/halo-sigs/theme-foo.git
settingName: "theme-foo-setting"
configMapName: "theme-foo-configMap"
version: 1.0.0
require: 2.0.0
```
:::tip
`settingName``configMapName` 必须同时配置,且可以自定义名称,但是 `settingName` 必须和 Setting 的 `metadata.name` 一致。
:::
```yaml title="theme-foo/settings.yaml" {4}
apiVersion: v1alpha1
kind: Setting
metadata:
name: theme-foo-setting
spec:
forms:
- group: style
label: 样式
formSchema:
- $formkit: radio
name: color_scheme
label: 默认配色
value: system
options:
- label: 跟随系统
value: system
- label: 深色
value: dark
- label: 浅色
value: light
- $formkit: color
name: background_color
label: 背景颜色
value: "#f2f2f2"
- group: layout
label: 布局
formSchema:
- $formkit: radio
name: nav
label: 导航栏布局
value: "single"
options:
- label: 单栏
value: "single"
- label: 双栏
value: "double"
```
:::tip
Setting 资源的 `metadata.name` 必须和 `theme.yaml` 中的 `spec.settingName` 一致。
:::
### 在主题模板中使用
在主题模板中,需要以 `theme.config.[group].[name]` 的形式进行调用。
其中:
1. `group`: 即 `spec.forms[].group`,如上面示例中的 `style``layout`
2. `name`: 即 `spec.forms[].formSchema[].name`,如上面示例中的 `color_scheme``nav`
示例:
```html
<body th:class="${theme.config.style.color_scheme}">
<!-- do something -->
</body>
```
```html
<ul th:if="${theme.config.layout.nav == 'single'}">
<!-- do something -->
</ul>
<div th:if="${theme.config.layout.nav == 'double'}">
<!-- do something -->
</div>
```
## 更新配置
`theme.yaml` 一样,`settings.yaml` 也是持久化存储在数据库中的,不会在修改之后主动更新。同样在主题详情页面点击 `重载主题配置` 即可。
![重载主题配置](/img/theme/reload-theme-config.png)
## 从 1.x 迁移
为了方便主题开发者从 1.x 迁移,我们提供了工具用于迁移设置表单配置文件。
工具仓库地址:<https://github.com/halo-sigs/convert-theme-config-to-next>
```bash
# 1.x 版本主题
cd path/to/theme
npx @halo-dev/convert-theme-config-to-next settings
```
执行完成之后即可看到主题目录下生成了 `settings.2.0.yaml` 文件,重命名为 `settings.yaml` 即可。
:::tip
转换完成之后需要修改 `metadata.name` 字段。
:::

@ -0,0 +1,55 @@
---
title: 静态资源
description: 本文档介绍主题的静态资源的引用方法。
---
通过 [目录结构](./structure.md) 的讲解我们可以知道,目前主题的静态资源统一托管在 `/templates/assets/` 目录下,下面讲解一下如何在模板中使用,大致会分为两种引入方式。
## 模板标签引用
```html
<link rel="stylesheet" th:href="@{/assets/dist/style.css}" />
<script th:src="@{/assets/dist/main.iife.js}"></script>
<img th:src="@{/assets/images/logo.png}" />
```
其中 `@{/assets/dist/style.css}` 表示引用 `/templates/assets/dist/style.css` 文件。最终会被渲染为:
```html
<link rel="stylesheet" href="/themes/my-theme/assets/dist/style.css" />
```
## API 引用
以上方式仅支持在 HTML 标签中使用,且必须使用 `@{}` 包裹才能渲染为正确的路径。如果需要在非 HTML 标签中得到正确的路径,我们提供了 `#theme.assets()` API。
:::info 注意
需要注意的是,调用 `#theme.assets()` 的时候,资源地址不需要添加 `/assets/`
:::
比如我们需要在 JavaScript 中异步获取一些资源:
```html {3}
<script th:inline="javascript">
loadScript('[(${#theme.assets("/dist/main.iife.js")})]');
// loadScript('/themes/my-theme/assets/dist/main.iife.js');
function loadScript(url) {
return new Promise(function (resolve, reject) {
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = url;
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
}
</script>
```
:::info 提示
关于在 JavaScript 中使用 Thymeleaf 语法可以参考 Thymeleaf 官方文档:[JavaScript inlining](https://www.thymeleaf.org/doc/tutorials/3.1/usingthymeleaf.html#javascript-inlining)
:::

@ -0,0 +1,33 @@
---
title: 目录结构
description: 主题的目录结构介绍
---
Halo 2.0 的主题基本目录结构如下:
```bash title="~/halo2-dev/themes/my-theme"
my-theme
├── templates/
│ ├── assets/
│ │ ├── css/
│ │ │ └── style.css
│ │ └── js/
│ │ └── main.js
│ ├── index.html
│ ├── post.html
│ ├── page.html
│ ├── tag.html
│ ├── tags.html
│ ├── category.html
│ ├── categories.html
│ └── archives.html
├── theme.yaml
└── settings.yaml
```
详细说明:
1. `/templates/` - 主题模板目录,存放主题模板文件,所有模板都需要放在这个目录。关于模板的详细说明,请查阅 [模板路由](./template-route-mapping)。
2. `/templates/assets/` - 主题静态资源目录,存放主题的静态资源文件,目前静态资源文件只能放在这个目录,引用方式请查阅 [静态资源](./static-resources)。
3. `/theme.yaml` - 主题配置文件,配置主题的基本信息,如主题名称、版本、作者等。详细文档请查阅 [配置文件](./config)。
4. `/settings.yaml` - 主题设置定义文件,配置主题的设置项表单。详细文档请查阅 [设置选项](./settings)。

@ -0,0 +1,87 @@
---
title: 模板路由
description: 本文档介绍路由与模板的映射关系,以及自定义模板。
---
此文档讲解系统内部提供的路由与模板映射。
## 主要模板
### index.html
站点的首页模板,访问地址为 `/`
### post.html
文章详情页面的模板,访问地址默认为 `/archives/:slug`
### page.html
独立页面详情的模板,访问地址默认为 `/:slug`
### archives.html
文章归档页面的模板,访问地址包括:
- `/archives`
- `/archives/:year`
- `/archives/:year/:month`
### tags.html
标签集合页面的模板,访问地址默认为 `/tags`
### tag.html
标签归档页面的模板,访问地址默认为 `/tags/:slug`
### categories.html
分类集合页面的模板,访问地址默认为 `/categories`
### category.html
分类归档页面的模板,访问地址默认为 `/categories/:slug`
## 自定义模板 {#custom-templates}
一般情况下,上文提到的模板已经能够满足大部分的需求,但如果需要针对某个特定的页面进行自定义,可以通过自定义模板来实现。目前系统支持为 **文章**、**独立页面**和**分类归档** 设置自定义模板:
`theme.yaml``spec` 节点下添加如下配置:
```yaml
customTemplates:
{type}:
- name: {name}
description: {description}
screenshot: {screenshot}
file: {file}.html
```
示例:
```yaml
customTemplates:
post:
- name: 文档
description: 文档类型的文章
screenshot:
file: post_documentation.html
```
字段说明:
- `type`:模板类型,目前支持 `post` `page` `category`
- `name`:模板名称
- `description`:模板描述
- `screenshot`:模板预览图
- `file`:模板文件名,需要在 `/templates/` 目录下创建
最终使用者即可在文章设置、独立页面设置、分类设置中选择自定义模板。
:::info 提示
1. 自定义模板与默认模板的功能相同,区别仅在于可以让使用者选择不同于默认模板风格的模板。
2. 自定义模板的文件名需要以 `.html` 结尾,且需要在 `/templates/` 目录下创建。
:::

@ -0,0 +1,7 @@
---
title: 模板变量
---
import DocCardList from '@theme/DocCardList';
<DocCardList />

@ -0,0 +1,234 @@
---
title: 文章归档
description: archives.html - /archives
---
## 路由信息
- 模板路径:`/templates/archives.html`
- 访问路径
- `/archives`
- `/archives/:year`
- `/archives/:year/:month`
## 变量
### archives
#### 变量类型
[#UrlContextListResult<PostArchiveVo\>](#urlcontextlistresultpostarchivevo)
#### 示例
```html title="/templates/archives.html"
<th:block th:each="archive : ${archives.items}">
<h1 th:text="${archive.year}"></h1>
<ul>
<th:block th:each="month : ${archive.months}">
<li th:each="post : ${month.posts}">
<a
th:href="@{${post.status.permalink}}"
th:text="${post.spec.title}">
</a>
</li>
</th:block>
</ul>
</th:block>
<div th:if="${archives.hasPrevious() || archives.hasNext()}">
<a
th:href="@{${archives.prevUrl}}"
>
<span>上一页</span>
</a>
<span th:text="${archives.page} +' / '+ ${archives.total}"></span>
<a
th:href="@{${archives.nextUrl}}"
>
<span>下一页</span>
</a>
</div>
```
## 类型定义
### CategoryVo
```json title="CategoryVo"
{
"metadata": {
"name": "string",
"labels": {
"additionalProp1": "string"
},
"annotations": {
"additionalProp1": "string"
},
"creationTimestamp": "2022-11-20T13:06:38.512Z",
},
"spec": {
"displayName": "string",
"slug": "string",
"description": "string",
"cover": "string",
"template": "string",
"priority": 0,
"children": [
"string"
]
},
"status": {
"permalink": "string",
"postCount": 0,
"visiblePostCount": 0
},
"postCount": 0
}
```
### TagVo
```json title="TagVo"
{
"metadata": {
"name": "string",
"labels": {
"additionalProp1": "string"
},
"annotations": {
"additionalProp1": "string"
},
"creationTimestamp": "2022-11-20T13:06:38.512Z",
},
"spec": {
"displayName": "string",
"slug": "string",
"color": "#F9fEB1",
"cover": "string"
},
"status": {
"permalink": "string",
"visiblePostCount": 0,
"postCount": 0
},
"postCount": 0
}
```
### ListedPostVo
```json title="ListedPostVo"
{
"metadata": {
"name": "string",
"labels": {
"additionalProp1": "string"
},
"annotations": {
"additionalProp1": "string"
},
"creationTimestamp": "2022-11-20T13:06:38.505Z",
},
"spec": {
"title": "string",
"slug": "string",
"releaseSnapshot": "string",
"headSnapshot": "string",
"baseSnapshot": "string",
"owner": "string",
"template": "string",
"cover": "string",
"deleted": false,
"publish": false,
"publishTime": "2022-11-20T13:06:38.505Z",
"pinned": false,
"allowComment": true,
"visible": "PUBLIC",
"priority": 0,
"excerpt": {
"autoGenerate": true,
"raw": "string"
},
"categories": [
"string"
],
"tags": [
"string"
],
"htmlMetas": [
{
"additionalProp1": "string"
}
]
},
"status": {
"permalink": "string",
"excerpt": "string",
"inProgress": true,
"commentsCount": 0,
"contributors": [
"string"
]
},
"categories": "List<#CategoryVo>",
"tags": "List<#TagVo>",
"contributors": [
{
"name": "string",
"displayName": "string",
"avatar": "string",
"bio": "string"
}
],
"owner": {
"name": "string",
"displayName": "string",
"avatar": "string",
"bio": "string"
},
"stats": {
"visit": 0,
"upvote": 0,
"comment": 0
}
}
```
- [#CategoryVo](#categoryvo)
- [#TagVo](#tagvo)
### PostArchiveVo
```json title="PostArchiveVo"
{
"year": "string",
"months": [
{
"month": "string",
"posts": "#ListedPostVo"
}
]
}
```
- [#ListedPostVo](#listedpostvo)
### UrlContextListResult<PostArchiveVo\>
```json title="UrlContextListResult<PostArchiveVo>"
{
"page": 0,
"size": 0,
"total": 0,
"items": "List<#PostArchiveVo>",
"first": true,
"last": true,
"hasNext": true,
"hasPrevious": true,
"totalPages": 0,
"nextUrl": "string",
"prevUrl": "string"
}
```
- [#PostArchiveVo](#postarchivevo)

@ -0,0 +1,84 @@
---
title: 文章分类集合
description: categories.html - /categories
---
## 路由信息
- 模板路径:`/templates/categories.html`
- 访问路径:`/categories`
## 变量
### categories
#### 变量类型
List<[#CategoryTreeVo](#categorytreevo)>
#### 示例
```html title="/templates/categories.html"
<ul>
<li th:replace="~{modules/category-tree :: single(categories=${categories})}" />
</ul>
```
```html title="/templates/category-tree.html"
<ul th:fragment="next (categories)">
<li th:fragment="single (categories)" th:each="category : ${categories}">
<a th:href="@{${category.status.permalink}}">
<span th:text="${category.spec.displayName}"> </span>
</a>
<th:block th:if="${not #lists.isEmpty(category.children)}">
<th:block th:replace="~{modules/category-tree :: next (categories=${category.children})}"></th:block>
</th:block>
</li>
</ul>
```
### _templateId
#### 变量值
`categories`
## 类型定义
### CategoryTreeVo
```json title="CategoryTreeVo"
{
"metadata": {
"name": "string",
"labels": {
"additionalProp1": "string"
},
"annotations": {
"additionalProp1": "string"
},
"creationTimestamp": "2022-11-20T14:18:49.230Z",
},
"spec": {
"displayName": "string",
"slug": "string",
"description": "string",
"cover": "string",
"template": "string",
"priority": 0,
"children": [
"string"
]
},
"status": {
"permalink": "string",
"postCount": 0,
"visiblePostCount": 0
},
"children": "List<#CategoryTreeVo>",
"parentName": "string",
"postCount": 0
}
```
- [#CategoryTreeVo](#categorytreevo)

@ -0,0 +1,225 @@
---
title: 分类归档
description: category.html - /categories/:slug
---
## 路由信息
- 模板路径:`/templates/category.html`
- 访问路径:`/categories/:slug`
## 变量
### category
#### 变量类型
[#CategoryVo](#categoryvo)
### posts
#### 变量类型
[#UrlContextListResult<ListedPostVo\>](#urlcontextlistresultlistedpostvo)
#### 示例
```html title="/templates/category.html"
<div>
<h1 th:text="${category.spec.displayName}"></h1>
<ul>
<li th:each="post : ${posts.items}">
<a
th:text="${post.spec.title}"
th:href="${post.status.permalink}"
></a>
</li>
</ul>
<div th:if="${posts.hasPrevious() || posts.hasNext()}">
<a
th:href="@{${posts.prevUrl}}"
>
<span>上一页</span>
</a>
<span th:text="${posts.page} +' / '+ ${posts.total}"></span>
<a
th:href="@{${posts.nextUrl}}"
>
<span>下一页</span>
</a>
</div>
</div>
```
### _templateId
#### 变量值
`category`
## 类型定义
### CategoryVo
```json title="CategoryVo"
{
"metadata": {
"name": "string",
"labels": {
"additionalProp1": "string"
},
"annotations": {
"additionalProp1": "string"
},
"creationTimestamp": "2022-11-20T13:06:38.512Z",
},
"spec": {
"displayName": "string",
"slug": "string",
"description": "string",
"cover": "string",
"template": "string",
"priority": 0,
"children": [
"string"
]
},
"status": {
"permalink": "string",
"postCount": 0,
"visiblePostCount": 0
},
"postCount": 0
}
```
### TagVo
```json title="TagVo"
{
"metadata": {
"name": "string",
"labels": {
"additionalProp1": "string"
},
"annotations": {
"additionalProp1": "string"
},
"creationTimestamp": "2022-11-20T13:06:38.512Z",
},
"spec": {
"displayName": "string",
"slug": "string",
"color": "#F9fEB1",
"cover": "string"
},
"status": {
"permalink": "string",
"visiblePostCount": 0,
"postCount": 0
},
"postCount": 0
}
```
### ListedPostVo
```json title="ListedPostVo"
{
"metadata": {
"name": "string",
"labels": {
"additionalProp1": "string"
},
"annotations": {
"additionalProp1": "string"
},
"creationTimestamp": "2022-11-20T13:06:38.505Z",
},
"spec": {
"title": "string",
"slug": "string",
"releaseSnapshot": "string",
"headSnapshot": "string",
"baseSnapshot": "string",
"owner": "string",
"template": "string",
"cover": "string",
"deleted": false,
"publish": false,
"publishTime": "2022-11-20T13:06:38.505Z",
"pinned": false,
"allowComment": true,
"visible": "PUBLIC",
"priority": 0,
"excerpt": {
"autoGenerate": true,
"raw": "string"
},
"categories": [
"string"
],
"tags": [
"string"
],
"htmlMetas": [
{
"additionalProp1": "string"
}
]
},
"status": {
"permalink": "string",
"excerpt": "string",
"inProgress": true,
"commentsCount": 0,
"contributors": [
"string"
]
},
"categories": "List<#CategoryVo>",
"tags": "List<#TagVo>",
"contributors": [
{
"name": "string",
"displayName": "string",
"avatar": "string",
"bio": "string"
}
],
"owner": {
"name": "string",
"displayName": "string",
"avatar": "string",
"bio": "string"
},
"stats": {
"visit": 0,
"upvote": 0,
"comment": 0
}
}
```
- [#CategoryVo](#categoryvo)
- [#TagVo](#tagvo)
### UrlContextListResult<ListedPostVo\>
```json title="UrlContextListResult<ListedPostVo>"
{
"page": 0,
"size": 0,
"total": 0,
"items": "List<#ListedPostVo>",
"first": true,
"last": true,
"hasNext": true,
"hasPrevious": true,
"totalPages": 0,
"nextUrl": "string",
"prevUrl": "string"
}
```
- [#ListedPostVo](#listedpostvo)

@ -0,0 +1,218 @@
---
title: 首页
description: index.html - /
---
## 路由信息
- 模板路径:`/templates/index.html`
- 访问路径:`/`
## 变量
### posts
#### 变量类型
[#UrlContextListResult<ListedPostVo\>](#urlcontextlistresultlistedpostvo)
#### 示例
```html title="/templates/index.html"
<div>
<ul>
<li th:each="post : ${posts.items}">
<a
th:text="${post.spec.title}"
th:href="${post.status.permalink}"
></a>
</li>
</ul>
<div th:if="${posts.hasPrevious() || posts.hasNext()}">
<a
th:href="@{${posts.prevUrl}}"
>
<span>上一页</span>
</a>
<span th:text="${posts.page} +' / '+ ${posts.total}"></span>
<a
th:href="@{${posts.nextUrl}}"
>
<span>下一页</span>
</a>
</div>
</div>
```
### _templateId
#### 变量值
`index`
## 类型定义
### CategoryVo
```json title="CategoryVo"
{
"metadata": {
"name": "string",
"labels": {
"additionalProp1": "string"
},
"annotations": {
"additionalProp1": "string"
},
"creationTimestamp": "2022-11-20T13:06:38.512Z",
},
"spec": {
"displayName": "string",
"slug": "string",
"description": "string",
"cover": "string",
"template": "string",
"priority": 0,
"children": [
"string"
]
},
"status": {
"permalink": "string",
"postCount": 0,
"visiblePostCount": 0
},
"postCount": 0
}
```
### TagVo
```json title="TagVo"
{
"metadata": {
"name": "string",
"labels": {
"additionalProp1": "string"
},
"annotations": {
"additionalProp1": "string"
},
"creationTimestamp": "2022-11-20T13:06:38.512Z",
},
"spec": {
"displayName": "string",
"slug": "string",
"color": "#F9fEB1",
"cover": "string"
},
"status": {
"permalink": "string",
"visiblePostCount": 0,
"postCount": 0
},
"postCount": 0
}
```
### ListedPostVo
```json title="ListedPostVo"
{
"metadata": {
"name": "string",
"labels": {
"additionalProp1": "string"
},
"annotations": {
"additionalProp1": "string"
},
"creationTimestamp": "2022-11-20T13:06:38.505Z",
},
"spec": {
"title": "string",
"slug": "string",
"releaseSnapshot": "string",
"headSnapshot": "string",
"baseSnapshot": "string",
"owner": "string",
"template": "string",
"cover": "string",
"deleted": false,
"publish": false,
"publishTime": "2022-11-20T13:06:38.505Z",
"pinned": false,
"allowComment": true,
"visible": "PUBLIC",
"priority": 0,
"excerpt": {
"autoGenerate": true,
"raw": "string"
},
"categories": [
"string"
],
"tags": [
"string"
],
"htmlMetas": [
{
"additionalProp1": "string"
}
]
},
"status": {
"permalink": "string",
"excerpt": "string",
"inProgress": true,
"commentsCount": 0,
"contributors": [
"string"
]
},
"categories": "List<#CategoryVo>",
"tags": "List<#TagVo>",
"contributors": [
{
"name": "string",
"displayName": "string",
"avatar": "string",
"bio": "string"
}
],
"owner": {
"name": "string",
"displayName": "string",
"avatar": "string",
"bio": "string"
},
"stats": {
"visit": 0,
"upvote": 0,
"comment": 0
}
}
```
- [#CategoryVo](#categoryvo)
- [#TagVo](#tagvo)
### UrlContextListResult<ListedPostVo\>
```json title="UrlContextListResult<ListedPostVo>"
{
"page": 0,
"size": 0,
"total": 0,
"items": "List<#ListedPostVo>",
"first": true,
"last": true,
"hasNext": true,
"hasPrevious": true,
"totalPages": 0,
"nextUrl": "string",
"prevUrl": "string"
}
```
- [#ListedPostVo](#listedpostvo)

@ -0,0 +1,109 @@
---
title: 独立页面
description: page.html - /:slug
---
## 路由信息
- 模板路径:`/templates/page.html`
- 访问路径:`/:slug`
## 变量
### singlePage
#### 变量类型
[#SinglePageVo](#singlepagevo)
#### 示例
```html title="/templates/page.html"
<article>
<h1 th:text="${singlePage.spec.title}"></h1>
<div th:utext="${singlePage.content.content}"> </div>
</article>
```
### _templateId
#### 变量值
`page`
## 类型定义
### SinglePageVo
```json title="SinglePageVo"
{
"metadata": {
"name": "string",
"labels": {
"additionalProp1": "string"
},
"annotations": {
"additionalProp1": "string"
},
"creationTimestamp": "2022-11-20T14:29:44.601Z",
},
"spec": {
"title": "string",
"slug": "string",
"releaseSnapshot": "string",
"headSnapshot": "string",
"baseSnapshot": "string",
"owner": "string",
"template": "string",
"cover": "string",
"deleted": false,
"publish": false,
"publishTime": "2022-11-20T14:29:44.601Z",
"pinned": false,
"allowComment": true,
"visible": "PUBLIC",
"priority": 0,
"excerpt": {
"autoGenerate": true,
"raw": "string"
},
"htmlMetas": [
{
"additionalProp1": "string"
}
]
},
"status": {
"permalink": "string",
"excerpt": "string",
"inProgress": true,
"commentsCount": 0,
"contributors": [
"string"
]
},
"stats": {
"visit": 0,
"upvote": 0,
"comment": 0
},
"contributors": [
{
"name": "string",
"displayName": "string",
"avatar": "string",
"bio": "string"
}
],
"owner": {
"name": "string",
"displayName": "string",
"avatar": "string",
"bio": "string"
},
"content": {
"raw": "string",
"content": "string"
}
}
```

@ -0,0 +1,182 @@
---
title: 文章
description: post.html - /archives/:slug
---
## 路由信息
- 模板路径:`/templates/post.html`
- 访问路径:`/archives/:slug`
## 变量
### post
#### 变量类型
[#PostVo](#postvo)
#### 示例
```html title="/templates/post.html"
<article>
<h1 th:text="${post.spec.title}"></h1>
<div th:utext="${post.content.content}"> </div>
</article>
```
### _templateId
#### 变量值
`post`
## 类型定义
### CategoryVo
```json title="CategoryVo"
{
"metadata": {
"name": "string",
"labels": {
"additionalProp1": "string"
},
"annotations": {
"additionalProp1": "string"
},
"creationTimestamp": "2022-11-20T13:06:38.512Z",
},
"spec": {
"displayName": "string",
"slug": "string",
"description": "string",
"cover": "string",
"template": "string",
"priority": 0,
"children": [
"string"
]
},
"status": {
"permalink": "string",
"postCount": 0,
"visiblePostCount": 0
},
"postCount": 0
}
```
### TagVo
```json title="TagVo"
{
"metadata": {
"name": "string",
"labels": {
"additionalProp1": "string"
},
"annotations": {
"additionalProp1": "string"
},
"creationTimestamp": "2022-11-20T13:06:38.512Z",
},
"spec": {
"displayName": "string",
"slug": "string",
"color": "#F9fEB1",
"cover": "string"
},
"status": {
"permalink": "string",
"visiblePostCount": 0,
"postCount": 0
},
"postCount": 0
}
```
### PostVo
```json title="PostVo"
{
"metadata": {
"name": "string",
"labels": {
"additionalProp1": "string"
},
"annotations": {
"additionalProp1": "string"
},
"creationTimestamp": "2022-11-20T12:45:43.888Z",
},
"spec": {
"title": "string",
"slug": "string",
"releaseSnapshot": "string",
"headSnapshot": "string",
"baseSnapshot": "string",
"owner": "string",
"template": "string",
"cover": "string",
"deleted": false,
"publish": false,
"publishTime": "2022-11-20T12:45:43.888Z",
"pinned": false,
"allowComment": true,
"visible": "PUBLIC",
"priority": 0,
"excerpt": {
"autoGenerate": true,
"raw": "string"
},
"categories": [
"string"
],
"tags": [
"string"
],
"htmlMetas": [
{
"additionalProp1": "string"
}
]
},
"status": {
"permalink": "string",
"excerpt": "string",
"commentsCount": 0,
"contributors": [
"string"
]
},
"categories": "List<#CategoryVo>",
"tags": "List<#TagVo>",
"contributors": [
{
"name": "string",
"displayName": "string",
"avatar": "string",
"bio": "string"
}
],
"owner": {
"name": "string",
"displayName": "string",
"avatar": "string",
"bio": "string"
},
"stats": {
"visit": 0,
"upvote": 0,
"comment": 0
},
"content": {
"raw": "string",
"content": "string"
}
}
```
- [#CategoryVo](#categoryvo)
- [#TagVo](#tagvo)

@ -0,0 +1,225 @@
---
title: 标签归档
description: tag.html - /tags/:slug
---
## 路由信息
- 模板路径:`/templates/tag.html`
- 访问路径:`/tags/:slug`
## 变量
### tag
#### 变量类型
[#TagVo](#tagvo)
### posts
#### 变量类型
[#UrlContextListResult<ListedPostVo\>](#urlcontextlistresultlistedpostvo)
#### 示例
```html title="/templates/tag.html"
<div>
<h1 th:text="${tag.spec.displayName}"></h1>
<ul>
<li th:each="post : ${posts.items}">
<a
th:text="${post.spec.title}"
th:href="${post.status.permalink}"
></a>
</li>
</ul>
<div th:if="${posts.hasPrevious() || posts.hasNext()}">
<a
th:href="@{${posts.prevUrl}}"
>
<span>上一页</span>
</a>
<span th:text="${posts.page} +' / '+ ${posts.total}"></span>
<a
th:href="@{${posts.nextUrl}}"
>
<span>下一页</span>
</a>
</div>
</div>
```
### _templateId
#### 变量值
`tag`
## 类型定义
### CategoryVo
```json title="CategoryVo"
{
"metadata": {
"name": "string",
"labels": {
"additionalProp1": "string"
},
"annotations": {
"additionalProp1": "string"
},
"creationTimestamp": "2022-11-20T13:06:38.512Z",
},
"spec": {
"displayName": "string",
"slug": "string",
"description": "string",
"cover": "string",
"template": "string",
"priority": 0,
"children": [
"string"
]
},
"status": {
"permalink": "string",
"postCount": 0,
"visiblePostCount": 0
},
"postCount": 0
}
```
### TagVo
```json title="TagVo"
{
"metadata": {
"name": "string",
"labels": {
"additionalProp1": "string"
},
"annotations": {
"additionalProp1": "string"
},
"creationTimestamp": "2022-11-20T13:06:38.512Z",
},
"spec": {
"displayName": "string",
"slug": "string",
"color": "#F9fEB1",
"cover": "string"
},
"status": {
"permalink": "string",
"visiblePostCount": 0,
"postCount": 0
},
"postCount": 0
}
```
### ListedPostVo
```json title="ListedPostVo"
{
"metadata": {
"name": "string",
"labels": {
"additionalProp1": "string"
},
"annotations": {
"additionalProp1": "string"
},
"creationTimestamp": "2022-11-20T13:06:38.505Z",
},
"spec": {
"title": "string",
"slug": "string",
"releaseSnapshot": "string",
"headSnapshot": "string",
"baseSnapshot": "string",
"owner": "string",
"template": "string",
"cover": "string",
"deleted": false,
"publish": false,
"publishTime": "2022-11-20T13:06:38.505Z",
"pinned": false,
"allowComment": true,
"visible": "PUBLIC",
"priority": 0,
"excerpt": {
"autoGenerate": true,
"raw": "string"
},
"categories": [
"string"
],
"tags": [
"string"
],
"htmlMetas": [
{
"additionalProp1": "string"
}
]
},
"status": {
"permalink": "string",
"excerpt": "string",
"inProgress": true,
"commentsCount": 0,
"contributors": [
"string"
]
},
"categories": "List<#CategoryVo>",
"tags": "List<#TagVo>",
"contributors": [
{
"name": "string",
"displayName": "string",
"avatar": "string",
"bio": "string"
}
],
"owner": {
"name": "string",
"displayName": "string",
"avatar": "string",
"bio": "string"
},
"stats": {
"visit": 0,
"upvote": 0,
"comment": 0
}
}
```
- [#CategoryVo](#categoryvo)
- [#TagVo](#tagvo)
### UrlContextListResult<ListedPostVo\>
```json title="UrlContextListResult<ListedPostVo>"
{
"page": 0,
"size": 0,
"total": 0,
"items": "List<#ListedPostVo>",
"first": true,
"last": true,
"hasNext": true,
"hasPrevious": true,
"totalPages": 0,
"nextUrl": "string",
"prevUrl": "string"
}
```
- [#ListedPostVo](#listedpostvo)

@ -0,0 +1,61 @@
---
title: 文章标签集合
description: tags.html - /tags
---
## 路由信息
- 模板路径:`/templates/tags.html`
- 访问路径:`/tags`
## 变量
### tags
#### 变量类型
List<[#TagVo](#tagvo)>
#### 示例
```html title="/templates/tags.html"
<ul>
<li th:each="tag : ${tags}" th:text="${tag.spec.displayName}" th:href="${tag.status.permalink}" />
</ul>
```
### _templateId
#### 变量值
`tags`
## 类型定义
### TagVo
```json title="TagVo"
{
"metadata": {
"name": "string",
"labels": {
"additionalProp1": "string"
},
"annotations": {
"additionalProp1": "string"
},
"creationTimestamp": "2022-11-20T13:06:38.512Z",
},
"spec": {
"displayName": "string",
"slug": "string",
"color": "#F9fEB1",
"cover": "string"
},
"status": {
"permalink": "string",
"visiblePostCount": 0,
"postCount": 0
},
"postCount": 0
}
```

@ -0,0 +1,238 @@
---
title: 配置参考
description: Halo 配置文件的详细介绍及参考
---
Halo 的配置文件名为 `application.yaml`,其必须位于[工作目录](/getting-started/prepare#工作目录) `~/.halo` 下。 Halo 会读取该目录下的配置文件进行加载。
:::info
如果您是单独下载的官方配置文件,则必须将文件名 application-template.yaml 重命名为 application.yaml
:::
如下将详细列出配置文件 `application.yaml` 中所有的配置项。
## 基础配置
基础配置中的配置设置一般来说是**必要的**,且必须在 application.yaml 里进行定义。
### 端口
用于指定 HTTP 服务器监听的端口Halo 默认设置为 `8090`
```yaml
server:
port: 8090
```
请注意,如果您选择设置端口为 `80`,则需要确保您的 80 端口未被占用,通常**不建议**直接设置为 80 端口。
### 数据库
Halo 目前支持 `H2``MySQL` 数据库。
:::tip
得益于我们使用的 ORM 框架Halo 在首次启动的时候会自动根据实体类创建表结构,无需通过 SQL 脚本自行创建,也不会提供所谓的 SQL 脚本。所以此步骤仅需配置好数据库连接地址和用户名密码即可。注意H2 无需手动创建数据库MySQL 需要。
:::
#### H2
:::info
推荐使用 **H2**,较其他数据库来说更为方便。
:::
```yaml
spring:
datasource:
driver-class-name: org.h2.Driver
url: jdbc:h2:file:~/.halo/db/halo
username: admin
password: 123456
h2:
console:
settings:
web-allow-others: false
path: /h2-console
enabled: false
```
**注意事项**
- `url` 为默认的数据本地存储地址,请勿修改。
- 默认的数据库账户和密码为 `admin``123456`,建议将其修改,并妥善保存(此用户名和密码在 Halo 第一次启动的时候将自动创建。并且不支持首次启动后,通过修改配置文件中的账户或者密码,如果修改,再次启动将提示用户名或者密码错误。)。
- 线上环境中,`h2` 的配置使用默认即可。如果需要手动修改一些数据,可将 `web-allow-others``enabled` 设为 `true` 来开启 h2 控制台,访问路径为 `ip:端口/h2-console`。`JDBC URL``username``password` 使用配置文件中的即可。
:::warning
特别注意:在开启 `h2-console` 并完成所需操作之后,一定要再次关闭 `h2-console` 并重启,不要长时间将 `h2-console` 处于开启状态,这可能会有隐性的安全风险。
:::
#### MySQL
```yaml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/halodb?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username: root
password: 123456
```
**要求**
1. 版本5.7 +
2. 字符集Character Set`utf8mb4`
3. 排序规则Collate`utf8mb4_bin`
4. 存储引擎:`InnoDB`
综上,建议创建数据库采用下面的命令:
```bash
create database halodb character set utf8mb4 collate utf8mb4_bin;
```
**注意事项**
- `username``password` 需要修改为您的 MySQL 数据库账号和密码。
- 默认时区为 `Asia/Shanghai`,如果与您所在时区不一致,则可以修改为您所在的时区。
## 高级配置
高级配置中的配置设置是可选的,如果不需要,可以略过。
### 后台路径
Halo 支持自定义后台管理的**根路径**。
```yaml
halo:
# Your admin client path is https://your-domain/{admin-path}
admin-path: admin
```
注意:仅为改动后台管理的根路径,因此前后不带 `/`
### 缓存
某些情况下,需要用户根据需求来设置缓存数据的保存方式,例如将缓存数据持久化保存在本地。
```yaml
halo:
# memory or level or redis
cache: memory
```
目前支持三种策略:
- `memory` 将数据缓存至内存,重启服务缓存将清空。
- `level` 将数据缓存至本地,重启服务不会清空缓存。
- `redis` 将数据缓存至 Redis重启服务不会清空缓存如需分布式部署 Halo请选用此种缓存方式。
**注意事项**
- 如果选用 Redis 缓存方式,请在配置文件加入 Redis 相关配置,完整的配置示例如下:
```yaml
server:
port: 8090
# Response data gzip.
compression:
enabled: true
spring:
datasource:
# MySQL database configuration.
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/halodb?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username: root
password: 123456
redis:
# Redis cache configuration.
port: 6379
database: 0
host: 127.0.0.1
password: 123456
halo:
# Your admin client path is https://your-domain/{admin-path}
admin-path: admin
# memory or level or redis
cache: redis
```
### 压缩
启用压缩对于减少带宽和加快页面加载非常有用,在**未使用** `Nginx``Caddy` 等反向代理服务器时(反向代理服务器通常是默认开启 Gzip 的),可以考虑开启系统自带的 Gzip 功能。
```yaml
server:
# Response data gzip.
compression:
enabled: true
```
## 示例配置文件
:::info
建议根据使用的数据库类型查看。
:::
### H2 Database
```yaml
server:
port: 8090
# Response data gzip.
compression:
enabled: true
spring:
datasource:
# H2 database configuration.
driver-class-name: org.h2.Driver
url: jdbc:h2:file:~/.halo/db/halo
username: admin
password: 123456
# H2 database console configuration.
h2:
console:
settings:
web-allow-others: false
path: /h2-console
enabled: false
halo:
# Your admin client path is https://your-domain/{admin-path}
admin-path: admin
# memory or level or redis
cache: memory
```
### MySQL
```yaml
server:
port: 8090
# Response data gzip.
compression:
enabled: true
spring:
datasource:
# MySQL database configuration.
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/halodb?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username: root
password: 123456
halo:
# Your admin client path is https://your-domain/{admin-path}
admin-path: admin
# memory or level or redis
cache: memory
```
官方的完整示例配置文件可以在 [https://dl.halo.run/config/](https://dl.halo.run/config/) 下找到。

@ -0,0 +1,25 @@
---
title: 资源下载
description: 目前所有与 Halo 相关的下载地址
---
## GitHub
:::note
如果您的服务器在海外,推荐从 GitHub 下载。
:::
- [运行包](https://github.com/halo-dev/halo/releases)
- [配置文件](https://github.com/halo-dev/halo-common)
## 官方镜像源
- [https://download.halo.run](https://download.halo.run)
此镜像源由 [Nova Kwok](https://nova.moe/) 提供并维护。
## 三方镜像源
- [https://halo.cary.tech](https://halo.cary.tech)
此镜像源由 [新逸Cary](https://blog.xinac.cn) 提供并维护。

@ -0,0 +1,38 @@
---
title: 第一篇文章
description: 安装完成后,如何写下第一篇文章。
---
![第一篇文章](/img/first-post.gif)
## 登录管理端
浏览器访问 `$HALO_EXTERNAL_URL/console/`(外部访问链接)即可进入 Halo 管理端。默认的管理员用户名为 `admin`,登录密码为安装时设置的 `HALO_SECURITY_INITIALIZER_SUPERADMINPASSWORD`,文档中的默认值为 `P@88w0rd`
:::info
如果你在安装时没有设置 `HALO_SECURITY_INITIALIZER_SUPERADMINPASSWORD` 变量或忘记了设置的密码,可以参考[常见问题中的忘记密码章节](../user-guide/faq.md)进行处理。
:::
## 新建文章
Halo 安装完成后,默认初始化了一篇 `Hello Halo` 文章,接下来我们将创建并发布一篇自己的文章。
在 Halo 管理端,点击仪表盘页面中的 `创建文章` 快捷入口,即可进入到文章编辑页面。
在文章编辑器中,你可以尽情写下你想展现的内容。
当内容编辑完成后,点击右上角的 `发布` 按钮,给文章设置一个合适的标题和别名,同时可以设置文章所属分类、标签及其他一些高级设置。
确认无误后,点击弹框下方的 `发布` 按钮,我们的第一篇文章就成功发布了。
:::info
关于文章相关其他功能及文章各种设置项的说明,请参考《[用户指南-文章](../user-guide/posts.md)》章节
:::
## 查看文章
文章发布成功后,就可以在主题端查看到我们刚刚创建的文章了。
浏览器访问 `$HALO_EXTERNAL_URL` 进入站点首页,站点展示的内容及样式由当前启用的主题所决定。
接下来,选一款喜欢的主题,尽情体验 Halo 吧!

@ -0,0 +1,265 @@
---
title: 使用 Docker Compose 部署
description: 使用 Docker Compose 部署
---
:::info
在继续操作之前,我们推荐您先阅读[《写在前面》](../prepare.md),这可以快速帮助你了解 Halo。
:::
## 创建容器组
可用的 Halo 2.0.0 的 Docker 镜像:
- [halohub/halo](https://hub.docker.com/r/halohub/halo)
- [ghcr.io/halo-dev/halo](https://github.com/halo-dev/halo/pkgs/container/halo)
:::info 注意
目前 Halo 2.0 并未更新 Docker 的 latest 标签镜像,主要因为 2.0 不兼容 1.x 版本,防止使用者误操作。我们推荐使用固定版本的标签,比如 `halohub/halo:2.0.0`
:::
1. 在系统任意位置创建一个文件夹,此文档以 `~/halo` 为例。
```bash
mkdir ~/halo && cd ~/halo
```
:::info
注意后续操作中Halo 产生的所有数据都会保存在这个目录,请妥善保存。
:::
2. 创建 `docker-compose.yaml`
此文档提供两种场景的 Docker Compose 配置文件,请根据你的需要选择一种。
:::info
需要注意的是,此文档为了更加方便的管理配置,所有与 Halo 相关的配置都使用 Docker 环境变量代替,所以无需创建 application.yaml 文件。
:::
1. 创建 Halo + PostgreSQL 的实例:
```yaml {18-28,46} title="~/halo/docker-compose.yaml"
version: "3"
services:
halo:
image: halohub/halo:2.0.0
container_name: halo
restart: on-failure:3
depends_on:
halodb:
condition: service_healthy
networks:
halo_network:
volumes:
- ./:/root/.halo2
ports:
- "8090:8090"
environment:
- SPRING_R2DBC_URL=r2dbc:pool:postgresql://halodb/halo
- SPRING_R2DBC_USERNAME=halo
# PostgreSQL 的密码,请保证与下方 POSTGRES_PASSWORD 的变量值一致。
- SPRING_R2DBC_PASSWORD=openpostgresql
- SPRING_SQL_INIT_PLATFORM=postgresql
# 外部访问地址,请根据实际需要修改
- HALO_EXTERNAL_URL=http://localhost:8090/
# 初始化的超级管理员用户名
- HALO_SECURITY_INITIALIZER_SUPERADMINUSERNAME=admin
# 初始化的超级管理员密码
- HALO_SECURITY_INITIALIZER_SUPERADMINPASSWORD=P@88w0rd
halodb:
image: postgres:latest
container_name: halodb
restart: on-failure:3
networks:
halo_network:
volumes:
- ./db:/var/lib/postgresql/data
ports:
- "5432:5432"
healthcheck:
test: [ "CMD", "pg_isready" ]
interval: 10s
timeout: 5s
retries: 5
environment:
- POSTGRES_PASSWORD=openpostgresql
- POSTGRES_USER=halo
- POSTGRES_DB=halo
networks:
halo_network:
```
2. 创建 Halo + MySQL 的实例:
```yaml
version: "3"
services:
halo:
image: halohub/halo:2.0.0
container_name: halo
restart: on-failure:3
depends_on:
halodb:
condition: service_healthy
networks:
halo_network:
volumes:
- ./:/root/.halo2
ports:
- "8090:8090"
environment:
- SPRING_R2DBC_URL=r2dbc:pool:mysql://halodb:3306/halo
- SPRING_R2DBC_USERNAME=root
# MySQL 的密码,请保证与下方 MYSQL_ROOT_PASSWORD 的变量值一致。
- SPRING_R2DBC_PASSWORD=o#DwN&JSa56
- SPRING_SQL_INIT_PLATFORM=mysql
# 外部访问地址,请根据实际需要修改
- HALO_EXTERNAL_URL=http://localhost:8090/
# 初始化的超级管理员用户名
- HALO_SECURITY_INITIALIZER_SUPERADMINUSERNAME=admin
# 初始化的超级管理员密码
- HALO_SECURITY_INITIALIZER_SUPERADMINPASSWORD=P@88w0rd
halodb:
image: mysql:8.0.27
container_name: halodb
restart: on-failure:3
networks:
halo_network:
command: --default-authentication-plugin=mysql_native_password
--character-set-server=utf8mb4
--collation-server=utf8mb4_general_ci
--explicit_defaults_for_timestamp=true
volumes:
- ./mysql:/var/lib/mysql
- ./mysqlBackup:/data/mysqlBackup
ports:
- "3306:3306"
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "--silent"]
interval: 3s
retries: 5
start_period: 30s
environment:
# 请修改此密码,并对应修改上方 Halo 服务的 SPRING_R2DBC_PASSWORD 变量值
- MYSQL_ROOT_PASSWORD=o#DwN&JSa56
- MYSQL_DATABASE=halo
networks:
halo_network:
```
3. 仅创建 Halo 实例(使用默认的 H2 数据库,**不推荐用于生产环境,建议体验和测试的时候使用**
```yaml {13-20} title="~/halo/docker-compose.yaml"
version: "3"
services:
halo:
image: halohub/halo:2.0.0
container_name: halo
restart: on-failure:3
volumes:
- ./:/root/.halo2
ports:
- "8090:8090"
environment:
# 外部访问地址,请根据实际需要修改
- HALO_EXTERNAL_URL=http://localhost:8090/
# 初始化的超级管理员用户名
- HALO_SECURITY_INITIALIZER_SUPERADMINUSERNAME=admin
# 初始化的超级管理员密码
- HALO_SECURITY_INITIALIZER_SUPERADMINPASSWORD=P@88w0rd
```
3. 启动 Halo 服务
```bash
docker-compose up -d
```
实时查看日志:
```bash
docker-compose logs -f
```
4. 用浏览器访问 `$HALO_EXTERNAL_URL/console/`(外部访问链接)即可进入 Halo 管理端。管理员用户名为 `admin`,登录密码为上方设置的 `HALO_SECURITY_INITIALIZER_SUPERADMINPASSWORD`
:::tip
如果需要配置域名访问,建议先配置好反向代理以及域名解析再进行初始化。如果通过 `http://ip:端口号` 的形式无法访问,请到服务器厂商后台将运行的端口号添加到安全组,如果服务器使用了 Linux 面板,请检查此 Linux 面板是否有还有安全组配置,需要同样将端口号添加到安全组。
:::
## 更新容器组
1. 停止运行中的容器组
```bash
cd ~/halo && docker-compose down
```
2. 备份数据(重要)
```bash
cp -r ~/halo ~/halo.archive
```
> 需要注意的是,`halo.archive` 文件名不一定要根据此文档命名,这里仅仅是个示例。
3. 更新 Halo 服务
修改 `docker-compose.yaml` 中配置的镜像版本。
```yaml {3}
services:
halo:
image: halohub/halo:2.0.0
container_name: halo
```
```bash
docker-compose pull
```
```bash
docker-compose up -d
```
## 反向代理
你可以在下面的反向代理软件中任选一项,我们假设你已经安装好了其中一项,并对其的基本操作有一定了解。如果你对 Nginx 不熟悉,我们推荐使用 [OneinStack](../install/other/oneinstack.md) 来管理 Nginx。
### Nginx
```nginx {2,7,10}
upstream halo {
server 127.0.0.1:8090;
}
server {
listen 80;
listen [::]:80;
server_name www.yourdomain.com;
client_max_body_size 1024m;
location / {
proxy_pass http://halo;
proxy_set_header HOST $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
```
### Caddy 2
```txt {1,5}
www.yourdomain.com
encode gzip
reverse_proxy 127.0.0.1:8090
```

@ -0,0 +1,57 @@
---
title: 使用 Docker 部署
description: 使用 Docker 部署
---
:::info
在继续操作之前,我们推荐您先阅读[《写在前面》](../prepare),这可以快速帮助你了解 Halo。
:::
:::tip
此文档仅提供使用默认 H2 数据库的 Docker 运行方式,主要用于体验和测试,在生产环境我们不推荐使用 H2 数据库。
如果需要使用其他数据库部署,我们推荐使用 Docker Compose 部署:[使用 Docker Compose 部署](./docker-compose)
:::
## 使用 Docker 镜像
可用的 Halo 2.0.0 的 Docker 镜像:
- [halohub/halo](https://hub.docker.com/r/halohub/halo)
- [ghcr.io/halo-dev/halo](https://github.com/halo-dev/halo/pkgs/container/halo)
:::info 注意
目前 Halo 2.0 并未更新 Docker 的 latest 标签镜像,主要因为 2.0 不兼容 1.x 版本,防止使用者误操作。我们推荐使用固定版本的标签,比如 `halohub/halo:2.0.0`
:::
1. 创建容器
```bash
docker run \
-it -d \
--name halo \
-p 8090:8090 \
-v ~/.halo2:/root/.halo2 \
-e HALO_EXTERNAL_URL=http://localhost:8090/ \
-e HALO_SECURITY_INITIALIZER_SUPERADMINPASSWORD=P@88w0rd \
halohub/halo:2.0.0
```
:::info
注意:此命令默认使用自带的 H2 Database 数据库。如需使用 PostgreSQL请参考[使用 Docker Compose 部署](./docker-compose)
:::
- **-it**:开启输入功能并连接伪终端
- **-d**:后台运行容器
- **--name**:为容器指定一个名称
- **-p**:端口映射,格式为 `主机(宿主)端口:容器端口` ,可在 `application.yaml` 配置。
- **-v**:工作目录映射。形式为:`-v 宿主机路径:/root/.halo2`,后者不能修改。
- **-e**:环境变量
- `HALO_EXTERNAL_URL`: 外部可访问的链接。例如:<https://域名/>
- `HALO_SECURITY_INITIALIZER_SUPERADMINPASSWORD`: 超级管理员用户admin的初始化密码。如果该环境变量没有设置系统将会生成随机密码并打印在日志中。
1. 用浏览器访问 `$HALO_EXTERNAL_URL/console/`(外部访问链接)即可进入 Halo 管理端。管理员用户名为 `admin`,登录密码为上方设置的 `HALO_SECURITY_INITIALIZER_SUPERADMINPASSWORD`
:::tip
如果需要配置域名访问,建议先配置好反向代理以及域名解析再进行初始化。如果通过 `http://ip:端口号` 的形式无法访问,请到服务器厂商后台将运行的端口号添加到安全组,如果服务器使用了 Linux 面板,请检查此 Linux 面板是否有还有安全组配置,需要同样将端口号添加到安全组。
:::

@ -0,0 +1,274 @@
---
title: 与 OneinStack 配合使用
description: 使用 OneinStack 管理 Halo 服务的反向代理
---
## Halo 部署
参见 [使用 Docker Compose 部署](../docker-compose.md)
:::info
`「反向代理」` 部分不进行操作,保证 Halo 服务运行无误即可。
:::
## 通过 OneinStack 安装 Nginx
点击下方链接进入 OneinStack 官网,仅选择 `安装 Nginx`,其他的都可以取消选择。
<https://oneinstack.com/auto>
最后点击 `复制安装命令` 到服务器执行即可。如果你仅安装 Nginx你的链接应该是这样
```bash
wget -c http://mirrors.linuxeye.com/oneinstack-full.tar.gz && tar xzf oneinstack-full.tar.gz && ./oneinstack/install.sh --nginx_option 1
```
:::info
这一步会经过编译安装,可能会导致安装时间很漫长,这主要取决于你服务器的性能。
:::
出现下面的信息即代表安装成功:
```bash
Nginx installed successfully!
Created symlink from /etc/systemd/system/multi-user.target.wants/nginx.service to /usr/lib/systemd/system/nginx.service.
Redirecting to /bin/systemctl start nginx.service
####################Congratulations########################
Total OneinStack Install Time: 5 minutes
Nginx install dir: /usr/local/nginx
```
## 创建 vhost
> 即创建一个站点,你可以通过这样的方式在你的服务器创建无限个站点。接下来的目的就是创建一个站点,并反向代理到 Halo。这一步在此教程使用 `demo.halo.run` 这个域名做演示,实际情况请修改此域名。
1. 进入到 oneinstack 目录,执行 vhost 创建命令
```bash
cd oneinstack
```
```bash
sh vhost.sh
```
2. 按照提示选择或输入相关信息
```bash
What Are You Doing?
1. Use HTTP Only
2. Use your own SSL Certificate and Key
3. Use Let's Encrypt to Create SSL Certificate and Key
q. Exit
Please input the correct option:
```
这一步是选择证书配置方式,如果你有自己的证书,输入 <kbd>2</kbd> 即可。如果需要使用 `Let's Encrypt` 申请证书,选择 <kbd>3</kbd> 即可。
```bash
Please input domain(example: www.example.com):
```
输入自己的域名即可,前提是已经提前解析好了域名。
```bash
Please input the directory for the domain:demo.halo.run :
(Default directory: /data/wwwroot/demo.halo.run):
```
提示输入站点根目录,因为我们是使用 Nginx 的反向代理,所以这个目录是没有必要配置的,我们直接使用默认的即可(直接回车)。
```bash
Do you want to add more domain name? [y/n]:
```
是否需要添加其他域名,按照需要选择即可,如果不需要,输入 <kbd>n</kbd> 并回车确认。
```bash
Do you want to add hotlink protection? [y/n]:
```
是否需要做防盗链处理,按照需要选择即可。
```bash
Allow Rewrite rule? [y/n]:
```
路径重写配置,我们不需要,选择 <kbd>n</kbd> 回车确定即可。
```bash
Allow Nginx/Tengine/OpenResty access_log? [y/n]:
```
Nginx 的请求日志,建议选择 <kbd>y</kbd>
这样就完成了 vhost 站点的创建,最终会输出站点的相关信息:
```bash
Your domain: demo.halo.run
Virtualhost conf: /usr/local/nginx/conf/vhost/demo.halo.run.conf
Directory of: /data/wwwroot/demo.halo.run
```
Nginx 的配置文件即 `/usr/local/nginx/conf/vhost/demo.halo.run.conf`
## 修改 Nginx 配置文件
上方创建 vhost 的过程并没有创建反向代理的配置,所以需要我们自己修改一下配置文件。
1. 使用你熟悉的工具打开配置文件,此教程使用 vim。
```bash
vim /usr/local/nginx/conf/vhost/demo.halo.run.conf
```
2. 删除一些不必要的配置
```nginx
location ~ [^/]\.php(/|$) {
#fastcgi_pass remote_php_ip:9000;
fastcgi_pass unix:/dev/shm/php-cgi.sock;
fastcgi_index index.php;
include fastcgi.conf;
}
```
此段配置是针对 php 应用的,所以可以删掉。
3. 添加 `upstream` 配置
`server` 的同级节点添加如下配置:
```nginx {2}
upstream halo {
server 127.0.0.1:8090;
}
```
4. 在 `server` 节点添加如下配置
```nginx {6}
location / {
proxy_set_header HOST $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://halo;
}
```
5. 修改 `location ~ .*\.(gif|jpg|jpeg|png|bmp|swf|flv|mp4|ico)$` 节点
```nginx {2}
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf|flv|mp4|ico)$ {
proxy_pass http://halo;
expires 30d;
access_log off;
}
```
6. 修改 `location ~ .*\.(js|css)?$` 节点
```nginx {2}
location ~ .*\.(js|css)?$ {
proxy_pass http://halo;
expires 7d;
access_log off;
}
```
如果不按照第 56 步操作,请求一些图片或者样式文件不会经过 Halo所以请不要忽略此配置。
7. 添加 acme.sh 续签验证路由
OneinStack 使用的 acme.sh 管理证书,如果你在创建 vhost 的时候选择了使用 `Let's Encrypt` 申请证书,那么 OneinStack 会在系统内添加一个定时任务去自动续签证书acme.sh 默认验证站点所有权的方式为在站点根目录生成一个文件(.well-known来做验证由于配置了反向代理所以在验证的时候是无法直接访问到站点目录下的 .well-known 文件夹下的验证文件的。需要添加如下配置:
```nginx {4}
location ^~ /.well-known/acme-challenge/ {
default_type "text/plain";
allow all;
root /data/wwwroot/demo.halo.run/;
}
```
至此,配置修改完毕,保存即可。最终你的配置文件可能如下面配置一样:
```nginx {2,20,29,34,41-47,51}
upstream halo {
server 127.0.0.1:8090;
}
server {
listen 80;
listen [::]:80;
listen 443 ssl http2;
listen [::]:443 ssl http2;
ssl_certificate /usr/local/nginx/conf/ssl/demo.halo.run.crt;
ssl_certificate_key /usr/local/nginx/conf/ssl/demo.halo.run.key;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
ssl_ciphers TLS13-AES-256-GCM-SHA384:TLS13-CHACHA20-POLY1305-SHA256:TLS13-AES-128-GCM-SHA256:TLS13-AES-128-CCM-8-SHA256:TLS13-AES-128-CCM-SHA256:EECDH+CHACHA20:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
ssl_prefer_server_ciphers on;
ssl_session_timeout 10m;
ssl_session_cache builtin:1000 shared:SSL:10m;
ssl_buffer_size 1400;
add_header Strict-Transport-Security max-age=15768000;
ssl_stapling on;
ssl_stapling_verify on;
server_name demo.halo.run;
access_log /data/wwwlogs/demo.halo.run_nginx.log combined;
index index.html index.htm index.php;
root /data/wwwroot/demo.halo.run;
if ($ssl_protocol = "") { return 301 https://$host$request_uri; }
include /usr/local/nginx/conf/rewrite/none.conf;
#error_page 404 /404.html;
#error_page 502 /502.html;
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf|flv|mp4|ico)$ {
proxy_pass http://halo;
expires 30d;
access_log off;
}
location ~ .*\.(js|css)?$ {
proxy_pass http://halo;
expires 7d;
access_log off;
}
location ~ /(\.user\.ini|\.ht|\.git|\.svn|\.project|LICENSE|README\.md) {
deny all;
}
location / {
proxy_set_header HOST $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://halo;
}
location ^~ /.well-known/acme-challenge/ {
default_type "text/plain";
allow all;
root /data/wwwroot/demo.halo.run/;
}
}
```
## 重载 Nginx 使配置生效
验证 nginx 配置
```bash
nginx -t
```
如果输出如下提示则代表配置有效:
```bash
nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful
```
重载 Nginx 配置:
```bash
nginx -s reload
```
至此,整个教程完毕,现在你可以访问域名检查是否已经配置成功。

@ -0,0 +1,17 @@
---
title: 三方指南
---
:::tip
此类指南或教程均来自第三方或者网友提供,我们不能一直保证其具有有效性,请参考的时候仔细甄别。另外,如果您有写类似的教程,也可以[联系我们](mailto:hi@halo.run)放在下方。
:::
## Halo 1.0
- [Halo 博客安装教程,一款优秀的 Java 开源博客系统](https://www.cnblogs.com/bronya0/p/14198512.html)
- [如何用服务器优雅地搭建一个博客—Hello,Halo!](https://www.bilibili.com/video/BV1JN411Q7Na/)
- [Halo+云服务器+Nginx反向代理实现个人博客搭建](https://yusart.xyz/archives/halo%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2%E6%90%AD%E5%BB%BA)
- [如何在 Windows 下搭建 Halo](https://ykanade.cn/2021/08/11/HaloInWindows/)
- [Halo 博客建站记录](https://catchersun.cn/archives/halo%E5%8D%9A%E5%AE%A2%E5%BB%BA%E7%AB%99%E8%AE%B0%E5%BD%95)
- [Windows下搭建halo并配置nginx与SSL](https://blog.shikangsi.cn/archives/halo-windows)
- [Linux环境下如何分布式部署Halo](https://luoxx.top/archives/halo-distributed-deploy)

@ -0,0 +1,57 @@
---
title: 从 Halo 1.x 迁移
description: 从 Halo 1.x 迁移的完整指南和注意事项
---
因为 Halo 2.0 的底层架构变动,无法兼容 1.x 的数据,导致无法平滑升级,所以需要进行数据迁移。为此,我们提供了从 Halo 1.5 / 1.6 版本迁移的插件。在进行迁移之前,**有几点注意事项和要求,如果你目前无法满足,建议先暂缓迁移。**
- Halo 版本必须为 1.5.x 或 1.6.x。如果不满足需要先升级到 1.5.x 或 1.6.x 版本。
- Halo 2.0 不兼容 1.x 的主题,建议在升级前先查询你正在使用的主题是否已经支持 2.0。你可以访问 [halo-sigs/awesome-halo](https://github.com/halo-sigs/awesome-halo) 查阅目前支持的主题。
- Halo 2.0 暂时没有 Markdown 编辑器,**在迁移之后会丢失 Markdown 编辑器的内容**,仅保留已渲染的 HTML 内容以供富文本编辑器使用。如果你对 Markdown 有强需求,建议先暂缓迁移。
- 暂时不支持友情链接、日志、图库的数据迁移,如果你在 1.x 版本中使用了这些功能,建议先暂缓迁移。
- Halo 2.0 的外置云存储目前仅有阿里云 OSS 的插件,如果你对其他外部云存储有强需求,建议先暂缓迁移。
- 关于附件,目前仅支持将本地附件代理,不支持在后台管理,也不支持迁移外部云存储的附件。如果你对外部云存储的附件有强需求,建议先暂缓迁移。
- 在迁移过程中不会保留旧版本的用户数据,迁移完成之后,关于文章等数据的关联都将改为 Halo 2.0 的新用户。
- 迁移插件目前仅支持 Chrome 浏览器。
- 为了防止直接升级 2.0 导致 1.x 的数据受到破坏,我们已经将工作目录由 `~/.halo` 变更为 `~/.halo2`
- 目前 Halo 2.0 仅提供 Docker 部署方式,没有提供可执行 JAR 包。
- 可以考虑先在本地运行一个 Halo 2.0,模拟一下导入,检查导入后是否满足要求。
如果遇到了迁移过程中的问题,也可以向我们提交 Issue: <https://github.com/halo-dev/halo/issues/new/choose>,以上暂不支持的功能我们也会陆续完善。
## 备份数据
在进行迁移操作之前,我们强烈建议先**完整备份所有数据**,可以参考 [备份迁移](https://docs.halo.run/user-guide/backup-migration) 进行整站备份。
## 导出数据文件
在 Halo 1.5.x / 1.6.x 后台的小工具中提供了数据导出的功能,将最新的数据进行备份,然后下载即可。这个数据文件包含了数据库所有的数据,后续我们在 2.0 的导入插件中就是通过这个文件进行数据导入。
![halo-data-export.png](/img/halo-data-export.png)
## 部署 Halo 2.0
可以参考以下文档进行部署:
- [使用 Docker 部署](./install/docker.md)
- [使用 Docker Compose 部署](./install/docker-compose.md)
:::tip
可以考虑暂时保留旧版本的 Halo等到迁移完成之后再移除。如果需要保留旧版本的 Halo请注意在部署 Halo 2.0 的时候使用其他端口然后在反向代理Nginx中修改为 Halo 2.0 的运行端口即可。
:::
## 移动附件
只需要将 1.x 工作目录的 `upload` 目录里面的所有文件夹移动到 2.0 工作目录下的 `attachments\migrate-from-1.x` 文件夹即可。**但需要注意的是,此操作仅为了让附件资源可以正常访问,目前暂不支持在后台进行管理。**
## 安装迁移插件
需要在 <https://github.com/halo-sigs/plugin-migrate/releases> 中下载最新版本的插件 JAR 包,然后在 Halo 2.0 的插件管理中安装即可,安装完成即可在左侧菜单中看到迁移菜单。
![Migrate Plugin](/img/migrate/halo2.0-migrate-plugin.png)
## 迁移
1. 点击左侧菜单的迁移进入迁移页面。
2. 点击 **选择文件** 按钮,选择在 Halo 1.5.x / 1.6.x 导出的数据文件JSON 格式)。
3. 最后点击页面下方的 **执行导入** 即可。

@ -0,0 +1,102 @@
---
title: 写在前面
description: 在开始前,您需要了解的事项
---
:::warning 提示
当前文档是 Halo 2.0 测试阶段的文档,强烈不建议尝试将 1.5 升级到 2.0 测试版,也不建议在生产环境使用,当前版本仅用于测试。我们欢迎大家在测试过程中反馈任何问题。另外,在 Halo 2.0 正式发布的时候,我们会提供完整的升级指南。
:::
## 环境要求
这里将讲述运行 Halo 所要求的一些软硬件的配置,我们建议您在运行或者部署之前先浏览一遍此页面。
### 硬件配置
:::tip
如果您要使用服务器进行部署 Halo您需要注意的是Halo 目前不支持市面上的云虚拟主机,请使用云服务器或者 VPS。
:::
#### CPU
无特别要求。目前我们的 [Docker 镜像](https://hub.docker.com/r/halohub/halo-dev) 也已经支持多平台。
#### 内存
为了获得更好的体验,我们建议至少配置 1G 的 RAM。
#### 磁盘
无特别要求理论上如果不大量在服务器上传附件Halo 对磁盘的容量要求并不是很高。但我们推荐最好使用 SSD 硬盘的服务器,能更快的运行 Halo。
#### 网络
Halo 目前必须在外网畅通的情况下使用,否则会导致页面异常。
### 软件环境
Halo 理论上可以运行在任何支持 Docker 及 Java 的平台。
#### Docker
必须在运行环境安装好 [Docker](https://www.docker.com/) 环境,目前 Halo 的默认安装运行方式均使用容器。
#### JRE可选
目前 Halo 的默认及推荐安装方式为 Docker 容器运行,使用 jar 包运行的方式需要用户自行构建 jar 包。
:::info
当前版本2.0)需要 JRE 17 的版本,推荐使用 OpenJDK 17。
:::
#### PostgreSQL可选
也可以使用系统自带的 H2 Database 数据库,无需安装。但不推荐在生产环境中使用 H2 Database。
#### Web 服务器(可选)
如果您部署在生产环境,那么你很可能需要进行域名绑定,这时候我们推荐使用诸如 [Nginx](http://nginx.org/)、[Caddy](https://caddyserver.com/) 之类的 Web 服务器进行反向代理。但需要注意的是,目前 Halo 不支持代理到子目录halo.run/blog
#### Wget可选
后续的文档中,我们会使用 wget 为例,用于下载所需要的文件,所以请确保服务器已经安装好了这个软件包。当然,下载文件不限制工具,如果你对其他工具熟悉,可以忽略。
#### VIM可选
后续的文档中,我们会使用 vim 为例,用于修改一些必要的配置文件,所以同样请确保服务器已经安装了这个软件包。当前,修改文档也不限制工具,如果你对其他编辑软件熟悉,也可以忽略。
## 浏览器支持
1. 用户前台:视主题所支持的情况而定,由于目前的评论模块使用了 [Vuejs](https://cn.vuejs.org/v2/guide/installation.html#%E5%85%BC%E5%AE%B9%E6%80%A7) 开发,所以在 [Vuejs](https://cn.vuejs.org/v2/guide/installation.html#%E5%85%BC%E5%AE%B9%E6%80%A7) 不支持的某些浏览器中无法正常显示评论区域。
2. 管理后台:支持目前常见的现代浏览器,具体视 [Vuejs](https://cn.vuejs.org/v2/guide/installation.html#%E5%85%BC%E5%AE%B9%E6%80%A7) 框架的支持情况而定。
## 名词解释
这里将列出后续文档中一些和 Halo 相关的名词含义。
### ~(符号)
代表当前系统下的 [用户目录](https://zh.wikipedia.org/wiki/%E5%AE%B6%E7%9B%AE%E5%BD%95)。
### 镜像
指 Halo 构建所产生的 [Docker 镜像](https://docs.docker.com/engine/reference/commandline/images/)。用户通过该镜像启动 Halo 应用。
### 工作目录
指 Halo 所依赖的工作目录,在 Halo 运行的时候会在系统当前用户目录下产生一个 `.halo2` 的文件夹,绝对路径为 `~/.halo2`。由于这个工作目录是固定的,所以上面所说的 `运行包`不限制所存放的位置,里面通常包含下列目录或文件:
1. `db`:存放 H2 Database 的物理文件,如果你使用其他数据库,那么不会存在这个目录。
2. `themes`:里面包含用户所安装的主题。
2. `plugins`:里面包含用户所安装的插件。
5. `attachments`:附件目录。
4. `logs`:运行日志目录。
6. `application.yaml`:配置文件。
### 主题
包含了各种站点页面模板的资源包。用户访问 Halo 站点浏览到的内容及样式,由 Halo 管理端所配置使用的主题所决定。
### 插件
用于扩展 Halo 功能的软件包。插件独立于 Halo 核心应用,可以单独安装、升级、卸载。

@ -0,0 +1,68 @@
---
id: intro
sidebar_label: 简介
title: ''
sidebar_position: 1
hide_title: true
slug: /
---
<p align="center">
<a href="https://halo.run" target="_blank" rel="noopener noreferrer">
<img width="100" src="https://halo.run/logo" alt="Halo logo" />
</a>
</p>
<p align="center"><b>Halo</b> [ˈheɪloʊ],好用又强大的开源建站工具。</p>
<p align="center">
<a href="https://github.com/halo-dev/halo/releases"><img alt="GitHub release" src="https://img.shields.io/github/release/halo-dev/halo.svg?style=flat-square&include_prereleases" /></a>
<a href="https://github.com/halo-dev/halo/releases"><img alt="GitHub All Releases" src="https://img.shields.io/github/downloads/halo-dev/halo/total.svg?style=flat-square" /></a>
<a href="https://hub.docker.com/r/halohub/halo"><img alt="Docker pulls" src="https://img.shields.io/docker/pulls/halohub/halo?style=flat-square" /></a>
<a href="https://github.com/halo-dev/halo/commits"><img alt="GitHub last commit" src="https://img.shields.io/github/last-commit/halo-dev/halo.svg?style=flat-square" /></a>
<a href="https://github.com/halo-dev/halo/actions"><img alt="GitHub Workflow Status" src="https://img.shields.io/github/workflow/status/halo-dev/halo/Halo%20CI?style=flat-square" /></a>
<br />
<a href="https://halo.run">官网</a>
<a href="https://docs.halo.run">文档</a>
<a href="https://bbs.halo.run">社区</a>
<a href="https://gitee.com/halo-dev">Gitee</a>
<a href="https://t.me/halo_dev">Telegram 频道</a>
</p>
------------------------------
## 快速开始
```bash
docker run -it -d --name halo-next -p 8090:8090 -v ~/.halo2:/root/.halo2 --restart=unless-stopped halohub/halo:2.0.0
```
详细部署文档请查阅:<https://docs.halo.run/getting-started/install/docker-compose>
## 在线体验
- 环境地址:<https://demo.halo.run>
- 后台地址:<https://demo.halo.run/console>
- 用户名:`demo`
- 密码:`P@ssw0rd123..`
## 生态
可访问 [awesome-halo](https://github.com/halo-sigs/awesome-halo) 查看已经适用于 Halo 2.0 的主题和插件,以及适用于 Halo
1.x 的相关仓库。
## 许可证
[![license](https://img.shields.io/github/license/halo-dev/halo.svg?style=flat-square)](https://github.com/halo-dev/halo/blob/master/LICENSE)
Halo 使用 GPL-v3.0 协议开源,请遵守开源协议。
## 贡献
参考 [CONTRIBUTING](https://github.com/halo-dev/halo/blob/master/CONTRIBUTING.md)。
<a href="https://github.com/halo-dev/halo/graphs/contributors"><img src="https://opencollective.com/halo/contributors.svg?width=890&button=false" /></a>
## 状态
![Repobeats analytics](https://repobeats.axiom.co/api/embed/ad008b2151c22e7cf734d2688befaa795d593b95.svg "Repobeats analytics image")

@ -0,0 +1,102 @@
---
title: 附件
description: 附件管理相关功能说明
---
## 存储策略
为了能够更加灵活地管理附件的存储位置Halo 提供了存储策略的概念。
Halo 中支持多种类型的存储策略,你可以通过安装插件的方式对支持的存储策略类型进行扩展。一个存储策略包含了存储提供者,具体存储位置等使用该类型存储所必要的各种信息。
你可以点击附件页面右上角的 `存储策略` 按钮对存储策略进行管理。
![存储策略](/img/user-guide/attachments/attachment-policy.png)
### 新建存储策略
点击存储策略列表右上方的 `+` 添加按钮即可新建一个存储策略。
![添加存储策略](/img/user-guide/attachments/attachment-policy-add.png)
添加时首先需要选择一种存储策略类型,系统内置的存储策略为本地存储,图中的 `Aliyun OSS` 阿里云的 OSS 对象存储由[该插件](https://github.com/halo-sigs/plugin-alioss)提供。
![添加本地存储策略](/img/user-guide/attachments/attachment-policy-add-local.png)
添加一个本地存储时,你需要输入名称及存储位置信息。其中的存储位置决定了使用该存储策略的附件,在服务器上的实际存储路径,路径规则为 `{Halo 工作目录}/attachments/{存储位置}`,其中的 Halo 工作目录由安装时指定的参数决定,默认为 `~/.halo2`
:::info
默认的 Docker 部署方式,实际存储位置由挂载到 Halo 容器工作目录的服务器目录所决定。
:::
### 删除存储策略
点击存储策略列表指定存储所在行后方的 `···` 更多操作按钮即可对该存储策略进行编辑或删除。
![存储策略操作](/img/user-guide/attachments/attachment-policy-operate.png)
:::info
为了保护附件安全避免用户误操作,当存储策略下存在附件时,该存储策略不允许删除。如果确定要删除某个存储策略及该存储策略中的所有附件,可以先按照存储策略对附件进行筛选,先批量删除存储策略下的所有附件,再删除存储策略。
:::
## 附件分组
通过附件分组功能可以方便地将同一类型、同一用途的附件划分到一个分组中,方便后续附件的管理和插件。
附件所使用的存储策略决定了附件的实际存储位置和 URL 规则,而附件分组功能仅是逻辑上的归类划分,不会影响附件的存储位置及 URL。
### 新建分组
点击附件列表上方的 `添加分组` 按钮即可新建一个分组。
![添加分组](/img/user-guide/attachments/attachment-group-add.png)
### 删除分组
点击附件列表上方指定分组上的 `···` 更多按钮,可以对分组进行重命名或删除操作。
Halo 目前提供了两种分组删除策略:
1. **删除并将附件移动至未分组**:分组被删将被删除,分组下的附件移动到未分组中;
2. **删除并同时删除附件**:先删除下的所有附件后,再删除该分组。
:::warning
当使用 `删除并同时删除附件` 方式时,分组下的所有附件会被同时删除且不可恢复、无法找回,请谨慎进行该操作。
:::
## 上传附件
点击附件列表右上方的 `上传` 按钮即可上传新的附件到 Halo。
:::info
附件会被上传到当前选中的分组中,如果选择的是 `全部` 附件,则附件会被默认上传到 `未分组` 中。
:::
![上传附件](/img/user-guide/attachments/attachment-upload.png)
上传附件时你需要先选择一个存储策略,后续上传的附件将会存储在选中的存储策略中。
你可以点击 `浏览``我的设备` 图标唤起本地文件管理器进行文件选择,也可以直接将文件拖拽到待上传文件区域中。
![上传多个附件](/img/user-guide/attachments/attachment-upload-multiple.png)
如上图所示,你可以同时添加多个文件一次性完成多个附件的上传。
## 查看附件
点击附件列表中的某一个附件即可查看该附件的详细信息。
:::info
对于存储策略类型为本地存储的附件,详细信息中的的原始链接生成规则为 `/upload/{存储策略存储路径}/{文件名称}`
:::
## 删除附件
附件页面提供两种浏览模式,列表模式和平铺模式。
在平铺模式下,你可以点击附件缩略图右上角的 `√` 图标选中若干个附件,通过上方的批量操作按钮删除选中的附件。
![批量操作附件](/img/user-guide/attachments/attachment-batch-operate.png)
在列表模式下,你可以点击指定附件所在行后方的 `···` 更多按钮,对附件进行删除操作。
:::warning
附件删除后不可恢复、无法找回,请谨慎进行该操作。
:::
## 移动附件所在分组
与批量删除操作类似,你可以选中多个附件后在上方的批量操作按钮中选择 `移动` 操作,将所选附件移动到指定的分组中。

@ -0,0 +1,38 @@
---
title: 备份迁移
description: 关于备份和迁移的最佳实践
---
:::info
在开始之前,我们推荐你先阅读 [《写在前面》](/getting-started/prepare) 的名词解释部分。
:::
## 备份
### 数据备份
目前 Halo 在后台的小工具中提供了数据导出的功能,此功能的作用为导出数据库的所有数据,格式为 `JSON`。通常可以作为切换数据库类型的时候使用。需要注意的是,此备份仅仅为备份数据,不包含其他诸如主题、附件等资料。如下图:
![halo-data-export.png](/img/halo-data-export.png)
点击右下角的备份按钮即可导出所有数据,之后点击备份文件的标题即可下载。
### 整站备份
通过 [《写在前面》](/getting-started/prepare) 的名词解释部分我们可以知道Halo 的所有数据都是存放在当前用户目录的工作目录(.halo下的使用 MySQL 数据库除外,你还需要导出 MySQL 数据)。**所以我们备份整站的数据仅需备份这个目录即可**,不管你使用何种方式。不过,为了操作方便,我们也在后台的小工具中提供了备份整站数据的功能,和上面所说的数据备份一致,点击备份按钮即可打包工作目录文件夹。如下图:
![halo-workspace-export.png](/img/halo-workspace-export.png)
## 迁移
### 导入数据
此功能为导入上面所说的数据备份产生的数据文件JSON 格式),并非整站备份的工作目录文件。需要注意的是,此功能仅在站点初始化的时候支持。如下图:
![halo-data-import.png](/img/halo-data-import.png)
上传文件之后,点击导入即可。
### 整站迁移
此操作通常用于迁移服务器,基于上面 **整站备份** 所说Halo 的所有数据都是存放于当前用户目录的工作目录(.halo下的。当然这仅限于使用 **H2 Database** 的情况下,如果你使用的 MySQL那么还需要手动导出 MySQL 数据。所以,我们迁移服务器仅仅需要将工作目录的备份文件上传到新服务器的用户目录下解压,然后按照 [《安装指南》](/getting-started/install/linux) 重新安装即可。MySQL 用户还需要做的就是手动导出 MySQL 数据,并在新服务器上导入。

@ -0,0 +1,72 @@
---
title: 基础说明
description: Halo 中的基本概念说明
---
Halo 作为一款好用又强大的开源建站工具,配合上不同的模板与插件,可以很好地帮助你构建你心中的理想站点。它可以是你公司的官方网站,可以是你的个人博客,也可以是团队共享的知识库,甚至可以是一个论坛、一个商城。
为了更好地发挥出 Halo 的价值,这里有一些基本概念需要你进行了解。
## 控制台
控制台是一个 Halo 站点的后台管理系统,只有具有权限的登录用户才可以正常使用控制台功能。你可以在控制台中管理站点中的文章、页面、附件等各种内容,调整站点使用的主题或各种设置。
:::info
控制台默认地址为 `$HALO_EXTERNAL_URL/console/`。忘记密码请参考[常见问题中的忘记密码章节](../user-guide/faq.md)进行处理。
:::
### 界面说明
![控制台界面说明](/img/user-guide/common/控制台界面说明.png)
1. **全局搜索框**:点击或通过快捷键 `Ctrl+K` 可以呼出全局搜索框,输入关键字可以在所有文章、页面、附件、用户及设置项等所有内容中进行全局搜索;
2. **侧边导航栏**:对控制台提供的功能进行导航,点击导航栏条目会在页面右侧显示对应功能页面。安装某些插件可能会扩展导航栏条目;
3. **用户信息展示及操作**:展示当前登录用户的头像、名称及角色等信息,`···` 中提供更多用户相关操作;
4. **功能页面标题** 当前所在的功能页面标题;
5. **功能页面操作区域**:当前所在功能页面提供的功能操作按钮;
6. **功能页面主体** 当前所在功能页面的主体显示区域,显示内容及形式视具体页面功能而定。
## 文章
文章是 Halo 中的核心概念之一。一篇文章主要由纯文本的文章标题和富文本的文章内容构成,除此之外你还可以为文章设置所属分类、添加标签、设置封面图等。
在不同的站点类型不同的应用场景中,文章的实际含义也会有所区别,它可以代表一则公司新闻、一篇博客或者产品文档中的某一章节。
## 页面
Halo 中存在两种类型的页面,`功能页面` 和 `自定义页面`
### 功能页面
功能页面通常由各个插件提供,页面功能及在控制台呈现的内容由具体提供该页面的插件决定。
### 自定义页面
自定义页面与文章类似,同样包含页面标题和富文本形式的页面内容。与文章不同的是自定义页面无法设置所属分类和标签信息,一般用于站点中单一展示功能的页面,例如常见的站点关于页面、联系我们页面等。
## 分类
通过分类可以更好的组织管理文章。分类之间存在层级关系,一个父分类下可包含多个子分类。一篇文章可以同时属于多个分类。
## 标签
标签可以用于为文章添加特定标记,与分类不同的是标签之间没有层级关系。一篇文章也可以同时添加多个标签。
## 附件
由用户上传的,供文章、主题设置等各个地方引用的文件。多用于文章配图、主题配图、用户头像等场景。
## 主题
包含了各种站点页面模板的资源包。用户访问 Halo 站点浏览到的内容及样式,由 Halo 管理端所配置使用的主题所决定。
:::info
当前 Halo 支持的主题可在[Awesome Halo](https://github.com/halo-sigs/awesome-halo)仓库查看。
:::
## 插件
用于扩展 Halo 功能的软件包。插件独立于 Halo 核心应用,可以单独安装、升级、卸载。
:::info
当前 Halo 支持的插件可在[Awesome Halo](https://github.com/halo-sigs/awesome-halo)仓库查看。
:::

@ -0,0 +1,172 @@
---
title: 功能配置
description: 系统功能的相关配置说明
---
## 修改博客地址
很多人在部署完成之后都会惊奇的发现,博客前台居然没有样式?究其原因就是无法获取到静态资源,那么为什么获取不到呢?那就是你的博客地址没有设置正确。
第一步:
登录到后台之后,进入 博客设置 -> 常规设置。
第二步:
修改你的博客地址:
- 如果你没有进行域名解析或者没有配置反向代理,那么博客地址一般为 `http://ip:端口`
- 如果你进行了域名解析且配置了反向代理,但是没有配置 SSL 证书,那么博客地址一般为 `http://域名`
- 如果你进行了域名解析、配置了反向代理、也配置了 SSL 证书,那么博客地址一般为 `https://域名`
说了这么多,就是希望你别把 `http://` 或者 `https://` 搞混了。
另外,需要注意的是,地址尾部不需要 `/`
## 注册 Gravatar
很多人反映评论中自己的头像不显示,其实评论部分调用的头像并不是在个人资料中设置的那个,而是 `Gravatar`。所以这一点怪我,从 Halo 发布至今我都没有说明这个事情。
至于什么是 `Gravatar`,引用 `Gravatar` 官方的说明:`全球公认的头像`。这可不是在吹牛,因为只要接入了 Gravatar 的网站,你在网站上使用在 Gravatar 注册的邮箱之后,都会显示你设置的头像,而不需要额外设置。比较出名的网站有 `GitHub``GitLab``V2ex` 等。所以点击 <https://cn.gravatar.com/> 注册一个吧。
## 设置用户头像/Logo/Favicon
### 头像
第一步:
登录到后台之后,进入 用户 -> 个人资料。
第二步:
点击左侧头像即可选择本地附件中的图片。另外,也支持设置为 `Gravatar` 头像。点击 `使用 Gravatar` 即可。当然,前提是你注册了 `Gravatar` 才行。
### Logo/Favicon
第一步:
登录到后台之后,进入 系统 -> 博客设置。
第二步:
在常规设置中找到 Logo/Favicon 输入框,点击输入框后面的按钮即可调用附件库选择图片。
## SEO 设置
先说说程序本身对 SEO 做的优化:
- 支持 sitemap 站点地图:可访问 /sitemap.xml 或 /sitemap.html
- 全站绝对路径
- 页面静态化
- 支持伪静态1.3.0+
需要自己设置的有:
第一步:
登录到后台之后,进入 系统 -> 博客设置 -> SEO 设置。
第二步:
填写你需要的一些关键词和博客描述,这部分可能需要你了解一定的 SEO 优化技巧。
> 另外,文章和自定义页面都可以自行设置关键词和描述(文章设置的高级设置中)。当然,你也可以不设置,系统为自动取你设置的文章标签为关键词,描述会自动取文章的部分内容。
Q&A
Q我刚发布的文章为啥百度搜索不到呢
A这是一件非常正常的事情建议注册百度的站长工具手动或者自动提交链接。
## SMTP 服务
SMTP 服务,简称发信服务,顾名思义就是用来发邮件的。那么,为什么要设置这个呢?有两个用途:
1. 评论通知,收到评论之后发信通知博主,评论者被回复之后发信通知被评论者。
2. 找回密码。
第一步:
登录到后台之后,进入 系统 -> 博客设置 -> SMTP 设置。
第二步:
勾选 `是否启用`,并配置相关信息:
1. SMTP 地址到邮箱服务商查询。举两个例子QQ 邮箱smtp.qq.com163 邮箱smtp.163.com
2. 发送协议:一般不修改,具体可到邮箱服务商查询。
3. SSL 端口:一般不修改,具体可到邮箱服务商查询。
4. 邮箱账号:你的账号。
5. 邮箱密码:你的密码。
6. 发件人:随意。
第三步:
点击发送测试,填上你可以接收到的邮箱,并填写测试内容,最后点击发送。不出意外就可以发送成功,失败了就检查下配置吧。
Q&A
Q发送失败
A先检查配置是否有误如不知道哪里错误请查看日志。点击左上角 `Halo Dashboard`即可进入开发者选项,点击 `实时日志` 即可看到最近的日志。
Q日志说我授权出错但是我密码明明是对的啊
A部分邮箱所谓的密码并不是你的登陆密码需要去邮箱服务商生成 `授权码`。这个才是你需要填写的。
## 设置统计代码
第一步:
登录到后台之后,进入 系统 -> 博客设置 -> 其他设置。
第二步:
`网站统计平台` 提供给你的一串代码填写到 `统计代码` 即可。
Q&A
Q什么是网站统计平台
A诸如百度统计CNZZGoogle Analytics。可以非常详细的统计你网站的访问情况。
## 找一个合适自己的主题
当你搭建好博客之后,如果你不喜欢默认的主题,那么你可以去 [https://halo.run/themes/](https://halo.run/themes/) 选择你喜欢的主题。在这里主要讲一些注意事项。
### 如何安装?
登录到后台之后,进入 外观 -> 主题,在后下角有一个按钮,点击之后选择 `安装主题`。系统提供的两种安装方式。
1. 第一种,远程拉取。你只需要把主题的 GitHub 地址复制进去点击下载即可。
2. 第二种本地上传你需要将主题安装包releases下载到本地然后选择 `本地上传` 点击选择你下载好的主题即可。
> 一般情况下,推荐第二种,第一种一般安装的为开发版本,建议谨慎使用。
### 设置主题
> 可能有一部分人不太清楚,一般情况下,每个主题都是有单独的设置选项的。
点击主题下方的设置按钮即可进入主题设置。另外主题设置还有一种预览模式,可以实时查看主题设置变更后的效果。当你设置当前激活主题的时候,在页面右下角有一个 `预览模式` 的按钮,点击即可进入预览模式。
## 设置备案信息
由于后台博客设置并没有提供设置备案信息的选项,所以有很多人不知道该怎么添加备案信息,这里简单说明一下。
第一步:
登录到后台之后,进入 系统 -> 博客设置 -> 常规设置。
这时候你应该可以看到一个`页脚信息`的选项,而且有提示 `支持 HTML 格式的文本`。所以我们把备案信息放在这里即可。
第二步:
将整理好的备案信息填写到页脚信息即可,如果没记错的话,公安联网备案是有提供`网站公安机关备案号`和`备案编号HTML代码`的。

@ -0,0 +1,105 @@
---
title: 常见问题
description: 使用上的常见问题
---
### Halo 是什么?
**Halo** [ˈheɪloʊ],是一款好用又强大的[开源建站工具](https://github.com/halo-dev/halo),配合上不同的模板与插件,可以很好地帮助你构建你心中的理想站点。它可以是你公司的官方网站,可以是你的个人博客,也可以是团队共享的知识库,甚至可以是一个论坛、一个商城。
### 为什么百度无法搜索到我的站点?
这是一个暂时无法解答的问题。所涉及到的问题过多,受影响因素可能有域名、服务器 IP 位置、建站时间、网站结构、内容等等。建议了解一下 SEO 相关知识对网站进行优化,目前我们在程序方面做的优化有:
- 支持 sitemap 站点地图:可访问 `/sitemap.xml``/sitemap.html`
- 全站绝对路径
- 页面静态化
- 支持自定义文章关键字和描述
- 支持自定义站点关键字以及站点描述
### 附件上传提示 `413 Request Entity Too Large` 如何解决?
这可能是由于 Nginx 的上传大小限制所导致的。可以在 Nginx 的配置文件下的 server 节点加入 `client_max_body_size 1024m;` 即可解决,如果 1024m 还不够,请自行断定,详细配置参考如下:
```nginx {4}
server {
listen 80;
server_name localhost;
client_max_body_size 1024m;
}
```
### 网站加载速度慢,是什么问题导致的?
导致网站加载速度慢的原因有很多,建议打开浏览器的 Developer Tools 查看具体是哪个请求时间过长,然后进行针对性的优化。这里提供一些可能的原因:
1. 服务器带宽过小,很多厂商提供的最低带宽一般是 1M。
2. 服务器地区过远,这个需要自行取舍。
3. 网站上的图片过多或者体积过大,可以尝试压缩图片,或者参考 [优雅的让 Halo 支持 webp 图片输出](https://halo.run/archives/halo-and-webp.html) 的教程配置一个 Webp 图片的服务。
4. 部分主题的静态资源可能是由公共 CDN 提供的,当这个 CDN 不稳定的时候可能会导致加载变慢。
一些提升网站加载速度的建议:
1. 尽量不要选择 1M 带宽的服务器,可以根据自己的预算适当提升带宽。一般 3M 以上即可。
2. 尽量购买网络质量较好的服务器,或者较近区域的服务器。
3. 如果一定需要放大量的图片,建议先无损压缩一下再使用。
4. 如上所说,可以自行搭建一个 Webp 图片转换的服务,参考 [优雅的让 Halo 支持 webp 图片输出](https://halo.run/archives/halo-and-webp.html)。
5. 如果网站的静态资源加载慢是由三方 CDN 导致的,可以自行修改主题。
6. 可以使用全站 CDN 加速的方案。
### 如何在一台服务器上部署多个站点?
参考 [写在前面/工作目录](../getting-started/prepare.md#工作目录) 我们可以知道,工作目录对于 Halo 主程序来说是固定的。如果我们需要部署多个站点,我们提供以下两种方式以供参考:
1. 创建多个 Linux 账户,并在每个账户上运行一个独立的 Halo。因为工作目录是基于账户的所以每个账户都有自己的工作目录。但是有一点需要注意就是需要修改每一个 Halo 的运行端口,参考:[配置参考/端口](../getting-started/config#%E7%AB%AF%E5%8F%A3)
2. 使用 Docker 创建多个容器,因为使用 Docker 可以将内部的工作目录映射到宿主机的任何目录,可以参考以下创建容器的方式:
```bash
# 第一个 Halo 容器
docker run \
-it -d \
--name halo-next-1 \
-p 8090:8090 \
-v ~/.halo2.1:/root/.halo2 \
-e HALO_EXTERNAL_URL=http://localhost:8090/ \
-e HALO_SECURITY_INITIALIZER_SUPERADMINPASSWORD=P@88w0rd \
halohub/halo-dev:latest
# 第二个 Halo 容器
docker run \
-it -d \
--name halo-next-2 \
-p 8090:8090 \
-v ~/.halo2.2:/root/.halo2 \
-e HALO_EXTERNAL_URL=http://localhost:8090/ \
-e HALO_SECURITY_INITIALIZER_SUPERADMINPASSWORD=P@88w0rd \
halohub/halo-dev:latest
```
更多 Docker 相关的教程请参考:[使用 Docker 部署 Halo](../getting-started/install/docker.md)
### 如何查看运行日志?
使用 docker logs 命令进行查看。
```bash
# '-f' 滚动更新日志
# '-n 200' 从倒数第200行开始查看
# 更多帮助可以查看 'docker logs --help'
docker logs -f halo_next -n 200
```
### 前台样式丢失,如何解决?
前台样式不正常或者丢失有很多种问题的可能,最快捷定位问题的方式就是打开浏览器控制台查看具体请求的错误,以下列出了部分导致出现该问题的常见原因:
1. 后台设置的 `博客地址` 与实际访问地址不一致。也可能是开启了 https 之后,无法正常加载 http 资源,将 `博客地址` 改为 https 协议即可。
2. Nginx 配置了静态资源缓存,但没有设置 `proxy_pass`,参考如下:
```nginx
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf|flv|mp4|ico)$ {
proxy_pass http://halo;
expires 30d;
access_log off;
}
```

@ -0,0 +1,454 @@
---
title: Markdown 语法
description: Halo 编辑器中所支持的 Markdown 语法说明
---
## 写在前面
从 1.5.0 版本开始Halo 默认保存编辑器渲染的 html 文档。使用的 Markdown 渲染库为 [markdown-it](https://github.com/markdown-it/markdown-it),我们也对此进行了封装:[@halo-dev/markdown-renderer](https://github.com/halo-dev/js-sdk/tree/master/packages/markdown-renderer)。后续我们会在任何需要渲染 Markdown 的地方都使用此库,保证 Markdown 渲染结果一致。
## 基础语法
markdown-it 使用了 [CommonMark spec](https://spec.commonmark.org) 规范,基础语法请参考 [CommonMark spec](https://spec.commonmark.org)。
## 自动链接Auto Link
支持自动将一个链接格式的文本转换为 a 标签链接。
语法规则:
```plain
https://halo.run
```
渲染结果:
```html
<a href="https://halo.run">https://halo.run</a>
```
预览:
<https://halo.run>
## 代码块Code Block
语法规则:
````markdown {1}
```language
内容
```
````
示例:
````markdown {1}
```java
public static void main(String[] args){
System.out.println("Hello World!");
}
```
````
````markdown {1}
```javascript
console.log("Hello World!")
```
````
:::info
注意:代码高亮需要主题添加插件支持,不同的主题可能有实现差异。
:::
## 缩写abbr
语法规则:
```markdown
*[HTML]: Hyper Text Markup Language
*[W3C]: World Wide Web Consortium
The HTML specification
is maintained by the W3C.
```
渲染结果:
```html
<p>
The
<abbr title="Hyper Text Markup Language">
HTML
</abbr>
specification
<br />
is maintained by the
<abbr title="World Wide Web Consortium">
W3C
</abbr>
.
</p>
```
## Attrs
此语法支持将 `id` `class` `target` 添加到渲染后的 HTML 标签上。
示例:
```markdown
# Halo {#halo}
```
```markdown
> Hello Halo {.info}
```
```markdown
[https://halo.run](https://halo.run) {target="_blank"}
```
渲染结果:
```html
<h1 id="halo" tabindex="-1">Halo</h1>
```
```html
<blockquote class="info"> <p>Hello Halo</p> </blockquote>
```
```html
<a href="https://halo.run" target="_blank">https://halo.run</a>
```
## Emoji
支持将 Emoji 的文字形式转化为图片形式。
示例:
```markdown
:100:
```
渲染结果:
```html
💯
```
## 脚注Footnote
语法规则:
```markdown
[^脚注名]
[^脚注名]: 脚注内容
```
示例:
```html
驿外[^1]断桥边,寂寞开无主。已是黄昏独自愁,更着风和雨
[^1]: 驿指荒僻、冷清之地。驿驿站。
```
渲染结果:
```html
<p>
驿外
<sup class="footnote-ref">
<a href="#fn1" id="fnref1">
[1]
</a>
</sup>
断桥边,寂寞开无主。已是黄昏独自愁,更着风和雨
</p>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item">
<p>
驿指荒僻、冷清之地。驿驿站。
<a href="#fnref1" class="footnote-backref">
↩︎
</a>
</p>
</li>
</ol>
</section>
```
## 下划线ins
示例:
```markdown
++inserted++
```
渲染结果:
```html
<ins>inserted</ins>
```
预览:
<ins>inserted</ins>
## 标记mark
示例:
```markdown
==marked==
```
渲染结果:
```html
<mark>marked</mark>
```
预览:
<mark>marked</mark>
## 下标sub
示例:
```markdown
H~2~0
```
渲染结果:
```html
H<sub>2</sub>0
```
预览:
H<sub>2</sub>0
## 上标sup
示例:
```markdown
29^th^
```
渲染结果:
```html
29<sup>th</sup>
```
预览:
29<sup>th</sup>
## 目录TOC
此语法支持根据标题生成文档目录。
示例:
```markdown
[toc]
# Heading
## Sub heading 1
Some nice text
## Sub heading 2
Some even nicer text
```
渲染结果:
```html
<p>
<div class="table-of-contents">
<ul>
<li>
<a href="#heading">
Heading
</a>
<ul>
<li>
<a href="#sub-heading-1">
Sub heading 1
</a>
</li>
<li>
<a href="#sub-heading-2">
Sub heading 2
</a>
</li>
</ul>
</li>
</ul>
</div>
</p>
<h1 id="heading" tabindex="-1">
Heading
</h1>
<h2 id="sub-heading-1" tabindex="-1">
Sub heading 1
</h2>
<p>
Some nice text
</p>
<h2 id="sub-heading-2" tabindex="-1">
Sub heading 2
</h2>
<p>
Some even nicer text
</p>
```
## 任务列表Task Lists
示例:
```markdown
- [x] Apple
- [ ] Banana
```
渲染结果:
```html
<ul class="contains-task-list">
<li class="task-list-item">
<input class="task-list-item-checkbox" checked="" disabled="" type="checkbox">
Apple
</li>
<li class="task-list-item">
<input class="task-list-item-checkbox" disabled="" type="checkbox">
Banana
</li>
</ul>
```
预览:
- [x] Apple
- [ ] Banana
## 数学公式Katex
我们使用了 Katex 作为数学公式渲染的插件,因为从 1.5.0 开始,我们将直接保存编辑器渲染的内容,在保存的时候就已经保存了渲染好的 Katex 结构。所以在前台无需引入 Katex 插件来进行渲染,但目前仍需要引入 Katex 的样式文件,如果主题没有支持,可以在系统设置的 `自定义内容页 head` 中加入以下代码:
```html
<link rel="stylesheet" href="https://unpkg.com/katex@0.12.0/dist/katex.min.css" />
```
### 行内公式
示例:
```markdown
$\sqrt{3x-1}+(1+x)^2$
```
预览:
Example$\sqrt{3x-1}+(1+x)^2$
### 块级公式
示例:
```markdown
$$\begin{array}{c}
\nabla \times \vec{\mathbf{B}} -\, \frac1c\, \frac{\partial\vec{\mathbf{E}}}{\partial t} &
= \frac{4\pi}{c}\vec{\mathbf{j}} \nabla \cdot \vec{\mathbf{E}} & = 4 \pi \rho \\
\nabla \times \vec{\mathbf{E}}\, +\, \frac1c\, \frac{\partial\vec{\mathbf{B}}}{\partial t} & = \vec{\mathbf{0}} \\
\nabla \cdot \vec{\mathbf{B}} & = 0
\end{array}$$
```
预览:
$$\begin{array}{c}
\nabla \times \vec{\mathbf{B}} -\, \frac1c\, \frac{\partial\vec{\mathbf{E}}}{\partial t} &
= \frac{4\pi}{c}\vec{\mathbf{j}} \nabla \cdot \vec{\mathbf{E}} & = 4 \pi \rho \\
\nabla \times \vec{\mathbf{E}}\, +\, \frac1c\, \frac{\partial\vec{\mathbf{B}}}{\partial t} & = \vec{\mathbf{0}} \\
\nabla \cdot \vec{\mathbf{B}} & = 0
\end{array}$$
## 图表Mermaid
从 Halo 1.5.0 开始,编辑器支持渲染 Mermaid 图表为 svg 内容,并直接保存渲染后的内容,所以无需在前台引入 Mermaid 插件进行渲染。
示例:
````markdown
```mermaid
graph TD;
A-->B;
A-->C;
B-->D;
C-->D;
```
````
````markdown
```mermaid
sequenceDiagram
participant Alice
participant Bob
Alice->>John: Hello John, how are you?
loop Healthcheck
John->>John: Fight against hypochondria
end
Note right of John: Rational thoughts <br/>prevail!
John-->>Alice: Great!
John->>Bob: How about you?
Bob-->>John: Jolly good!
```
````
预览:
```mermaid
graph TD;
A-->B;
A-->C;
B-->D;
C-->D;
```
```mermaid
sequenceDiagram
participant Alice
participant Bob
Alice->>John: Hello John, how are you?
loop Healthcheck
John->>John: Fight against hypochondria
end
Note right of John: Rational thoughts <br/>prevail!
John-->>Alice: Great!
John->>Bob: How about you?
Bob-->>John: Jolly good!
```
## 短连接
参考 [1.4.17/Markdown 语法](../../version-1.4/user-guide/markdown.md#短连接)。目前已经在 1.5.x 移除,建议直接使用官方提供的嵌入代码。

@ -0,0 +1,41 @@
---
title: 页面
description: 页面管理相关功能说明
---
Halo 中存在两种类型的页面,`功能页面` 和 `自定义页面`
### 功能页面
功能页面通常由各个插件提供,页面功能及在控制台呈现的内容由具体提供该页面的插件决定。
例如[链接插件](https://github.com/halo-sigs/plugin-links)便实现了一个站点链接管理功能,用户可以通过该插件更加方便地管理与站点相关的友情链接。安装该插件后,在功能页面会出现列表会出现如下条目。
![链接功能页面](/img/user-guide/pages/page-links.png)
点击这个条目即可进入到链接插件提供的配置管理页面。
![链接功能页面](/img/user-guide/pages/page-links-edit.png)
你可以在这个页面中管理链接分组和链接条目。链接信息维护完成后,通过 `$HALO_EXTERNAL_URL/links/` 便可访问到对应的页面。
:::info
对于 `links` 页面的访问需要主题端支持,即安装使用的主题需要有对应的 `links.html` 模板,且在模板中正确处理链接插件提供的数据。
:::
### 自定义页面
自定义页面与文章类似,同样包含页面标题和富文本形式的页面内容。与文章不同的是自定义页面无法设置所属分类和标签信息,一般用于站点中单一展示功能的页面,例如常见的站点关于页面、联系我们页面等。
自定义页面的访问链接为 `$HALO_EXTERNAL_URL/{slug}/` 其中 `slug` 为自定义页面的别名。
对于如下的关于页面,便可以通过 `$HALO_EXTERNAL_URL/page/` 地址进行访问。
![链接功能页面](/img/user-guide/pages/page-about.png)
:::info
自定义页面默认使用主题端的 `page.html` 模板进行渲染,如果主题中提供了针对自定义页面的其他模板,用户可以通过修改自定义页面高级设置中的自定义模板设置进行使用。
:::
#### 自定义页面操作
对于自定义页面的新建、设置、发布及删除等操作,与文章操作基本一致,具体操作请参考[《用户指南-文章》](./posts.md)章节,此处不再赘述。

@ -0,0 +1,47 @@
---
title: 插件
description: 插件管理相关功能说明
---
插件时用于扩展 Halo 功能的软件包。插件独立于 Halo 核心应用,可以单独安装、升级、卸载。
:::info
当前 Halo 支持的插件可在[Awesome Halo](https://github.com/halo-sigs/awesome-halo)仓库查看。
:::
本文档仅对插件的基本管理功能进行说明,关于插件的详细使用说明请参考对应插件的文档。
## 安装插件
点击插件页面右上角的 `安装` 按钮,在弹出的文件上传对话框中上传插件文件即可完成插件安装。
![安装插件](/img/user-guide/plugins/plugin-install.png)
插件安装成功后,便会出现在已安装主题列表中。你可以点击某个插件进入到插件详情页面。
## 插件设置
点击插件列表中的某个插件进入插件详情页面。与主题设置类似,插件详情页面默认显示出了当前插件的详细信息,在详细信息标签页后方的标签页,即为插件提供的相关设置。不同的插件提供的设置项各不相同,设置项的详细说明请参考对应插件的文档。
![插件设置](/img/user-guide/plugins/plugin-setting.png)
以 Unsplash 插件为例,该提供仅在基本设置组中提供了 `Access Key` 一项配置,用于调用 Unsplash 接口。
## 启用/禁用插件
点击插件列表或插件详情页中的启用/禁用开关,即可切换插件的启用禁用状态。
![插件启用/禁用](/img/user-guide/plugins/plugin-switch.png)
## 升级插件
点击插件列表指定插件所在行后方的 `···` 更多操作按钮,选择其中的 `升级` 选项即可上传新的插件文件对当前插件进行升级。
## 卸载插件
点击插件列表指定插件所在行后方的 `···` 更多操作按钮,选择其中的 `卸载``卸载并删除配置` 选项即可对当前插件进行卸载。
![卸载主题](/img/user-guide/themes/theme-uninstall.png)
:::info
仅卸载插件时插件的配置信息会进行保留,当重新安装插件后还可以使用之前已保存的配置。
:::

@ -0,0 +1,146 @@
---
title: 文章
description: 文章管理相关功能说明
---
## 新建文章
目前你可以通过以下两种方式新建一篇文章:
1. 点击仪表盘快捷访问组件中的创建文章
:::info
仪表盘中需要存在快捷访问组件(默认存在)。关于仪表盘组件配置,请参考《用户指南-仪表盘》章节。
:::
2. 通过左侧导航栏进入文章页面,点击右上角的新建按钮
进入到如下页面后,你就可以开始编辑自己的文章内容了:
![文章编辑界面说明](/img/user-guide/posts/post-edit.png)
在文章编辑器中输入你想展现的内容,你可以通过编辑器上方的工具栏调整指定内容的格式或者插入图片、音视频等各类附件。编辑器还提供了各种快捷指令,通过输入 `/` 字符便可以快速选择常用的输入类型,不需要操作鼠标便可以轻松完成文章排版。
:::info
编辑器详细使用说明请参考[《用户指南-编辑器》](./posts.md)
:::
在页面右侧你可以看到文章的大纲结构及字词数等详细信息。一切满意之后,你可以选择暂存这篇文章的草稿或直接将文章进行发布。
:::info
点击保存按钮将只保存文章内容的修改,不会将新修改的内容进行发布。对于未发布的文章内容,只有授权用户可以在控制台进行查看,不会出现在站点内容中;
点击发布按钮将直接发布修改后的内容。对于已发布的文章,默认可以通过站点地址进行公开访问,用户可以在文章高级设置中修改可见性。
:::
## 文章设置
当你想修改一篇文章的标题、所属分类等信息时,你可以通过以下方式进行操作:
1. 点击文章列表指定文章所在行的 `···` 更多操作按钮,选择设置;
2. 在文章列表点击指定文章的标题进入文章编辑页面,点击页面右上角的设置按钮。
![文章编辑界面说明](/img/user-guide/posts/post-setting.png)
### 设置说明
- **标题**:用于在主题端显示的文章标题;
- **别名**:多用于文章 URL 路径定位,一般不会出现在页面中;
- **分类目录**:文章所属分类,方便用户区分文章类型进行针对性浏览,一篇文章可以属于多个分类;
- **标签**:文章添加的标签,方便用户更进一步标识文章信息,一篇文章可以添加多个标签;
- **自动生成摘要**:文章摘要是对文章内容的概括性描述;
- **是** 系统根据文章内容,自动生成一段摘要文本;
- **否** 用户自行输入文章摘要文本。
- **允许评论**:是否允许用户在主题端浏览文章时对该文章发起评论;
- **是否置顶**:文章是否排序在文章列表的最顶部;
- **可见性**:访问站点主题端时,哪些人可以看到这篇文章;
- **公开**:所有用户均可看到这篇文章,包括未登录用户;
- **内部成员可访问**:仅登录用户可以看到这篇文章;
- **私有**:仅文章作者可以看到这篇文章。
- **发表时间**:手动指定文章的发表时间,未指定时以实际发布时间为准;
- **自定义模板**:主题端使用哪一个文章模板进行页面渲染;
- **封面图**:用户在主题端显示的文章封面图,需要主题支持该功能。
## 发布及取消发布
对于已发布的文章,默认可以通过站点地址进行公开访问,用户可以在文章高级设置中修改可见性。
你可以在上文介绍的文章设置对话框中,修改文章的发布状态。
在文章设置对话框中,若文章当前处于已发布状态,下方会显示取消发布按钮;若文章处于未发布状态,下方怎会显示发布按钮。
## 删除文章
当你不再需要一篇文章时,你可以通过以下方式删除该文章:
1. 点击文章列表指定文章所在行的 `···` 更多操作按钮,选择删除;
2. 勾选文章列表中的全选/多选框,选中一篇或多篇文章进行批量删除。
文章删除后会进入回收站中,点击右上角的回收站按钮进入回收站。在回收站中,你可以永久删除或恢复指定的文章。
:::warning
文章永久删除后将从数据库删除该记录,后续无法再恢复找回。
:::
## 文章分类管理
通过分类可以更好的组织管理文章。分类之间存在层级关系,一个父分类下可包含多个子分类。一篇文章可以同时属于多个分类。
在文章管理页面,点击页面右上角的 `分类` 按钮即可进入分类管理页面。
### 新建文章分类
点击分类管理页面右上角的 `新建` 按钮即可新建一个分类。
![文章分类编辑界面说明](/img/user-guide/posts/category-create.png)
#### 设置说明
- **名称**:用于在主题端显示的分类名称;
- **别名**:用于分类 URL 路径定位。默认路径规则为 `$HALO_EXTERNAL_URL/categories/{slug}`,其中 `slug` 为分类别名,访问该地址即可浏览该分类下的所有文章。分类页路由前缀可[在设置中修改](./settings#主题路由设置)
- **自定义模板**:主题端使用哪一个文章分类模板进行页面渲染;
- **封面图**:用户在主题端显示的文章分类封面图,需要主题支持该功能;
- **描述**:关于改文章分类的更多描述信息。
### 调整分类层级
分类之间存在层级关系,一个父分类下可包含多个子分类。
你可以按住分类前的图标,通过拖拽来调整分类间的层级关系和顺序。
![移动文章分类](/img/user-guide/posts/category-move.gif)
### 修改删除分类
点击指定分类所在行后方的 `···` 更多操作按钮,可以对文章分类进行修改或删除。
:::warning
文章分类删除后,对应文章的关联将被解除,且该操作不可恢复,请谨慎进行该操作。
:::
## 文章标签管理
标签可以用于为文章添加特定标记,与分类不同的是标签之间没有层级关系。一篇文章也可以同时添加多个标签。
### 新建文章标签
点击分类管理页面右上角的 `新建` 按钮即可新建一个分类。
![标签编辑界面说明](/img/user-guide/posts/tag-create.png)
#### 设置说明
- **名称**:用于在主题端显示的标签名称;
- **别名**:用于标签 URL 路径定位。默认路径规则为 `$HALO_EXTERNAL_URL/tags/{slug}`,其中 `slug` 为标签别名,访问该地址即可浏览具有该标签的所有文章。标签页路由前缀可[在设置中修改](./settings#主题路由设置)
- **颜色**:用于在控制台及主题端显示的标签颜色,主题端显示颜色需要主题支持该功能;
- **封面图**:用户在主题端显示的标签封面图,需要主题支持该功能;
### 修改删除标签
标签页面提供两种浏览模式,列表模式和平铺模式。点击右上方红框区域对应模式的切换按钮即可进行切换。
![标签浏览](/img/user-guide/posts/tag-list.png)
在列表模式下,点击指定标签所在行后方的 `···` 更多操作按钮,可以对文章标签进行修改或删除。
在平铺模式下,直接点击对应的标签,即可对文章标签进行修改。
:::warning
文章标签删除后,对应文章的关联将被解除,且该操作不可恢复,请谨慎进行该操作。
:::

@ -0,0 +1,72 @@
---
title: 站点设置
description: 站点设置相关功能说明
---
## 基本设置
Halo 提供了以下站点基本信息设置:
- **站点标题**
- **站点副标题**
- **站点 Logo 图片**
- **站点 Favion 图片**
在控制台设置完成后,主题端可以通过特定的表达式获取到这些信息并且在对应的位置进行展示。具体是否读取使用这些配置、在哪些位置显示这些信息由使用的不同主题而决定。
以 Halo 2.0 的[默认主题 Earth](https://github.com/halo-dev/theme-earth) 为例,这些设置信息将在如下位置进行展示。
![默认主题基本设置说明](/img/user-guide/settings/setting-basic.png)
## 文章设置
针对主题端的文章展示Halo 提供了以下设置项:
- **文章列表显示条数**
- **归档页文章显示条数**
- **分类页文章显示条数**
- **标签页文章显示条数**
## SEO设置
针对站点的 SEO搜索引擎优化需求Halo 提供了以下设置项:
- **屏蔽搜索引擎**:配置后会在所有页面 HTML 源码的 head 部分添加 `<meta name="robots" content="noindex" />`
- **站点关键词**:格式为以`,`分隔的关键词列表,配置后会在所有页面 HTML 源码的 head 部分添加 `<meta name="keywords"> content="{用户的配置关键词}" />`
- **站点描述**:配置后会在所有页面 HTML 源码的 head 部分添加 `<meta name=""> content="{用户的配置描述}" />`
## 评论设置
针对站点的评论功能Halo 提供了以下设置项:
- **启用评论**:全局评论功能开关配置,修改后影响所有文章、页面的评论功能;
- **新评论审核**:新增的评论是否需要在控制台进行审核,审核通过后其他访问者才能看到该条评论;
- **仅允许注册用户评论**:开启后只有登录用户才能添加评论,关闭后匿名(未登录)访问者也可以添加评论。
## 主题路由设置
针对访问站点各种类型页面的 URL 生成规则Halo 提供了以下主题路由设置项:
- **分类页路由前缀**:定位到指定文章分类页面的 URL 规则前缀,默认为 `/categories/{文章分类别名}`,修改后为 `/{用户定义的分类页路由前缀/{文章分类别名}`
- **标签页路由前缀**:定位到指定文章标签页面的 URL 规则前缀,默认为 `/tags/{文章标签别名}`,修改后为 `/{用户定义的标签页路由前缀/{文章标签别名}`
- **归档页路由前缀**:定位到文章归档页面的 URL 规则前缀,默认为 `/archives/{年}/{月}` 等路径,修改后为 `/{用户定义的归档页路由前缀/{年}/{月}` 等;
- **文章详情页访问规则**:定位到具体文章详情页面的 URL 规则前缀,默认为 `/archives/{文章别名}` ,用户可从以下路径规则进行选择:
- `/archives/{slug}`
- `/archives/{name}`
- `/?p={name}`
- `/?p={slug}`
- `/?p={slug}`
- `/{year}/{slug}`
- `/{year}/{month}/{slug}`
- `/{year}/{month}/{day}/{slug}`
:::info 变量说明
- `slug`:文章别名;
- `name`:文章 `metadata.name` 字段值;
- `year`:四位数格式的文章发布年份;
- `month`:两位数格式的文章发布月份:
- `day`:两位数格式的文章发布日。
:::
## 代码注入
你可以使用代码注入功能,在特定类型的页面中注入额外的代码。你可以通过该功能覆盖或补充部分主题 CSS 样式,或者引入额外的 JavaScript 脚本扩展主题端功能。

@ -0,0 +1,68 @@
---
title: 主题
description: 主题管理相关功能说明
---
主题包含了各种站点页面模板的资源包。用户访问 Halo 站点浏览到的内容及样式,由 Halo 管理端所配置使用的主题所决定。
:::info
当前 Halo 支持的主题可在[Awesome Halo](https://github.com/halo-sigs/awesome-halo)仓库查看。
:::
## 安装主题
点击主题页面右上方的 `切换主题` 按钮即可弹出主题管理对话框。
目前 Halo 提供了两种主题安装方式:
1. 通过控制台上传安装
2. 将主题文件夹拷贝到服务器上的 `{Halo 工作目录}/themes/` 目录下等待 Halo 自动扫描
针对第一种安装方式,你可以点击主题管理对话框下方的 `安装主题` 按钮,在弹出的文件上传对话框中上传主题压缩包。
![安装主题](/img/user-guide/themes/theme-install.png)
主题安装成功后,便会出现在已安装主题列表中。
针对第二种方式,你需要手动将解压后的主题文件夹拷贝到服务器上的指定目录下,如果主题校验通过,你便可以在主题列表的 `未安装` 标签页中看到该主题。
之后点击主题所在行后方的 `安装` 按钮,即可完成该主题的安装。
![后台安装主题](/img/user-guide/themes/theme-install-alt.png)
## 切换主题
同样点击主题页面右上方的 `切换主题` 按钮弹出主题管理对话框。
在弹框中点击选中要切换的目标主题,此时页面返回到主题详情页,点击右上角的 `启用` 按钮即可将当前主题切换为实际使用的主题。
:::info
仅切换主题不点击右上角的 `启用` 按钮时,不会影响当前实际使用的主题。
:::
## 修改主题设置
主题页面默认显示出了当前主题的详细信息,在详细信息标签页后方的标签页,即为主题提供的相关设置。不同的主题提供的设置项各不相同,设置项的详细说明请参考对应主题的文档。
![主题设置](/img/user-guide/themes/theme-setting.png)
以 Halo 的默认主题 Earth 为例,该主题提供了布局、样式、侧边栏、页脚及备案设置共五组配置项。
## 预览主题
通过预览功能,你可以在不更改当前启用主题的情况下查看不同主题的效果。点击主题详情页面右上角的 `预览` 按钮预览当前主题,或者进入已安装主题列表,通过后方 `···` 的更多操作中的预览选项预览指定的主题。
![主题设置](/img/user-guide/themes/theme-preview.png)
在主题预览页面你可以切换不同分辨率的设备,模拟主题在不同终端下的显示效果。也可以通过右上角的功能菜单切换预览的主题,或者调整当前主题的设置,查看不同设置下主题所展现的区别。
## 更新主题
点击主题详情页右上角的 `···` 更多操作按钮,选择其中的 `更新` 选项即可上传新的主题包对当前主题进行更新。
## 重载主题配置
如果当前主题提供的设置象发生变化,可以通过 `···` 更多中的 `重载主题配置` 选项对主体配置项进行更新。
## 卸载主题
进入已安装主题列表,通过后方 `···` 的更多操作中的卸载选项即可卸载指定的主题。
![卸载主题](/img/user-guide/themes/theme-uninstall.png)

@ -0,0 +1,94 @@
---
title: 用户与权限
description: 用户管理、权限配置相关功能说明
---
## 概览
Halo 2.0 版本采用了基于角色的权限控制RBAC体系。不同于之前 1.x 版本的单用户设计,现在你可以创建多个用户并且通过赋予不同用户不同角色的方式,为他们分配不同的权限。
Halo 中的权限控制体系示例:
![权限控制体系示例](/img/user-guide/user-permission.png)
## 用户
点击左侧导航栏的 `用户` 条目进入用户管理页面。
### 创建用户
在用户管理页面,点击右上角的 `添加用户` 按钮即可弹出新建用户对话框。
![创建用户](/img/user-guide/users/user-setting.png)
目前支持配置的用户属性有:
- **用户名**:用户登录 Halo 控制台使用的用户名,不可与已有的用户名重复;
- **显示名称**:用于显示文章作者等用户信息时使用的,更友好的用户名称;
- **密码**
- **电子邮箱**
- **手机号**
- **头像**
- **描述**
### 其他操作
点击用户列表中的 `···` 更多操作按钮,可以对指定用户进行修改资料、修改密码等其他更多操作。
![用户列表其他操作](/img/user-guide/users/user-operate.png)
#### 修改用户资料
你可以修改除用户名以外的所有资料,具有用户管理相关权限的用户,也可对其他用户的资料进行修改。
#### 修改用户密码
对于已有的用户,你可以在 `···` 更多操作中修改指定用户的登录密码。
#### 分配角色
对于已有的用户,你可以在 `···` 更多操作中为该用户分配角色,分配角色后该用户具有角色所对应的权限。
:::info
修改用户分配的角色会影响用户所拥有的权限,可能影响用户使用。
:::
#### 删除用户
对于已有的用户,你可以在 `···` 更多操作中删除该用户。
:::warning
删除用户后,该用户之前创建的文章、页面等内容的作者信息将会丢失,可能影响站点内容浏览。此操作不可恢复。
:::
## 角色
如下图所示,在用户管理页面,点击右上角红色区域的 `角色管理` 按钮,即可进入到角色管理页面。
![角色管理](/img/user-guide/users/role-management.png)
### 创建角色
Halo 提供了全新创建和基于已有角色创建两种角色创建方式。
#### 全新创建
点击角色管理页面右上角的 `新建角色` 按钮即可弹出新建角色对话框,通过这种方式创建的角色默认未勾选任何权限,你需要在对话框中点击切换到权限标签页,为该角色勾选需要的权限。
#### 基于已有角色创建
当系统中已经存在一些基础角色时,你可以点击某个角色所在行中的 `···` 更多操作按钮,选择 `基于此角色创建` 来创建一个新的角色。通过这种方式创建的角色默认继承了原有角色的权限,但你同样可以对新角色的权限进行进一步调整。
![基于已有角色创建角色](/img/user-guide/users/role-fork.png)
### 修改角色信息
对于已有的角色,你可以在 `···` 更多操作中修改指定角色的名称和该角色所拥有的权限。
### 其他操作
对于已有的角色,你可以在 `···` 更多操作中删除指定角色。
:::warning
删除角色后,分配了该角色的用户会丧失对应的权限,影响用户使用。此操作不可恢复。
:::

@ -0,0 +1,151 @@
{
"tutorialSidebar": [
"intro",
{
"type": "category",
"label": "入门",
"link": {
"type": "generated-index"
},
"collapsed": false,
"items": [
"getting-started/prepare",
{
"type": "category",
"label": "安装指南",
"link": {
"type": "generated-index"
},
"items": [
"getting-started/install/docker-compose",
"getting-started/install/docker",
{
"type": "category",
"label": "其他指南",
"items": [
"getting-started/install/other/oneinstack"
]
},
"getting-started/install/third-party"
]
},
"getting-started/migrate-from-1.x",
"getting-started/first-post"
]
},
{
"type": "category",
"label": "用户指南",
"link": {
"type": "generated-index"
},
"items": [
"user-guide/common",
"user-guide/posts",
"user-guide/pages",
"user-guide/attachments",
"user-guide/themes",
"user-guide/plugins",
"user-guide/users",
"user-guide/settings"
]
},
{
"type": "category",
"label": "开发者指南",
"link": {
"type": "generated-index"
},
"items": [
{
"type": "category",
"label": "系统开发",
"link": {
"type": "generated-index"
},
"items": [
"developer-guide/core/prepare",
"developer-guide/core/run"
]
},
{
"type": "category",
"label": "插件开发",
"link": {
"type": "generated-index"
},
"items": [
"developer-guide/plugin/prepare"
]
},
{
"type": "category",
"label": "主题开发",
"link": {
"type": "generated-index"
},
"items": [
"developer-guide/theme/prepare",
"developer-guide/theme/config",
"developer-guide/theme/structure",
"developer-guide/theme/template-route-mapping",
"developer-guide/theme/static-resources",
"developer-guide/theme/settings",
{
"type": "category",
"label": "模板变量",
"link": {
"type": "doc",
"id": "developer-guide/theme/template-variables"
},
"items": [
"developer-guide/theme/template-variables/index_",
"developer-guide/theme/template-variables/post",
"developer-guide/theme/template-variables/page",
"developer-guide/theme/template-variables/archives",
"developer-guide/theme/template-variables/tags",
"developer-guide/theme/template-variables/tag",
"developer-guide/theme/template-variables/categories",
"developer-guide/theme/template-variables/category"
]
},
{
"type": "category",
"label": "Finder API",
"link": {
"type": "doc",
"id": "developer-guide/theme/finder-apis"
},
"items": [
"developer-guide/theme/finder-apis/category",
"developer-guide/theme/finder-apis/tag",
"developer-guide/theme/finder-apis/post",
"developer-guide/theme/finder-apis/single-page",
"developer-guide/theme/finder-apis/comment",
"developer-guide/theme/finder-apis/contributor",
"developer-guide/theme/finder-apis/menu",
"developer-guide/theme/finder-apis/site-stats",
"developer-guide/theme/finder-apis/theme",
"developer-guide/theme/finder-apis/plugin"
]
},
"developer-guide/theme/code-snippets"
]
},
"developer-guide/form-schema"
]
},
{
"type": "category",
"label": "参与贡献",
"link": {
"type": "generated-index"
},
"items": [
"contribution/issue",
"contribution/pr"
]
},
"about"
]
}

@ -1 +1,6 @@
["1.6", "1.5", "1.4"] [
"2.0",
"1.6",
"1.5",
"1.4"
]

Loading…
Cancel
Save