docs: update documentation for Halo 2.10 (#262)

为 Halo 2.10.0 更新文档。

/kind documentation

```release-note
None 
```
This commit is contained in:
Ryan Wang
2023-10-10 12:40:27 +08:00
committed by GitHub
parent f1e2d27c8a
commit ba0c62ef6b
112 changed files with 9549 additions and 120 deletions

View File

@@ -0,0 +1,64 @@
---
title: 模型元数据
---
在 [元数据表单定义](../annotations-form.md) 我们介绍了如何为模型添加元数据表单,此文档将介绍如何在主题模板中使用元数据。
我们在模板中专门为获取 annotations 数据提供了三个方法,可以更加方便的设置默认值和判断元数据字段是否存在。
## #annotations.get(extension,key)
### 描述
根据对象和元数据的 key 获取元数据的值。
### 示例
```html {4}
<div th:with="menu = ${menuFinder.getPrimary()}">
<ul th:with="menuItems = ${menu.menuItems}">
<li th:each="menuItem : ${menuItems}">
<i th:class="${#annotations.get(menuItem, 'icon')}"></i>
<a th:href="@{${menuItem.status.href}}" th:text="${menuItem.status.displayName}"></a>
</li>
</ul>
</div>
```
## #annotations.getOrDefault(extension,key,defaultValue)
### 描述
根据对象和元数据的 key 获取元数据的值,同时支持设置默认值。
### 示例
```html {4}
<div th:with="menu = ${menuFinder.getPrimary()}">
<ul th:with="menuItems = ${menu.menuItems}">
<li th:each="menuItem : ${menuItems}">
<i th:class="${#annotations.getOrDefault(menuItem, 'icon', 'fa')}"></i>
<a th:href="@{${menuItem.status.href}}" th:text="${menuItem.status.displayName}"></a>
</li>
</ul>
</div>
```
## #annotations.contains(extension,key)
### 描述
根据对象和元数据的 key 判断元数据是否存在。
### 示例
```html {4}
<div th:with="menu = ${menuFinder.getPrimary()}">
<ul th:with="menuItems = ${menu.menuItems}">
<li th:each="menuItem : ${menuItems}">
<i th:if="${#annotations.contains(menuItem, 'icon')}" th:class="${#annotations.get(menuItem, 'icon')}"></i>
<a th:href="@{${menuItem.status.href}}" th:text="${menuItem.status.displayName}"></a>
</li>
</ul>
</div>
```

View File

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

View File

@@ -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`。
:::

View File

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

View File

@@ -0,0 +1,187 @@
---
title: 文章分类
description: 文章分类 - CategoryFinder
---
import CategoryVo from "../vo/CategoryVo.md"
import CategoryTreeVo from "../vo/CategoryTreeVo.md"
## 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
<CategoryVo />
### 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
<CategoryTreeVo />
- [#CategoryTreeVo](#categorytreevo)

View File

@@ -0,0 +1,155 @@
---
title: 评论
description: 评论 - CommentFinder
---
import CommentVo from "../vo/CommentVo.md"
import ReplyVo from "../vo/ReplyVo.md"
## 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
<CommentVo />
### 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
<ReplyVo />
### 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"
}
```

View File

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

View File

@@ -0,0 +1,87 @@
---
title: 导航菜单
description: 导航菜单 - MenuFinder
---
import MenuItemVo from "../vo/MenuItemVo.md"
import MenuVo from "../vo/MenuVo.md"
## 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.status.displayName}"
th:target="${menuItem.spec.target?.value}"
>
</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.status.displayName}"
th:target="${menuItem.spec.target?.value}"
>
</a>
</li>
</ul>
</div>
```
## 类型定义
### MenuVo
<MenuVo />
### MenuItemVo
<MenuItemVo />

View File

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

View File

@@ -0,0 +1,430 @@
---
title: 文章
description: 文章 - PostFinder
---
import CategoryVo from "../vo/CategoryVo.md";
import TagVo from "../vo/TagVo.md";
import PostVo from "../vo/PostVo.md";
import ContentVo from "../vo/ContentVo.md"
import ContributorVo from "../vo/ContributorVo.md"
import ListedPostVo from "../vo/ListedPostVo.md"
## 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` - 分页条数
3. `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` - 分页条数
3. `year:string` - 年份
4. `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
<CategoryVo />
### TagVo
<TagVo />
### ContributorVo
<ContributorVo />
### PostVo
<PostVo />
- [#CategoryVo](#categoryvo)
- [#TagVo](#tagvo)
- [#ContributorVo](#contributorvo)
- [#ContentVo](#contentvo)
### ContentVo
<ContentVo />
### NavigationPostVo
```json title="NavigationPostVo"
{
"previous": "#PostVo", // 上一篇文章
"current": "#PostVo", // 当前文章
"next": "#PostVo" // 下一篇文章
}
```
- [#PostVo](#postvo)
### ListedPostVo
<ListedPostVo />
- [#CategoryVo](#categoryvo)
- [#TagVo](#tagvo)
- [#ContributorVo](#contributorvo)
### 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)

View File

@@ -0,0 +1,131 @@
---
title: 独立页面
description: 独立页面 - SinglePageFinder
---
import SinglePageVo from "../vo/SinglePageVo.md"
import ListedSinglePageVo from "../vo/ListedSinglePageVo.md"
import ContributorVo from "../vo/ContributorVo.md"
import ContentVo from "../vo/ContentVo.md"
## 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
<SinglePageVo />
- [#ContributorVo](#contributorvo)
- [#ContentVo](#contentvo)
### ListedSinglePageVo
- [#ContributorVo](#contributorvo)
<ListedSinglePageVo />
### 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
<ContentVo />
### ContributorVo
<ContributorVo />

View File

@@ -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 // 分类数量
}
```

View File

@@ -0,0 +1,139 @@
---
title: 文章标签
description: 文章标签 - TagFinder
---
import TagVo from "../vo/TagVo.md"
## 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
<TagVo />
### 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)

View File

@@ -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", // Logo
"website": "string", // 网站
"repo": "string", // 仓库地址
"version": "string", // 版本
"require": "string", // 依赖 Halo 的版本
"settingName": "string", // 表单定义的名称,即 Setting 资源的 metadata.name
"configMapName": "string", // 设置项存储的名称,即 ConfigMap 资源的 metadata.name
"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": {}
}
```

View File

@@ -0,0 +1,58 @@
---
title: 全局变量
description: 本文档介绍 Halo 为模板引擎提供的专有全局变量。
---
import SiteSettingVo from "./vo/SiteSettingVo.md"
import ThemeVo from "./vo/ThemeVo.md"
Halo 目前为模板引擎在全局提供了一些变量,本文档将列出已提供的变量以及介绍这些变量的使用方法。
## site
### 描述
提供了部分可公开的系统相关的设置项,其中所有参数均来自于 Console 的系统设置。
### 类型
<SiteSettingVo />
### 示例
显示站点标题:
```html
<h1 th:text="${site.title}"></h1>
```
显示站点 Logo
```html
<img th:src="${site.logo}" alt="Logo" />
```
## theme
### 描述
关于当前激活主题的信息。
### 类型
<ThemeVo />
### 示例
显示主题名称:
```html
<h1 th:text="${theme.spec.displayName}"></h1>
```
在静态资源加入版本号参数,以防止升级之后的缓存问题:
```html
<link rel="stylesheet" th:href="@{/assets/dist/style.css?v={version}(version=${theme.spec.version})}" />
<script th:src="@{/assets/dist/main.iife.js?v={version}(version=${theme.spec.version})}"></script>
```

View File

@@ -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` 模板渲染后的页面了。

View File

@@ -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` 字段。
:::

View File

@@ -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)
:::

View File

@@ -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)。

View File

@@ -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/` 目录下创建。
:::

View File

@@ -0,0 +1,57 @@
---
title: 自定义标签
description: 本文档介绍 Halo 为模板引擎提供的专有标签。
---
Halo 为满足部分代码注入和模板扩展点的需求,提供了一些专有标签,本文档将列出已支持的标签以及介绍这些标签的使用方法。
## halo:comment
### 描述
此标签用作评论组件的扩展点,如果有插件实现了这个扩展点,那么将在编写了此标签的模板中显示插件提供的内容。
### 使用示例
```html title="/templates/post.html"
<div th:if="${pluginFinder.available('PluginCommentWidget')}">
<halo:comment
group="content.halo.run"
kind="Post"
th:attr="name=${post.metadata.name}"
colorScheme="window.main.currentColorScheme"
/>
</div>
```
参数详解:
1. `group` - 自定义模型的分组,目前已支持的模型请参考下面表格。
2. `kind` - 自定义模型的类型,目前已支持的模型请参考下面表格。
3. `name` - 自定义模型数据的唯一标识。
4. `colorScheme` - 评论组件的颜色方案,支持 light 和 dark 两种,支持固定或者 JavaScript 变量。需要注意的是,如果需要固定一个值,那么需要添加单引号,如 'dark'。使用 JavaScript 变量时不需要。
已支持的模型列表:
| 对应模型 | group | kind |
| ---------- | ---------------- | ---------- |
| 文章 | content.halo.run | Post |
| 自定义页面 | content.halo.run | SinglePage |
## halo:footer
### 描述
支持将系统设置中的页脚代码注入内容插入到此标签。
### 使用示例
```html
<footer>
<halo:footer />
</footer>
```
:::info 注意
为了保证 Halo 的功能完整性,建议主题开发者尽可能在主题中实现此标签。
:::

View File

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

View File

@@ -0,0 +1,108 @@
---
title: 文章归档
description: archives.html - /archives
---
import CategoryVo from "../vo/CategoryVo.md";
import TagVo from "../vo/TagVo.md";
import ContributorVo from "../vo/ContributorVo.md";
import ListedPostVo from "../vo/ListedPostVo.md";
## 路由信息
- 模板路径:`/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
<CategoryVo />
### TagVo
<TagVo />
### ContributorVo
<ContributorVo />
### ListedPostVo
<ListedPostVo />
- [#CategoryVo](#categoryvo)
- [#TagVo](#tagvo)
- [#ContributorVo](#contributorvo)
### PostArchiveVo
```json title="PostArchiveVo"
{
"year": "string", // 年份
"months": [ // 按月的文章集合
{
"month": "string", // 月份
"posts": "List<#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)

View File

@@ -0,0 +1,104 @@
---
title: 作者归档
description: author.html - /authors/:name
---
import UserVo from "../vo/UserVo.md"
import CategoryVo from "../vo/CategoryVo.md"
import TagVo from "../vo/TagVo.md"
import ContributorVo from "../vo/ContributorVo.md"
import ListedPostVo from "../vo/ListedPostVo.md"
## 路由信息
- 模板路径:`/templates/author.html`
- 访问路径:`/authors/:name`
## 变量
### author
#### 变量类型
[#UserVo](#uservo)
### posts
#### 变量类型
[#UrlContextListResult<ListedPostVo\>](#urlcontextlistresultlistedpostvo)
#### 示例
```html title="/templates/author.html"
<div>
<h1 th:text="${author.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>
```
## 类型定义
### UserVo
<UserVo />
### CategoryVo
<CategoryVo />
### TagVo
<TagVo />
### ContributorVo
<ContributorVo />
### ListedPostVo
<ListedPostVo />
- [#CategoryVo](#categoryvo)
- [#TagVo](#tagvo)
- [#ContributorVo](#contributorvo)
### 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)

View File

@@ -0,0 +1,54 @@
---
title: 文章分类集合
description: categories.html - /categories
---
import CategoryTreeVo from "../vo/CategoryTreeVo.md"
## 路由信息
- 模板路径:`/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
<CategoryTreeVo />
- [#CategoryTreeVo](#categorytreevo)

View File

@@ -0,0 +1,105 @@
---
title: 分类归档
description: category.html - /categories/:slug
---
import CategoryVo from "../vo/CategoryVo.md"
import TagVo from "../vo/TagVo.md"
import ContributorVo from "../vo/ContributorVo.md";
import ListedPostVo from "../vo/ListedPostVo.md"
## 路由信息
- 模板路径:`/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
<CategoryVo />
### TagVo
<TagVo />
### ContributorVo
<ContributorVo />
### ListedPostVo
<ListedPostVo />
- [#CategoryVo](#categoryvo)
- [#TagVo](#tagvo)
- [#ContributorVo](#contributorvo)
### 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)

View File

@@ -0,0 +1,98 @@
---
title: 首页
description: index.html - /
---
import CategoryVo from "../vo/CategoryVo.md"
import TagVo from "../vo/TagVo.md"
import ContributorVo from "../vo/ContributorVo.md";
import ListedPostVo from "../vo/ListedPostVo.md"
## 路由信息
- 模板路径:`/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
<CategoryVo />
### TagVo
<TagVo />
### ContributorVo
<ContributorVo />
### ListedPostVo
<ListedPostVo />
- [#CategoryVo](#categoryvo)
- [#TagVo](#tagvo)
- [#ContributorVo](#contributorvo)
### 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)

View File

@@ -0,0 +1,53 @@
---
title: 独立页面
description: page.html - /:slug
---
import SinglePageVo from "../vo/SinglePageVo.md"
import ContributorVo from "../vo/ContributorVo.md"
import ContentVo from "../vo/ContentVo.md"
## 路由信息
- 模板路径:`/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
<SinglePageVo />
- [#ContentVo](#contentvo)
- [#ContributorVo](#contributorvo)
### ContentVo
<ContentVo />
### ContributorVo
<ContributorVo />

View File

@@ -0,0 +1,65 @@
---
title: 文章
description: post.html - /archives/:slug
---
import CategoryVo from "../vo/CategoryVo.md"
import TagVo from "../vo/TagVo.md"
import ContentVo from "../vo/ContentVo.md"
import ContributorVo from "../vo/ContributorVo.md"
import PostVo from "../vo/PostVo.md"
## 路由信息
- 模板路径:`/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
<CategoryVo />
### TagVo
<TagVo />
### ContributorVo
<ContributorVo />
### ContentVo
<ContentVo />
### PostVo
<PostVo />
- [#CategoryVo](#categoryvo)
- [#TagVo](#tagvo)
- [#ContributorVo](#contributorvo)
- [#ContentVo](#contentvo)

View File

@@ -0,0 +1,105 @@
---
title: 标签归档
description: tag.html - /tags/:slug
---
import CategoryVo from "../vo/CategoryVo.md"
import TagVo from "../vo/TagVo.md"
import ContributorVo from "../vo/ContributorVo.md";
import ListedPostVo from "../vo/ListedPostVo.md"
## 路由信息
- 模板路径:`/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
<CategoryVo />
### TagVo
<TagVo />
### ContributorVo
<ContributorVo />
### ListedPostVo
<ListedPostVo />
- [#CategoryVo](#categoryvo)
- [#TagVo](#tagvo)
- [#ContributorVo](#contributorvo)
### 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)

View File

@@ -0,0 +1,39 @@
---
title: 文章标签集合
description: tags.html - /tags
---
import TagVo from '../vo/TagVo.md'
## 路由信息
- 模板路径:`/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
<TagVo />

View File

@@ -0,0 +1,33 @@
```json title="CategoryTreeVo"
{
"metadata": {
"name": "string", // 唯一标识
"labels": {
"additionalProp1": "string"
},
"annotations": {
"additionalProp1": "string"
},
"creationTimestamp": "2022-11-20T14:18:49.230Z", // 创建时间
},
"spec": {
"displayName": "string", // 显示名称
"slug": "string", // 别名,通常用于生成 status.permalink
"description": "string", // 描述
"cover": "string", // 封面图
"template": "string", // 自定义渲染模板名称
"priority": 0, // 排序字段
"children": [ // 下级分类,分类的 metadata.name 集合
"string"
]
},
"status": {
"permalink": "string", // 固定链接
"postCount": 0, // 文章数
"visiblePostCount": 0 // 已发布文章数
},
"children": "List<#CategoryTreeVo>", // 下级分类CategoryTreeVo 的集合
"parentName": "string",
"postCount": 0
}
```

View File

@@ -0,0 +1,31 @@
```json title="CategoryVo"
{
"metadata": {
"name": "string", // 唯一标识
"labels": {
"additionalProp1": "string"
},
"annotations": {
"additionalProp1": "string"
},
"creationTimestamp": "2022-11-20T13:06:38.512Z", // 创建时间
},
"spec": {
"displayName": "string", // 显示名称
"slug": "string", // 别名,通常用于生成 status.permalink
"description": "string", // 描述
"cover": "string", // 封面图
"template": "string", // 自定义渲染模板名称
"priority": 0, // 排序字段
"children": [ // 下级分类,分类的 metadata.name 集合
"string"
]
},
"status": {
"permalink": "string", // 固定链接
"postCount": 0, // 文章数
"visiblePostCount": 0 // 已发布文章数
},
"postCount": 0
}
```

View File

@@ -0,0 +1,53 @@
```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", // 评论者 UserAgent 信息
"ipAddress": "string", // 评论者 IP 地址
"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"
}
}
```

View File

@@ -0,0 +1,6 @@
```json title="ContentVo"
{
"raw": "string", // 原始文本,一般用于给编辑器使用
"content": "string" // 最终渲染的文本
}
```

View File

@@ -0,0 +1,9 @@
```json title="ContributorVo"
{
"name": "string", // 用户名
"displayName": "string", // 显示名称
"avatar": "string", // 头像
"bio": "string", // 描述
"permalink": "string" // 作者的文章归档页面链接
}
```

View File

@@ -0,0 +1,65 @@
```json title="ListedPostVo"
{
"metadata": {
"name": "string", // 唯一标识
"labels": {
"additionalProp1": "string"
},
"annotations": {
"additionalProp1": "string"
},
"creationTimestamp": "2022-11-20T13:06:38.505Z", // 创建时间
},
"spec": {
"title": "string", // 标题
"slug": "string", // 别名,通常用于生成 status.permalink
"releaseSnapshot": "string",
"headSnapshot": "string",
"baseSnapshot": "string",
"owner": "string", // 创建者名称,即 ContributorVo 的 metadata.name非显示名称
"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": [ // 分类的名称集合,即 Category 的 metadata.name 的集合
"string"
],
"tags": [ // 标签的名称集合,即 Tag 的 metadata.name 的集合
"string"
],
"htmlMetas": [
{
"additionalProp1": "string"
}
]
},
"status": {
"permalink": "string", // 固定链接
"excerpt": "string", // 最终生成的摘要
"inProgress": true,
"lastModifyTime": "2022-11-20T13:06:38.505Z", // 最后修改时间
"commentsCount": 0, // 评论数
"contributors": [ // 贡献者名称Contributor 的 metadata.name 的集合
"string"
]
},
"categories": "List<#CategoryVo>", // 分类的集合
"tags": "List<#TagVo>", // 标签的集合
"contributors": "List<#ContributorVo>", // 贡献者的集合
"owner": "#ContributorVo", // 创建者
"stats": {
"visit": 0, // 访问数量
"upvote": 0, // 点赞数量
"comment": 0 // 评论数量
}
}
```

View File

@@ -0,0 +1,57 @@
```json title="ListedSinglePageVo"
{
"metadata": {
"name": "string", // 唯一标识
"labels": {
"additionalProp1": "string"
},
"annotations": {
"additionalProp1": "string"
},
"creationTimestamp": "2022-11-20T14:31:00.876Z" // 创建时间
},
"spec": {
"title": "string", // 标题
"slug": "string", // 别名,通常用于生成 status.permalink
"releaseSnapshot": "string",
"headSnapshot": "string",
"baseSnapshot": "string",
"owner": "string", // 创建者名称,即 ContributorVo 的 metadata.name非显示名称
"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,
"lastModifyTime": "2022-11-20T13:06:38.505Z", // 最后修改时间
"commentsCount": 0, // 评论数
"contributors": [ // 贡献者名称Contributor 的 metadata.name 的集合
"string"
]
},
"stats": {
"visit": 0, // 访问数量
"upvote": 0, // 点赞数量
"comment": 0 // 评论数量
},
"contributors": "List<#ContributorVo>", // 贡献者的集合
"owner": "#ContributorVo" // 创建者
}
```

View File

@@ -0,0 +1,44 @@
```json title="MenuItemVo"
{
"metadata": {
"name": "string", // 唯一标识
"labels": {
"additionalProp1": "string"
},
"annotations": {
"additionalProp1": "string"
},
"creationTimestamp": "2022-11-20T14:44:58.984Z", // 创建时间
},
"spec": {
"displayName": "string", // 显示名称,但是不要直接使用这个字段进行显示,最终字段为 status.displayName
"href": "string", // 链接,同样不要直接使用这个字段,最终字段为 status.href
"priority": 0, // 排序字段
"children": [ // 下级菜单项,菜单项的 metadata.name 集合
"string"
],
"target": "#Target", // 菜单页面打开方式,枚举类型
"targetRef": { // 与其他资源比如文章的关联,一般无需直接使用
"group": "string",
"version": "string",
"kind": "string",
"name": "string"
},
},
"status": {
"displayName": "string", // 显示名称
"href": "string" // 链接
},
"children": "List<#MenuItemVo>", // 下级菜单项MenuItemVo 的集合
"parentName": "string",
}
```
```java title="Target"
enum Target {
BLANK("_blank"), // 在新窗口打开
SELF("_self"), // 在当前窗口打开
PARENT("_parent"), // 在父窗口打开
TOP("_top"); // 在顶级窗口打开
}
```

View File

@@ -0,0 +1,21 @@
```json title="MenuVo"
{
"metadata": {
"name": "string", // 唯一标识
"labels": {
"additionalProp1": "string"
},
"annotations": {
"additionalProp1": "string"
},
"creationTimestamp": "2022-11-20T14:44:58.984Z", // 创建时间
},
"spec": {
"displayName": "string", // 显示名称
"menuItems": [ // 菜单的菜单项名称集合,即 MenuItem 的 metadata.name 的集合
"string"
]
},
"menuItems": "List<#MenuItemVo>" // 菜单项的集合
}
```

View File

@@ -0,0 +1,66 @@
```json title="PostVo"
{
"metadata": {
"name": "string", // 唯一标识
"labels": {
"additionalProp1": "string"
},
"annotations": {
"additionalProp1": "string"
},
"creationTimestamp": "2022-11-20T12:45:43.888Z", // 创建时间
},
"spec": {
"title": "string", // 标题
"slug": "string", // 别名,通常用于生成 status.permalink
"releaseSnapshot": "string",
"headSnapshot": "string",
"baseSnapshot": "string",
"owner": "string", // 创建者名称,即 ContributorVo 的 metadata.name非显示名称
"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": [ // 分类的名称集合,即 Category 的 metadata.name 的集合
"string"
],
"tags": [ // 标签的名称集合,即 Tag 的 metadata.name 的集合
"string"
],
"htmlMetas": [
{
"additionalProp1": "string"
}
]
},
"status": {
"permalink": "string", // 固定链接
"excerpt": "string", // 最终生成的摘要
"inProgress": true,
"lastModifyTime": "2022-11-20T13:06:38.505Z", // 最后修改时间
"commentsCount": 0, // 评论数
"contributors": [ // 贡献者名称Contributor 的 metadata.name 的集合
"string"
]
},
"categories": "List<#CategoryVo>", // 分类的集合
"tags": "List<#TagVo>", // 标签的集合
"contributors": "List<#ContributorVo>", // 贡献者的集合
"owner": "#ContributorVo", // 创建者
"stats": {
"visit": 0, // 访问数量
"upvote": 0, // 点赞数量
"comment": 0 // 评论数量
},
"content": "#ContentVo" // 内容
}
```

View File

@@ -0,0 +1,42 @@
```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", // 评论者 UserAgent 信息
"ipAddress": "string", // 评论者 IP 地址
"priority": 0, // 排序字段
"top": false, // 是否置顶
"allowNotification": true, // 是否允许通知
"approved": false,
"hidden": false,
"commentName": "string", // 被回复的评论名称,即 Comment 的 metadata.name
"quoteReply": "string" // 被回复的回复名称,即 Reply 的 metadata.name
},
"owner": { // 创建者信息
"kind": "string",
"name": "string",
"displayName": "string",
"avatar": "string",
"email": "string"
}
}
```

View File

@@ -0,0 +1,58 @@
```json title="SinglePageVo"
{
"metadata": {
"name": "string", // 唯一标识
"labels": {
"additionalProp1": "string"
},
"annotations": {
"additionalProp1": "string"
},
"creationTimestamp": "2022-11-20T14:29:44.601Z", // 创建时间
},
"spec": {
"title": "string", // 标题
"slug": "string", // 别名,通常用于生成 status.permalink
"releaseSnapshot": "string",
"headSnapshot": "string",
"baseSnapshot": "string",
"owner": "string", // 创建者名称,即 ContributorVo 的 metadata.name非显示名称
"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" // 摘要内容
},
"htmlMetas": [
{
"additionalProp1": "string"
}
]
},
"status": {
"permalink": "string", // 固定链接
"excerpt": "string", // 最终生成的摘要
"inProgress": true,
"lastModifyTime": "2022-11-20T13:06:38.505Z", // 最后修改时间
"commentsCount": 0, // 评论数
"contributors": [ // 贡献者名称Contributor 的 metadata.name 的集合
"string"
]
},
"stats": {
"visit": 0, // 访问数量
"upvote": 0, // 点赞数量
"comment": 0 // 评论数量
},
"contributors": "List<#ContributorVo>", // 贡献者的集合
"owner": "#ContributorVo", // 创建者
"content": "#ContentVo" // 内容
}
```

View File

@@ -0,0 +1,26 @@
```json title="SiteSettingVo"
{
"title": "string", // 站点标题
"subtitle": "string", // 站点副标题
"url": "string", // 站点的外部访问链接
"logo": "string", // Logo 地址
"favicon": "string", // Favicon 地址
"allowRegistration": false, // 是否允许注册
"post": { // 文章相关设置
"postPageSize": 10, // 首页默认分页大小
"archivePageSize": 10, // 归档页默认分页大小
"categoryPageSize": 10, // 分类归档页默认分页大小
"tagPageSize": 10 // 标签归档页默认分页大小
},
"seo": { // SEO 相关设置
"blockSpiders": false, // 禁止搜索引擎抓取
"keywords": "string", // 站点全局关键词一般不需要主动使用Halo 会自动插入到 head 标签中
"description": "string" // 站点全局描述一般不需要主动使用Halo 会自动插入到 head 标签中
},
"comment": { // 评论相关设置
"enable": true, // 是否开启评论
"systemUserOnly": false, // 是否只允许登录用户评论
"requireReviewForNew": false // 是否需要审核新评论
}
}
```

View File

@@ -0,0 +1,26 @@
```json title="TagVo"
{
"metadata": {
"name": "string", // 唯一标识
"labels": {
"additionalProp1": "string"
},
"annotations": {
"additionalProp1": "string"
},
"creationTimestamp": "2022-11-20T13:06:38.512Z", // 创建时间
},
"spec": {
"displayName": "string", // 显示名称
"slug": "string", // 别名,通常用于生成 status.permalink
"color": "#F9fEB1", // 背景颜色
"cover": "string" // 封面图
},
"status": {
"permalink": "string", // 固定链接
"visiblePostCount": 0, // 已发布文章数
"postCount": 0 // 文章数
},
"postCount": 0
}
```

View File

@@ -0,0 +1,36 @@
```json title="ThemeVo"
{
"metadata": {
"name": "string", // 唯一标识
"labels": {
"additionalProp1": "string"
},
"annotations": {
"additionalProp1": "string"
},
"creationTimestamp": "2022-11-20T14:44:58.984Z" // 创建时间
},
"spec": {
"displayName": "string", // 显示名称
"author": { // 作者相关信息
"name": "string", // 作者名称
"website": "string" // 作者网站
},
"description": "string", // 主题描述
"logo": "string", // 主题 Logo
"website": "string", // 主题网站
"repo": "string", // 主题仓库地址
"version": "string", // 主题版本
"requires": "string", // 主题依赖 Halo 版本的设置
"settingName": "string", // 主题设置表单名称
"configMapName": "string", // 主题配置名称
"customTemplates": {} // 主题自定义模板设置
},
"config": {} // 主题配置
}
```
其中:
1. `customTemplates`:一般不会在模板引擎中使用,使用文档请参考:[模板路由#自定义模板](../template-route-mapping.md#custom-templates)
2. `config`:主题配置,使用文档请参考:[设置选项](../settings.md)

View File

@@ -0,0 +1,28 @@
```json title="UserVo"
{
"metadata": {
"name": "string", // 唯一标识
"labels": {
"additionalProp1": "string"
},
"annotations": {
"additionalProp1": "string"
},
"creationTimestamp": "2022-11-20T13:06:38.512Z" // 创建时间
},
"spec": {
"displayName": "string", // 显示名称
"avatar": "string", // 头像链接
"email": "string", // 邮箱地址
"phone": "string", // 电话号码
"bio": 0, // 描述
"registeredAt": "2022-11-20T13:06:38.512Z", // 注册时间
"twoFactorAuthEnabled": false, // 是否启用二次验证
"disabled": false // 是否禁用
},
"status": {
"lastLoginAt": "2022-11-20T13:06:38.512Z", // 最后登录时间
"permalink": "string" // 作者的文章归档页面链接
}
}
```