diff --git a/.vscode/settings.json b/.vscode/settings.json
index e261552..f0d31a0 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,5 +1,8 @@
{
"[markdown]": {
"editor.defaultFormatter": "DavidAnson.vscode-markdownlint"
- }
+ },
+ "i18n-ally.localesPaths": [
+ "i18n"
+ ]
}
diff --git a/docs/getting-started/install/docker-compose.md b/docs/getting-started/install/docker-compose.md
index fd16b2a..93cfe74 100644
--- a/docs/getting-started/install/docker-compose.md
+++ b/docs/getting-started/install/docker-compose.md
@@ -20,13 +20,13 @@ import DockerArgs from "./slots/docker-args.md"
## 创建容器组
-可用的 Halo 2.3.0 的 Docker 镜像:
+可用的 Halo 2.4.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 并未更新 Docker 的 latest 标签镜像,主要因为 Halo 2 不兼容 1.x 版本,防止使用者误操作。我们推荐使用固定版本的标签,比如 `halohub/halo:2.3.0`。
+目前 Halo 2 并未更新 Docker 的 latest 标签镜像,主要因为 Halo 2 不兼容 1.x 版本,防止使用者误操作。我们推荐使用固定版本的标签,比如 `halohub/halo:2.4.0`。
:::
1. 在系统任意位置创建一个文件夹,此文档以 `~/halo` 为例。
@@ -54,7 +54,7 @@ import DockerArgs from "./slots/docker-args.md"
services:
halo:
- image: halohub/halo:2.3.0
+ image: halohub/halo:2.4.0
container_name: halo
restart: on-failure:3
depends_on:
@@ -116,7 +116,7 @@ import DockerArgs from "./slots/docker-args.md"
services:
halo:
- image: halohub/halo:2.3.0
+ image: halohub/halo:2.4.0
container_name: halo
restart: on-failure:3
depends_on:
@@ -184,7 +184,7 @@ import DockerArgs from "./slots/docker-args.md"
services:
halo:
- image: halohub/halo:2.3.0
+ image: halohub/halo:2.4.0
container_name: halo
restart: on-failure:3
volumes:
@@ -251,7 +251,7 @@ import DockerArgs from "./slots/docker-args.md"
```yaml {3}
services:
halo:
- image: halohub/halo:2.3.0
+ image: halohub/halo:2.4.0
container_name: halo
```
@@ -290,7 +290,7 @@ networks:
services:
halo:
- image: halohub/halo:2.3.0
+ image: halohub/halo:2.4.0
container_name: halo
restart: on-failure:3
volumes:
diff --git a/docs/getting-started/install/docker.md b/docs/getting-started/install/docker.md
index 8d88527..cd928b6 100644
--- a/docs/getting-started/install/docker.md
+++ b/docs/getting-started/install/docker.md
@@ -25,13 +25,13 @@ import DockerArgs from "./slots/docker-args.md"
## 使用 Docker 镜像
-可用的 Halo 2.3.0 的 Docker 镜像:
+可用的 Halo 2.4.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 并未更新 Docker 的 latest 标签镜像,主要因为 Halo 2 不兼容 1.x 版本,防止使用者误操作。我们推荐使用固定版本的标签,比如 `halohub/halo:2.3.0`。
+目前 Halo 2 并未更新 Docker 的 latest 标签镜像,主要因为 Halo 2 不兼容 1.x 版本,防止使用者误操作。我们推荐使用固定版本的标签,比如 `halohub/halo:2.4.0`。
:::
1. 创建容器
@@ -42,7 +42,7 @@ import DockerArgs from "./slots/docker-args.md"
--name halo \
-p 8090:8090 \
-v ~/.halo2:/root/.halo2 \
- halohub/halo:2.3.0 \
+ halohub/halo:2.4.0 \
--halo.external-url=http://localhost:8090/ \
--halo.security.initializer.superadminusername=admin \
--halo.security.initializer.superadminpassword=P@88w0rd
@@ -73,7 +73,7 @@ import DockerArgs from "./slots/docker-args.md"
1. 拉取新版本镜像
```bash
- docker pull halohub/halo:2.3.0
+ docker pull halohub/halo:2.4.0
```
2. 停止运行中的容器
@@ -101,7 +101,7 @@ import DockerArgs from "./slots/docker-args.md"
--name halo \
-p 8090:8090 \
-v ~/.halo2:/root/.halo2 \
- halohub/halo:2.3.0 \
+ halohub/halo:2.4.0 \
--halo.external-url=http://localhost:8090/ \
--halo.security.initializer.superadminusername=admin \
--halo.security.initializer.superadminpassword=P@88w0rd
diff --git a/docs/getting-started/install/other/traefik.md b/docs/getting-started/install/other/traefik.md
index 85f3f80..aec2678 100644
--- a/docs/getting-started/install/other/traefik.md
+++ b/docs/getting-started/install/other/traefik.md
@@ -96,7 +96,7 @@ networks:
services:
halo:
- image: halohub/halo:2.3.0
+ image: halohub/halo:2.4.0
container_name: halo
restart: on-failure:3
volumes:
diff --git a/docs/intro.md b/docs/intro.md
index 7306cba..16b14b3 100644
--- a/docs/intro.md
+++ b/docs/intro.md
@@ -13,7 +13,7 @@ slug: /
-Halo [ˈheɪloʊ],好用又强大的开源建站工具。
+Halo [ˈheɪloʊ],强大易用的开源建站工具。
@@ -39,7 +39,7 @@ docker run \
--name halo \
-p 8090:8090 \
-v ~/.halo2:/root/.halo2 \
- halohub/halo:2.3 \
+ halohub/halo:2.4 \
--halo.external-url=http://localhost:8090/ \
--halo.security.initializer.superadminusername=admin \
--halo.security.initializer.superadminpassword=P@88w0rd
diff --git a/docs/user-guide/faq.md b/docs/user-guide/faq.md
index 66f045b..83211da 100644
--- a/docs/user-guide/faq.md
+++ b/docs/user-guide/faq.md
@@ -113,7 +113,7 @@ server {
-v ~/.halo2.1:/root/.halo2 \
-e HALO_EXTERNAL_URL=http://localhost:8090/ \
-e HALO_SECURITY_INITIALIZER_SUPERADMINPASSWORD=P@88w0rd \
- halohub/halo:2.3.0
+ halohub/halo:2.4.0
# 第二个 Halo 容器
docker run \
@@ -123,7 +123,7 @@ server {
-v ~/.halo2.2:/root/.halo2 \
-e HALO_EXTERNAL_URL=http://localhost:8090/ \
-e HALO_SECURITY_INITIALIZER_SUPERADMINPASSWORD=P@88w0rd \
- halohub/halo:2.3.0
+ halohub/halo:2.4.0
```
更多 Docker 相关的教程请参考:[使用 Docker 部署 Halo](../getting-started/install/docker.md)
diff --git a/docusaurus.config.js b/docusaurus.config.js
index 27f680e..2f96897 100644
--- a/docusaurus.config.js
+++ b/docusaurus.config.js
@@ -34,11 +34,11 @@ const config = {
showLastUpdateAuthor: true,
remarkPlugins: [math, mermaid],
rehypePlugins: [katex],
- lastVersion: "2.3",
+ lastVersion: "2.4",
versions: {
current: {
- label: "2.4.0-SNAPSHOT",
- path: "2.4.0-SNAPSHOT",
+ label: "2.5.0-SNAPSHOT",
+ path: "2.5.0-SNAPSHOT",
},
},
},
@@ -272,12 +272,13 @@ const config = {
if (existingPath.startsWith("/1.4/")) {
return [existingPath.replace("/1.4/", "/1.4.17/")];
}
- if (existingPath.startsWith("/2.4.0-SNAPSHOT/")) {
+ if (existingPath.startsWith("/2.5.0-SNAPSHOT/")) {
return [
- existingPath.replace("/2.4.0-SNAPSHOT/", "/2.0.0-SNAPSHOT/"),
- existingPath.replace("/2.4.0-SNAPSHOT/", "/2.1.0-SNAPSHOT/"),
- existingPath.replace("/2.4.0-SNAPSHOT/", "/2.2.0-SNAPSHOT/"),
- existingPath.replace("/2.4.0-SNAPSHOT/", "/2.3.0-SNAPSHOT/"),
+ existingPath.replace("/2.5.0-SNAPSHOT/", "/2.0.0-SNAPSHOT/"),
+ existingPath.replace("/2.5.0-SNAPSHOT/", "/2.1.0-SNAPSHOT/"),
+ existingPath.replace("/2.5.0-SNAPSHOT/", "/2.2.0-SNAPSHOT/"),
+ existingPath.replace("/2.5.0-SNAPSHOT/", "/2.3.0-SNAPSHOT/"),
+ existingPath.replace("/2.5.0-SNAPSHOT/", "/2.4.0-SNAPSHOT/"),
];
}
return undefined;
diff --git a/i18n/zh-Hans/code.json b/i18n/zh-Hans/code.json
index dbcc06d..751ec81 100644
--- a/i18n/zh-Hans/code.json
+++ b/i18n/zh-Hans/code.json
@@ -25,6 +25,18 @@
"message": "请联系原始链接来源网站的所有者,并告知他们链接已损坏。",
"description": "The 2nd paragraph of the 404 page"
},
+ "theme.blog.archive.title": {
+ "message": "历史博文",
+ "description": "The page & hero title of the blog archive page"
+ },
+ "theme.blog.archive.description": {
+ "message": "历史博文",
+ "description": "The page & hero description of the blog archive page"
+ },
+ "theme.BackToTopButton.buttonAriaLabel": {
+ "message": "回到顶部",
+ "description": "The ARIA label for the back to top button"
+ },
"theme.admonition.note": {
"message": "备注",
"description": "The default label used for the Note admonition (:::note)"
@@ -45,10 +57,6 @@
"message": "警告",
"description": "The default label used for the Caution admonition (:::caution)"
},
- "theme.BackToTopButton.buttonAriaLabel": {
- "message": "回到顶部",
- "description": "The ARIA label for the back to top button"
- },
"theme.blog.paginator.navAriaLabel": {
"message": "博文列表分页导航",
"description": "The ARIA label for the blog pagination"
@@ -61,14 +69,6 @@
"message": "较旧的博文",
"description": "The label used to navigate to the older blog posts page (next page)"
},
- "theme.blog.archive.title": {
- "message": "历史博文",
- "description": "The page & hero title of the blog archive page"
- },
- "theme.blog.archive.description": {
- "message": "历史博文",
- "description": "The page & hero description of the blog archive page"
- },
"theme.blog.post.paginator.navAriaLabel": {
"message": "博文分页导航",
"description": "The ARIA label for the blog posts pagination"
@@ -105,14 +105,14 @@
"message": "浅色模式",
"description": "The name for the light color mode"
},
- "theme.docs.DocCard.categoryDescription": {
- "message": "{count} 个项目",
- "description": "The default description for a category card in the generated index about how many items this category includes"
- },
"theme.docs.breadcrumbs.navAriaLabel": {
"message": "页面路径",
"description": "The ARIA label for the breadcrumbs"
},
+ "theme.docs.DocCard.categoryDescription": {
+ "message": "{count} 个项目",
+ "description": "The default description for a category card in the generated index about how many items this category includes"
+ },
"theme.docs.paginator.navAriaLabel": {
"message": "文档分页导航",
"description": "The ARIA label for the docs pagination"
@@ -125,13 +125,8 @@
"message": "下一页",
"description": "The label used to navigate to the next doc"
},
- "theme.docs.tagDocListPageTitle.nDocsTagged": {
- "message": "{count} 篇文档带有标签",
- "description": "Pluralized label for \"{count} docs tagged\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)"
- },
- "theme.docs.tagDocListPageTitle": {
- "message": "{nDocsTagged}「{tagName}」",
- "description": "The title of the page for a docs tag"
+ "theme.docs.versionBadge.label": {
+ "message": "版本:{versionLabel}"
},
"theme.docs.versions.unreleasedVersionLabel": {
"message": "此为 {siteTitle} {versionLabel} 版尚未发行的文档。",
@@ -153,9 +148,6 @@
"message": "编辑此页",
"description": "The link label to edit the current page"
},
- "theme.docs.versionBadge.label": {
- "message": "版本:{versionLabel}"
- },
"theme.common.headingLinkTitle": {
"message": "{heading}的直接链接",
"description": "Title for link to heading"
@@ -172,6 +164,14 @@
"message": "最后{byUser}{atDate}更新",
"description": "The sentence used to display when a page has been last updated, and by who"
},
+ "theme.docs.tagDocListPageTitle.nDocsTagged": {
+ "message": "{count} 篇文档带有标签",
+ "description": "Pluralized label for \"{count} docs tagged\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)"
+ },
+ "theme.docs.tagDocListPageTitle": {
+ "message": "{nDocsTagged}「{tagName}」",
+ "description": "The title of the page for a docs tag"
+ },
"theme.navbar.mobileVersionsDropdown.label": {
"message": "选择版本",
"description": "The label for the navbar versions dropdown on mobile view"
@@ -220,10 +220,6 @@
"message": "本页总览",
"description": "The label used by the button on the collapsible TOC component"
},
- "theme.blog.post.readingTime.plurals": {
- "message": "阅读需 {readingTime} 分钟",
- "description": "Pluralized label for \"{readingTime} min read\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)"
- },
"theme.blog.post.readMore": {
"message": "阅读更多",
"description": "The label used in blog post item excerpts to link to full blog posts"
@@ -232,9 +228,9 @@
"message": "阅读 {title} 的全文",
"description": "The ARIA label for the link to full blog posts from excerpts"
},
- "theme.docs.breadcrumbs.home": {
- "message": "主页面",
- "description": "The ARIA label for the home page in the breadcrumbs"
+ "theme.blog.post.readingTime.plurals": {
+ "message": "阅读需 {readingTime} 分钟",
+ "description": "Pluralized label for \"{readingTime} min read\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)"
},
"theme.docs.sidebar.collapseButtonTitle": {
"message": "收起侧边栏",
@@ -248,6 +244,10 @@
"message": "Docs sidebar",
"description": "The ARIA label for the sidebar navigation"
},
+ "theme.docs.breadcrumbs.home": {
+ "message": "主页面",
+ "description": "The ARIA label for the home page in the breadcrumbs"
+ },
"theme.docs.sidebar.closeSidebarButtonAriaLabel": {
"message": "关闭导航栏",
"description": "The ARIA label for close button of mobile sidebar"
@@ -268,9 +268,6 @@
"message": "展开侧边栏",
"description": "The ARIA label and title attribute for expand button of doc sidebar"
},
- "theme.SearchBar.seeAll": {
- "message": "查看全部 {count} 个结果"
- },
"theme.SearchPage.documentsFound.plurals": {
"message": "找到 {count} 份文件",
"description": "Pluralized label for \"{count} documents found\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)"
@@ -303,6 +300,9 @@
"message": "正在获取新的搜索结果...",
"description": "The paragraph for fetching new search results"
},
+ "theme.SearchBar.seeAll": {
+ "message": "查看全部 {count} 个结果"
+ },
"theme.SearchBar.label": {
"message": "搜索",
"description": "The ARIA label and placeholder for search button"
diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current.json b/i18n/zh-Hans/docusaurus-plugin-content-docs/current.json
index 1660e14..2eee943 100644
--- a/i18n/zh-Hans/docusaurus-plugin-content-docs/current.json
+++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current.json
@@ -1,6 +1,6 @@
{
"version.label": {
- "message": "2.4.0-SNAPSHOT",
+ "message": "2.5.0-SNAPSHOT",
"description": "The label for version current"
},
"sidebar.tutorialSidebar.category.入门": {
diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/version-2.4.json b/i18n/zh-Hans/docusaurus-plugin-content-docs/version-2.4.json
new file mode 100644
index 0000000..4f4486c
--- /dev/null
+++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/version-2.4.json
@@ -0,0 +1,62 @@
+{
+ "version.label": {
+ "message": "2.4",
+ "description": "The label for version 2.4"
+ },
+ "sidebar.tutorialSidebar.category.入门": {
+ "message": "入门",
+ "description": "The label for category 入门 in sidebar tutorialSidebar"
+ },
+ "sidebar.tutorialSidebar.category.安装指南": {
+ "message": "安装指南",
+ "description": "The label for category 安装指南 in sidebar tutorialSidebar"
+ },
+ "sidebar.tutorialSidebar.category.其他指南": {
+ "message": "其他指南",
+ "description": "The label for category 其他指南 in sidebar tutorialSidebar"
+ },
+ "sidebar.tutorialSidebar.category.用户指南": {
+ "message": "用户指南",
+ "description": "The label for category 用户指南 in sidebar tutorialSidebar"
+ },
+ "sidebar.tutorialSidebar.category.开发者指南": {
+ "message": "开发者指南",
+ "description": "The label for category 开发者指南 in sidebar tutorialSidebar"
+ },
+ "sidebar.tutorialSidebar.category.系统开发": {
+ "message": "系统开发",
+ "description": "The label for category 系统开发 in sidebar tutorialSidebar"
+ },
+ "sidebar.tutorialSidebar.category.插件开发": {
+ "message": "插件开发",
+ "description": "The label for category 插件开发 in sidebar tutorialSidebar"
+ },
+ "sidebar.tutorialSidebar.category.基础": {
+ "message": "基础",
+ "description": "The label for category 基础 in sidebar tutorialSidebar"
+ },
+ "sidebar.tutorialSidebar.category.示例": {
+ "message": "示例",
+ "description": "The label for category 示例 in sidebar tutorialSidebar"
+ },
+ "sidebar.tutorialSidebar.category.API 参考": {
+ "message": "API 参考",
+ "description": "The label for category API 参考 in sidebar tutorialSidebar"
+ },
+ "sidebar.tutorialSidebar.category.主题开发": {
+ "message": "主题开发",
+ "description": "The label for category 主题开发 in sidebar tutorialSidebar"
+ },
+ "sidebar.tutorialSidebar.category.模板变量": {
+ "message": "模板变量",
+ "description": "The label for category 模板变量 in sidebar tutorialSidebar"
+ },
+ "sidebar.tutorialSidebar.category.Finder API": {
+ "message": "Finder API",
+ "description": "The label for category Finder API in sidebar tutorialSidebar"
+ },
+ "sidebar.tutorialSidebar.category.参与贡献": {
+ "message": "参与贡献",
+ "description": "The label for category 参与贡献 in sidebar tutorialSidebar"
+ }
+}
diff --git a/versioned_docs/version-2.4/about.md b/versioned_docs/version-2.4/about.md
new file mode 100644
index 0000000..d0eb731
--- /dev/null
+++ b/versioned_docs/version-2.4/about.md
@@ -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` 等待我们合并即可。
diff --git a/versioned_docs/version-2.4/contribution/issue.md b/versioned_docs/version-2.4/contribution/issue.md
new file mode 100644
index 0000000..6ab45dd
--- /dev/null
+++ b/versioned_docs/version-2.4/contribution/issue.md
@@ -0,0 +1,28 @@
+---
+title: 问题反馈
+description: 问题反馈渠道及指南
+---
+
+:::info
+如果您在使用过程中遇到了什么问题,您可以通过下面的方式反馈,但请尽量按照要求提出反馈。
+:::
+
+## GitHub 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 官方社区
+
+链接:
+
+此平台主要目的用于与其他 Halo 用户进行交流。但如果您对 GitHub 不是很熟悉或者没有账号,您也可以在此平台进行反馈。
diff --git a/versioned_docs/version-2.4/contribution/pr.md b/versioned_docs/version-2.4/contribution/pr.md
new file mode 100644
index 0000000..10d26ff
--- /dev/null
+++ b/versioned_docs/version-2.4/contribution/pr.md
@@ -0,0 +1,110 @@
+---
+title: 代码贡献
+description: 代码贡献指南
+---
+
+欢迎关注并有想法参与 Halo 的开发,以下是关于如何参与到 Halo 项目的指南,仅供参考。
+
+## 发现 Issue
+
+所有的代码尽可能都有依据(Issue),不是凭空产生。
+
+### 寻找一个 Good First Issue
+
+> 这个步骤非常适合首次贡献者。
+
+在 [halo-dev](https://github.com/halo-dev) 和 [halo-sigs](https://github.com/halo-sigs) 组织下,有非常多的仓库。每个仓库下都有可能包含一些“首次贡献者”友好的 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 main
+ ```
+
+4. 创建新的开发分支
+
+ 我们需要从主仓库的主分支创建一个新的开发分支。
+
+ ```bash
+ git checkout upstream/main
+ 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/main
+ git merge upstream/main
+ git push origin {BRANCH_NAME}
+ ```
+
+## Pull Request
+
+进入此阶段说明已经完成了代码的编写,测试和自测,并且准备好接受 Code Review。
+
+### 创建 Pull Request
+
+回到自己的仓库页面,选择 `New pull request` 按钮,创建 `Pull request` 到原仓库的 `main` 分支。
+然后等待我们 Review 即可,如有 `Change Request`,再本地修改之后再次提交即可。
+
+提交 Pull Request 的注意事项:
+
+- 提交 Pull Request 请充分自测。
+- 每个 Pull Request 尽量只解决一个 Issue,特殊情况除外。
+- 应尽可能多的添加单元测试,其他测试(集成测试和 E2E 测试)可看情况添加。
+- 不论需要解决的 Issue 发生在哪个版本,提交 Pull Request 的时候,请将主仓库的主分支设置为 `main`。例如:即使某个 Bug 于 Halo 2.0.x 被发现,但是提交 Pull Request 仍只针对
+ `main` 分支,等待 Pull Request 合并之后,我们会通过 `/cherrypick release-2.0` 或者 `/cherry-pick release-2.1` 指令将此 Pull Request
+ 的修改应用到 `release-2.0` 和 `release-2.1` 分支上。
+
+### 更新 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)
+,请确保所有代码格式化之后再提交。
diff --git a/versioned_docs/version-2.4/developer-guide/annotations-form.md b/versioned_docs/version-2.4/developer-guide/annotations-form.md
new file mode 100644
index 0000000..15ed076
--- /dev/null
+++ b/versioned_docs/version-2.4/developer-guide/annotations-form.md
@@ -0,0 +1,89 @@
+---
+title: 元数据表单定义
+---
+
+在 Halo 2.0,所有的模型都包含了 `metadata.annotations` 字段,用于存储元数据信息。元数据信息可以用于存储一些自定义的信息,可以等同于扩展字段。此文档主要介绍如何在 Halo 中为具体的模型定义元数据编辑表单,至于如何在插件或者主题模板中使用,请看插件或者主题的文档。
+
+定义元数据编辑表单同样使用 `FormKit Schema`,但和主题或插件的定义方式稍有不同,其中输入组件类型可参考 [表单定义](./form-schema.md)。
+
+:::info 提示
+因为 `metadata.annotations` 是一个键值都为字符串类型的对象,所以表单项的值必须为字符串类型。这就意味着,FormKit 的 `number`、`group`、`repeater` 等类型的输入组件都不能使用。
+:::
+
+## AnnotationSetting 资源定义方式
+
+```yaml title="annotation-setting.yaml"
+apiVersion: v1alpha1
+kind: AnnotationSetting
+metadata:
+ generateName: annotation-setting-
+spec:
+ targetRef:
+ group: content.halo.run
+ kind: Post
+ formSchema:
+ - $formkit: "text"
+ name: "download"
+ label: "下载地址"
+ - $formkit: "text"
+ name: "version"
+ label: "版本"
+```
+
+以上定义为文章模型添加了两个元数据字段,分别为 `download` 和 `version`,分别对应了下载地址和版本号,最终效果:
+
+![Annotation Setting Preview](/img/annotation-setting/annotation-setting-preview.png)
+
+字段说明:
+
+1. `metadata.generateName`:可以不做修改,最终会以这个值当做前缀生成一个唯一的名称。
+2. `spec.targetRef`:模型的关联,即为哪个模型添加元数据表单,目前支持的模型可查看下方的列表。
+3. `spec.formSchema`:表单的定义,使用 FormKit Schema 来定义。虽然我们使用的 YAML,但与 FormKit Schema 完全一致。
+
+targetRef 支持列表:
+
+| 对应模型 | group | kind |
+| ---------- | ---------------- | ---------- |
+| 文章 | content.halo.run | Post |
+| 自定义页面 | content.halo.run | SinglePage |
+| 文章分类 | content.halo.run | Category |
+| 文章标签 | content.halo.run | Tag |
+| 菜单项 | `""` | MenuItem |
+| 用户 | `""` | User |
+
+## 为多个模型定义表单
+
+考虑到某些情况可能会同时为多个模型添加元数据表单,推荐在一个 `yaml` 文件中使用 `---` 来分割多个资源定义,如下:
+
+```yaml title="annotation-setting.yaml"
+apiVersion: v1alpha1
+kind: AnnotationSetting
+metadata:
+ generateName: annotation-setting-
+spec:
+ targetRef:
+ group: content.halo.run
+ kind: Post
+ formSchema:
+ - $formkit: "text"
+ name: "download"
+ label: "下载地址"
+ - $formkit: "text"
+ name: "version"
+ label: "版本"
+
+---
+
+apiVersion: v1alpha1
+kind: AnnotationSetting
+metadata:
+ generateName: annotation-setting-
+spec:
+ targetRef:
+ group: ""
+ kind: MenuItem
+ formSchema:
+ - $formkit: "text"
+ name: "icon"
+ label: "图标"
+```
diff --git a/versioned_docs/version-2.4/developer-guide/core/build.md b/versioned_docs/version-2.4/developer-guide/core/build.md
new file mode 100644
index 0000000..7120eba
--- /dev/null
+++ b/versioned_docs/version-2.4/developer-guide/core/build.md
@@ -0,0 +1,99 @@
+---
+title: 构建
+description: 构建为可执行 JAR 和 Docker 镜像的文档
+---
+
+:::info
+在此之前,我们推荐你先阅读[《准备工作》](./prepare),检查本地环境是否满足要求。
+:::
+
+一般情况下,为了保证版本一致性和可维护性,我们并不推荐自行构建和二次开发。但考虑到我们目前仅提供 Docker 镜像的发行版本,不再提供可执行 JAR 的发行版本,因此我们提供了构建的文档,以供用户自行构建。
+
+## 克隆项目
+
+如果你已经 fork 了相关仓库,请将以下命令中的 halo-dev 替换为你的 GitHub 用户名。
+
+```bash
+git clone https://github.com/halo-dev/halo
+
+# 或者使用 ssh 的方式 clone(推荐)
+
+git clone git@github.com:halo-dev/halo.git
+
+# 切换到最新的 tag
+
+cd halo
+
+git checkout v2.0.0
+```
+
+:::tip
+请务必按照以上要求切换到最新的 tag,而不是直接使用 main 分支构建,main 分支是我们的开发分支。此文档以 `2.0.0` 为例,查看最新的 tag 可使用 `git tag --column` 查看。
+:::
+
+:::warning
+从 2.4.0 开始,Console 项目已经合并到 Halo 主项目,所以不再需要单独克隆 Console 的项目仓库。
+
+详情可查阅:
+:::
+
+## 构建 Console
+
+```bash
+cd path/to/halo
+```
+
+Linux / macOS 平台:
+
+```bash
+make -C console build
+```
+
+Windows 平台:
+
+```bash
+cd console
+
+pnpm install
+
+pnpm build:packages
+
+pnpm build
+```
+
+## 构建 Fat Jar
+
+构建之前需要修改 `gradle.properties` 中的 `version` 为当前 tag 的版本号,如:`version=2.0.0`
+
+```bash
+cd path/to/halo
+```
+
+```bash
+# Windows
+./gradlew.bat clean build -x check -x jar
+
+# macOS / Linux
+./gradlew clean build -x check -x jar
+```
+
+构建完成之后,在 halo 项目下产生的 `build/libs/halo-2.0.0.jar` 即为构建完成的文件。
+
+## 构建 Docker 镜像
+
+在进行之前,请确保已经完成上述操作,最终需要确认在 halo 项目的 `build/libs/` 目录已经包含了 `halo-2.0.0.jar` 文件。
+
+```bash
+cd path/to/halo
+```
+
+```bash
+docker build -t halo-dev/halo:2.0.0 .
+```
+
+```bash
+# 插件构建完成的版本
+docker images | grep halo
+```
+
+最终部署文档可参考:[使用 Docker Compose 部署](./docker-compose)
diff --git a/versioned_docs/version-2.4/developer-guide/core/code-style.md b/versioned_docs/version-2.4/developer-guide/core/code-style.md
new file mode 100644
index 0000000..c4298a1
--- /dev/null
+++ b/versioned_docs/version-2.4/developer-guide/core/code-style.md
@@ -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` 配置文件,点击确定即可。
+
+至此,有关代码风格检查工具和格式化配置已经完成。
diff --git a/versioned_docs/version-2.4/developer-guide/core/prepare.md b/versioned_docs/version-2.4/developer-guide/core/prepare.md
new file mode 100644
index 0000000..e0f578f
--- /dev/null
+++ b/versioned_docs/version-2.4/developer-guide/core/prepare.md
@@ -0,0 +1,25 @@
+---
+title: 准备工作
+description: 开发环境的准备工作
+---
+
+## 环境要求
+
+- [OpenJDK 17 LTS](https://github.com/openjdk/jdk)
+- [Node.js 18 LTS](https://nodejs.org)
+- [pnpm 7](https://pnpm.io/)
+- [IntelliJ IDEA](https://www.jetbrains.com/idea/)
+- [Git](https://git-scm.com/)
+- [Docker](https://www.docker.com/)(可选)
+
+## 名词解释
+
+### 工作目录
+
+指 Halo 所依赖的工作目录,在 Halo 运行的时候会在系统当前用户目录下产生一个 halo-next 的文件夹,绝对路径为 ~/halo-next。里面通常包含下列目录或文件:
+
+1. `db`:存放 H2 Database 的物理文件,如果你使用其他数据库,那么不会存在这个目录。
+2. `themes`:里面包含用户所安装的主题。
+2. `plugins`:里面包含用户所安装的插件。
+5. `attachments`:附件目录。
+4. `logs`:运行日志目录。
diff --git a/versioned_docs/version-2.4/developer-guide/core/run.md b/versioned_docs/version-2.4/developer-guide/core/run.md
new file mode 100644
index 0000000..0087639
--- /dev/null
+++ b/versioned_docs/version-2.4/developer-guide/core/run.md
@@ -0,0 +1,122 @@
+---
+title: 开发环境运行
+description: 开发环境运行的指南
+---
+
+:::info
+在此之前,我们推荐你先阅读[《准备工作》](./prepare),检查本地环境是否满足要求。
+:::
+
+## 项目结构说明
+
+目前如果需要完整的运行 Halo,总共需要三个部分:
+
+1. Halo 主项目([halo-dev/halo](https://github.com/halo-dev/halo))
+2. Console 控制台(托管在 Halo 主项目)
+3. 主题(Halo 主项目内已包含默认主题)
+
+:::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
+```
+
+:::warning
+从 2.4.0 开始,Console 项目已经合并到 Halo 主项目,所以不再需要单独克隆 Console 的项目仓库。
+
+详情可查阅:
+:::
+
+## 运行 Console
+
+```bash
+cd path/to/halo
+```
+
+Linux / macOS 平台:
+
+```bash
+make -C console dev
+```
+
+Windows 平台:
+
+```bash
+cd console
+
+pnpm install
+
+pnpm build:packages
+
+pnpm dev
+```
+
+最终控制台打印了如下信息即代表运行正常:
+
+```bash
+VITE v3.1.6 ready in 638 ms
+
+➜ Local: http://localhost:3000/console/
+```
+
+:::info 提示
+请不要直接使用 Console 的运行端口(3000)访问,会因为跨域问题导致无法正常登录,建议按照后续的步骤以 dev 的配置文件运行 Halo,在 dev 的配置文件中,我们默认代理了 Console 的访问地址,所以后续访问 Console 使用 `http://localhost:8090/console` 访问即可,代理的相关配置:
+
+```yaml
+halo:
+ console:
+ proxy:
+ endpoint: http://localhost:3000/
+ enabled: true
+```
+
+:::
+
+## 运行 Halo
+
+1. 在 IntelliJ IDEA 中打开 Halo 项目,等待 Gradle 初始化和依赖下载完成。
+
+2. 修改 IntelliJ IDEA 的运行配置
+ 1. macOS / Linux
+ 将 Active Profiles 改为 `dev`,如图所示:
+ ![IntelliJ IDEA Profiles](/img/developer-run/IntelliJ-IDEA-Profiles-MacOs.png)
+ 2. Windows
+ 将 Active Profiles 改为 `dev,win`,如图所示:
+ ![IntelliJ IDEA Profiles](/img/developer-run/IntelliJ-IDEA-Profiles-Win.png)
+
+3. 点击 IntelliJ IDEA 的运行按钮,等待项目启动完成。
+
+4. 或者使用 Gradle 运行
+
+ ```bash
+ # macOS / Linux
+ ./gradlew bootRun --args="--spring.profiles.active=dev"
+
+ # Windows
+ gradlew.bat bootRun --args="--spring.profiles.active=dev,win"
+ ```
+
+5. 最终访问 `http://localhost:8090/console` 即可进入控制台。访问 `http://localhost:8090` 即可进入站点首页。
+
+:::info
+开发环境下,超级管理员的默认用户名为 `admin`,默认密码为 `admin`。也可通过修改 `application-dev.yaml` 配置文件进行更改:
+
+```yaml
+halo:
+ security:
+ initializer:
+ super-admin-username: admin
+ super-admin-password: admin
+```
+
+:::
diff --git a/versioned_docs/version-2.4/developer-guide/core/structure.md b/versioned_docs/version-2.4/developer-guide/core/structure.md
new file mode 100644
index 0000000..cf76cd7
--- /dev/null
+++ b/versioned_docs/version-2.4/developer-guide/core/structure.md
@@ -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`。
+:::
diff --git a/versioned_docs/version-2.4/developer-guide/form-schema.md b/versioned_docs/version-2.4/developer-guide/form-schema.md
new file mode 100644
index 0000000..061798b
--- /dev/null
+++ b/versioned_docs/version-2.4/developer-guide/form-schema.md
@@ -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:
+ -
+ -
+- FormKit 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 效果:
+
+
+
+### 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: []
+```
diff --git a/versioned_docs/version-2.4/developer-guide/plugin/api-reference/extension-client.md b/versioned_docs/version-2.4/developer-guide/plugin/api-reference/extension-client.md
new file mode 100644
index 0000000..d7f88a2
--- /dev/null
+++ b/versioned_docs/version-2.4/developer-guide/plugin/api-reference/extension-client.md
@@ -0,0 +1,104 @@
+---
+title: 与自定义模型交互
+description: 了解如果通过代码的方式操作数据
+---
+
+Halo 提供了两个类用于与自定义模型数据交互 `ExtensionClient` 和 `ReactiveExtensionClient`。
+
+它们的本质就是操作数据库,区别在于 `ExtensionClient` 是阻塞式 API,而 `ReactiveExtensionClient` 是响应式 API,接口返回值只有两种 Mono 或 Flux,它们由 [reactor](https://projectreactor.io/) 提供。
+
+```java
+public interface ReactiveExtensionClient {
+
+ /**
+ * Lists Extensions by Extension type, filter and sorter.
+ *
+ * @param type is the class type of Extension.
+ * @param predicate filters the reEnqueue.
+ * @param comparator sorts the reEnqueue.
+ * @param is Extension type.
+ * @return all filtered and sorted Extensions.
+ */
+ Flux list(Class type, Predicate predicate,
+ Comparator comparator);
+
+ /**
+ * Lists Extensions by Extension type, filter, sorter and page info.
+ *
+ * @param type is the class type of Extension.
+ * @param predicate filters the reEnqueue.
+ * @param comparator sorts the reEnqueue.
+ * @param page is page number which starts from 0.
+ * @param size is page size.
+ * @param is Extension type.
+ * @return a list of Extensions.
+ */
+ Mono> list(Class type, Predicate predicate,
+ Comparator comparator, int page, int size);
+
+ /**
+ * Fetches Extension by its type and name.
+ *
+ * @param type is Extension type.
+ * @param name is Extension name.
+ * @param is Extension type.
+ * @return an optional Extension.
+ */
+ Mono fetch(Class type, String name);
+
+ Mono fetch(GroupVersionKind gvk, String name);
+
+ Mono get(Class type, String name);
+
+ /**
+ * Creates an Extension.
+ *
+ * @param extension is fresh Extension to be created. Please make sure the Extension name does
+ * not exist.
+ * @param is Extension type.
+ */
+ Mono create(E extension);
+
+ /**
+ * Updates an Extension.
+ *
+ * @param extension is an Extension to be updated. Please make sure the resource version is
+ * latest.
+ * @param is Extension type.
+ */
+ Mono update(E extension);
+
+ /**
+ * Deletes an Extension.
+ *
+ * @param extension is an Extension to be deleted. Please make sure the resource version is
+ * latest.
+ * @param is Extension type.
+ */
+ Mono delete(E extension);
+}
+```
+
+### 示例
+
+如果你想在插件中根据 name 参数查询获取到 Person 自定义模型的数据,则可以这样写:
+
+```java
+private final ReactiveExtensionClient client;
+
+Mono getPerson(String name) {
+ return client.fetch(Person.class, name);
+}
+```
+
+或者使用阻塞式 API:
+
+```java
+private final ExtensionClient client;
+
+Optional getPerson(String name) {
+ return client.fetch(Person.class, name);
+}
+```
+
+我们建议你更多的使用响应式的 `ReactiveExtensionClient` 去替代 `ExtensionClient`。
diff --git a/versioned_docs/version-2.4/developer-guide/plugin/api-reference/extension.md b/versioned_docs/version-2.4/developer-guide/plugin/api-reference/extension.md
new file mode 100644
index 0000000..8f06d0d
--- /dev/null
+++ b/versioned_docs/version-2.4/developer-guide/plugin/api-reference/extension.md
@@ -0,0 +1,127 @@
+---
+title: 自定义模型
+description: 了解什么是自定义模型及如何创建
+---
+
+Halo 自定义模型主要参考自 [Kubernetes CRD](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/) 。自定义模型遵循 [OpenAPI v3](https://spec.openapis.org/oas/v3.1.0)。设计目的在于为插件提供自定义数据支持。比如某插件需要存储自定义数据,同时也想读取和操作自定义数据。
+
+一个典型的自定义模型 `Java` 代码示例如下:
+
+```java
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import run.halo.app.extension.AbstractExtension;
+import run.halo.app.extension.GVK;
+import run.halo.app.extension.GroupKind;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@GVK(group = "my-plugin.halo.run",
+ version = "v1alpha1",
+ kind = "Person",
+ plural = "persons",
+ singular = "person")
+public class Person extends AbstractExtension {
+
+ @Schema(description = "The description on name field", maxLength = 100)
+ private String name;
+
+ @Schema(description = "The description on age field", maximum = "150", minimum = "0")
+ private Integer age;
+
+ @Schema(description = "The description on gender field")
+ private Gender gender;
+
+ private Person otherPerson;
+
+ public enum Gender {
+ MALE, FEMALE,
+ }
+}
+```
+
+要创建一个自定义模型需要三步:
+
+1. 创建一个类继承 `AbstractExtension`。
+2. 使用 `GVK` 注解。
+3. 在插件 `start()` 生命周期方法中注册自定义模型:
+
+```java
+@Autowired
+private SchemeManager schemeManager;
+
+@Override
+public void start() {
+ schemeManager.register(Person.class);
+}
+```
+
+有了自定义模型后可以通过在插件项目的 `src/main/resources/extensions` 目录下声明 `yaml` 文件来创建一个实例,此目录下的所有自定义模型 `yaml` 都会在插件启动时被创建:
+
+```yaml
+groupVersion: my-plugin.halo.run/v1alpha1
+kind: Person
+metadata:
+ name: fake-person
+name: halo
+age: 18
+gender: male
+```
+
+:::tip 释义
+
+- @GVK:此注解标识该类为一个自定义模型,同时必须继承 `AbstractExtension`。
+ - kind:表示自定义模型所表示的 REST 资源。
+ - group:表示一组公开的资源,通常采用域名形式,Halo 项目保留使用空组和任何以“*.halo.run”结尾的组名供其单独使用。
+ 选择群组名称时,我们建议选择你的群组或组织拥有的子域,例如“widget.mycompany.com”。
+ - version:API 的版本,它与 group 组合使用为 apiVersion=“GROUP/VERSION”,例如“api.halo.run/v1alpha1”。
+ - singular: 资源的单数名称,这允许客户端不透明地处理复数和单数,必须全部小写。通常为小写的“kind”。
+ - plural: 资源的复数名称,自定义资源在 `/apis///.../` 下提供,必须为全部小写。
+- @Schema:属性校验注解,会在创建/修改资源前对资源校验,参考 [schema-validator](https://www.openapi4j.org/schema-validator.html)。
+:::
+
+### 自定义模型 API
+
+定义好自定义模型并注册后,会根据 `GVK` 注解自动生成一组 `CRUD` API,规则为:
+`/apis////{extensionname}/`
+
+对于上述 Person 自定义模型将有以下 APIs:
+
+```text
+GET /apis/my-plugin.halo.run/v1alpha1/persons
+PUT /apis/my-plugin.halo.run/v1alpha1/persons/{name}
+POST /apis/my-plugin.halo.run/v1alpha1/persons
+DELETE /apis/my-plugin.halo.run/v1alpha1/persons/{name}
+```
+
+### 自定义 API
+
+在一些场景下,只有自动生成的 `CRUD` API 往往是不够用的,此时可以通过自定义一些 API 来满足功能。
+
+你可以使用 `SpringBoot` 的控制器写法来暴露 API,不同的是需要添加 `@ApiVersion` 注解,没有此注解的 `Controller` 将被忽略:
+
+```java
+@ApiVersion("v1alpha1")
+@RequestMapping("/apples")
+@RestController
+public class AppleController {
+
+ @PostMapping("/starting")
+ public void starting() {
+ }
+}
+```
+
+当插件被启动时,Halo 将会为此 AppleController 生成统一路径的 API。API 前缀组成规则如下:
+
+```text
+/apis/plugin.api.halo.run/{version}/plugins/{plugin-name}/**
+```
+
+示例:
+
+```text
+/apis/plugin.api.halo.run/v1alpha1/plugins/my-plugin/apples/starting
+```
diff --git a/versioned_docs/version-2.4/developer-guide/plugin/api-reference/reverseproxy.md b/versioned_docs/version-2.4/developer-guide/plugin/api-reference/reverseproxy.md
new file mode 100644
index 0000000..ea26180
--- /dev/null
+++ b/versioned_docs/version-2.4/developer-guide/plugin/api-reference/reverseproxy.md
@@ -0,0 +1,36 @@
+---
+title: 静态资源代理
+description: 了解如果使用静态资源代理来访问插件中的静态资源
+---
+
+插件中的静态资源如图片等如果想被外部访问到,需要放到 `src/main/resources` 目录下,并通过创建 ReverseProxy 自定义模型来进行静态资源代理访问。
+
+例如 `src/main/resources` 下的 `static` 目录下有一张 `halo.jpg`:
+
+1. 首先需要在 `src/main/resources/extensions` 下创建一个 `yaml`,文件名可以任意。
+2. 示例配置如下。
+
+```yaml
+apiVersion: plugin.halo.run/v1alpha1
+kind: ReverseProxy
+metadata:
+ # name 为此资源的唯一标识名称,不允许重复,为了避免与其他插件冲突,推荐带上插件名称前缀
+ name: my-plugin-fake-reverse-proxy
+rules:
+ - path: /res/**
+ file:
+ directory: static
+ # 如果想代理 static 下所有静态资源则省略 filename 配置
+ filename: halo.jpg
+```
+
+插件启动后会根据 `/plugins/{plugin-name}/assets/**` 规则生成 API。
+因此该 `ReverseProxy` 的访问路径为: `/plugins/my-plugin/assets/res/halo.jpg`。
+
+- `rules` 下可以添加多组规则。
+- `path` 为路径前缀。
+- `file` 表示访问文件系统,目前暂时仅支持这一种。
+- `directory` 表示要代理的目标文件目录,它相对于 `src/main/resources/` 目录。
+- `filename` 表示要代理的目标文件名。
+
+`directory` 和 `filename` 都是可选的,但必须至少有一个被配置。
diff --git a/versioned_docs/version-2.4/developer-guide/plugin/api-reference/role-template.md b/versioned_docs/version-2.4/developer-guide/plugin/api-reference/role-template.md
new file mode 100644
index 0000000..a92f2c2
--- /dev/null
+++ b/versioned_docs/version-2.4/developer-guide/plugin/api-reference/role-template.md
@@ -0,0 +1,103 @@
+---
+title: API 权限控制
+description: 了解如果对插件中的 API 定义角色模板以接入权限控制
+---
+
+插件中的 APIs 无论是自定义模型自动生成的 APIs 或者是通过 Controller 自定义的 APIs 都只有超级管理员能够访问,如果想将这些 APIs 授权给其他用户访问则需要定义一些 RoleTemplate 的资源以便可以在用户界面上将其分配给其他角色使用。
+
+RoleTemplate 的 yaml 资源也需要放到 `src/main/resources/extensions` 目录下,文件名称可以任意,它的 kind 为 Role 但需要一个 label `halo.run/role-template: "true"` 来表示它是角色模板。
+
+Halo 的权限控制对同一种资源一般只定义两个 RoleTemplate,一个是只读权限,另一个是管理权限,因此如果没有特殊情况需要更细粒度的控制,我们建议你也保持一致:
+
+```yaml
+apiVersion: v1
+kind: Role
+metadata:
+ # 使用 plugin name 作为前缀防止与其他插件冲突
+ name: my-plugin-role-view-persons
+ labels:
+ halo.run/role-template: "true"
+ annotations:
+ rbac.authorization.halo.run/module: "Persons Management"
+ rbac.authorization.halo.run/display-name: "Person Manage"
+ rbac.authorization.halo.run/ui-permissions: |
+ ["plugin:my-plugin:person:view"]
+rules:
+ - apiGroups: ["my-plugin.halo.run"]
+ resources: ["my-plugin/persons"]
+ verbs: ["*"]
+---
+apiVersion: v1
+kind: Role
+metadata:
+ name: my-plugin-role-manage-persons
+ labels:
+ halo.run/role-template: "true"
+ annotations:
+ rbac.authorization.halo.run/dependencies: |
+ [ "role-template-view-person" ]
+ rbac.authorization.halo.run/module: "Persons Management"
+ rbac.authorization.halo.run/display-name: "Person Manage"
+ rbac.authorization.halo.run/ui-permissions: |
+ ["plugin:my-plugin:person:manage"]
+rules:
+ - apiGroups: [ "my-plugin.halo.run" ]
+ resources: [ "my-plugin/persons" ]
+ verbs: [ "get", "list" ]
+```
+
+上述便是根据 [自定义模型](./extension.md) 章节中定义的 Person 自定义模型配置角色模板的示例。
+
+定义了一个用于管理 Person 资源的角色模板 `my-plugin-role-manage-persons`,它具有所有权限,同时定义了一个只允许查询 Person 资源的角色模板 `my-plugin-role-view-persons`。
+
+下面让我们回顾一下这些配置:
+
+`rules` 是个数组,它允许配置多组规则:
+
+- `apiGroups` 对应 `GVK` 中的 `group` 所声明的值。
+- `resources` 对应 API 中的 resource 部分,`my-plugin` 表示插件名称。
+- `verbs` 表示请求动词,可选值为 "create", "delete", "deletecollection", "get", "list", "patch", "update"。对应的 HTTP 请求方式如下表所示:
+
+| HTTP verb | request verb |
+| --------- | ------------------------------------------------------------ |
+| POST | create |
+| GET, HEAD | get (for individual resources), list (for collections, including full object content), watch (for watching an individual resource or collection of resources) |
+| PUT | update |
+| PATCH | patch |
+| DELETE | delete (for individual resources), deletecollection (for collections) |
+
+`metadata.labels` 中必须包含 `halo.run/role-template: "true"` 以表示它此资源要作为角色模板。
+
+`metadata.annotations` 中:
+
+- `rbac.authorization.halo.run/dependencies`:用于声明角色间的依赖关系,例如管理角色必须要依赖查看角色,以避免分配了管理权限却没有查看权限的情况。
+- `rbac.authorization.halo.run/module`:角色模板分组名称。在此示例中,管理 Person 的模板角色将和查看 Person 的模板角色将被在 UI 层面归为一组展示。
+- `rbac.authorization.halo.run/display-name`:模板角色的显示名称,用于展示为用户可读的名称信息。
+- `rbac.authorization.halo.run/ui-permissions`:用于控制 UI 权限,规则为 `plugin:{your-plugin-name}:scope-name`,使用示例为在插件前端部分入口文件 `index.ts` 中用于控制菜单是否显示或者控制页面按钮是否展示:
+
+```javascript
+{
+ path: "",
+ name: "HelloWorld",
+ component: DefaultView,
+ meta: {
+ permissions: ["plugin:my-plugin:person:view"]
+ }
+}
+```
+
+以上定义角色模板的方式适合资源型请求,即需要符合以下规则
+
+- 以 `/api//[///]` 规则组成 APIs。
+- 以 `/apis///[///]` 规则组成的 APIs。
+
+注:`[]`包裹的部分表示可选
+
+如果你的 API 不符合以上资源型 API 的规则,则被定型为非资源型 API,例如 `/healthz`,需要使用一下配置方式:
+
+```yaml
+rules:
+ # nonResourceURL 中的 '*' 是一个全局通配符
+ - nonResourceURLs: ["/healthz", "/healthz/*"]
+ verbs: [ "get", "create"]
+```
diff --git a/versioned_docs/version-2.4/developer-guide/plugin/examples/todolist.md b/versioned_docs/version-2.4/developer-guide/plugin/examples/todolist.md
new file mode 100644
index 0000000..b8ca2da
--- /dev/null
+++ b/versioned_docs/version-2.4/developer-guide/plugin/examples/todolist.md
@@ -0,0 +1,675 @@
+---
+title: Todo List
+description: 这个例子展示了如何开发 Todo List 插件
+---
+
+本示例用于展示如何从插件模板创建一个插件并写一个 Todo List:
+
+首先通过模板仓库创建一个插件,例如叫 `halo-plugin-todolist`
+
+## 配置你的插件
+
+1. 修改 `build.gradle` 中的 `group` 为你自己的,如:
+
+ ```groovy
+ group = 'run.halo.tutorial'
+ ```
+
+2. 修改 `settings.gradle` 中的 `rootProject.name`
+
+ ```groovy
+ rootProject.name = 'halo-plugin-todolist'
+ ```
+
+3. 修改插件的描述文件 `plugin.yaml`,它位于 `src/main/resources/plugin.yaml`。示例:
+
+ ```yaml
+ apiVersion: plugin.halo.run/v1alpha1
+ kind: Plugin
+ metadata:
+ name: todolist
+ spec:
+ enabled: true
+ requires: ">=2.0.0"
+ author:
+ name: halo-dev
+ website: https://halo.run
+ logo: https://halo.run/logo
+ homepage: https://github.com/guqing/halo-plugin-hello-world
+ displayName: "插件 Todo List"
+ description: "插件开发的 hello world,用于学习如何开发一个简单的 Halo 插件"
+ license:
+ - name: "MIT"
+ ```
+
+参考链接:
+
+- [SemVer expression](https://github.com/zafarkhaja/jsemver#semver-expressions-api-ranges)
+- [表单定义](../form-schema.md)
+
+此时我们已经准备好了可以开发一个 TodoList 插件的一切,下面让我们正式进入 TodoList 插件开发教程。
+
+## 运行插件
+
+为了看到效果,首先我们需要让插件能最简单的运行起来。
+
+1. 在 `src/main/java` 下创建包,如 `run.halo.tutorial`,在创建一个类 `TodoListPlugin`,它继承自 `BasePlugin` 类内容如下:
+
+```java
+package run.halo.tutorial;
+
+import org.pf4j.PluginWrapper;
+import org.springframework.stereotype.Component;
+import run.halo.app.plugin.BasePlugin;
+
+@Component
+public class TodoListPlugin extends BasePlugin {
+ public TodoListPlugin(PluginWrapper wrapper) {
+ super(wrapper);
+ }
+}
+```
+
+`src/main/java` 下的文件结构如下:
+
+```text
+.
+└── run
+ └── halo
+ └── tutorial
+ └── TodoListPlugin.java
+```
+
+然后在项目目录执行命令
+
+```shell
+./gradlew build
+```
+
+使用 `IntelliJ IDEA` 打开 Halo,参考 [Halo 开发环境运行](../core/run.md) 及 [插件入门](../hello-world.md) 配置插件的运行模式和路径:
+
+```yaml
+halo:
+ plugin:
+ runtime-mode: development
+ fixed-plugin-path:
+ # 配置为插件绝对路径
+ - /Users/guqing/halo-plugin-todolist
+```
+
+启动 Halo,然后访问 `http://localhost:8090/console`
+
+在插件列表将能看到插件已经被正确启用
+![plugin-todolist-in-list-view](/img/todolist-in-list.png)
+
+## 创建一个自定义模型
+
+我们希望 TodoList 能够被持久化以避免重启后数据丢失,因此需要创建一个自定义模型来进行数据持久化。
+
+首先创建一个 `class` 名为 `Todo` 并写入如下内容:
+
+```java
+package run.halo.tutorial;
+
+import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import run.halo.app.extension.AbstractExtension;
+import run.halo.app.extension.GVK;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@GVK(kind = "Todo", group = "todo.plugin.halo.run",
+ version = "v1alpha1", singular = "todo", plural = "todos")
+public class Todo extends AbstractExtension {
+
+ @Schema(requiredMode = REQUIRED)
+ private TodoSpec spec;
+
+ @Data
+ public static class TodoSpec {
+
+ @Schema(requiredMode = REQUIRED, minLength = 1)
+ private String title;
+
+ @Schema(defaultValue = "false")
+ private Boolean done;
+ }
+}
+```
+
+然后在 `TodoListPlugin` 的 `start` 生命周期方法中注册此自定义模型到 Halo 中。
+
+```diff
+// ...
++ import run.halo.app.extension.SchemeManager;
+
+@Component
+public class TodoListPlugin extends BasePlugin {
++ private final SchemeManager schemeManager;
+
+- public TodoListPlugin(PluginWrapper wrapper) {
++ public TodoListPlugin(PluginWrapper wrapper, SchemeManager schemeManager) {
+ super(wrapper);
++ this.schemeManager = schemeManager;
+ }
+
+ @Override
+ public void start() {
++ // 插件启动时注册自定义模型
++ schemeManager.register(Todo.class);
+ System.out.println("Hello world 插件启动了!");
+ }
+
+ @Override
+ public void stop() {
++ // 插件停用时取消注册自定义模型
++ Scheme todoScheme = schemeManager.get(Todo.class);
++ schemeManager.unregister(todoScheme);
+ System.out.println("Hello world 被停止!");
+ }
+ // ....
+}
+```
+
+然后 build 项目,重启 Halo,访问 `http://localhost:8090/swagger-ui.html`,
+可以找到如下 Todo APIs:
+
+![hello world plugin swagger api for toto](/img/halo-plugin-hello-world-todo-swagger-api.png)
+
+由于所有以 `/api` 和 `/apis` 开头的 APIs 都需要认证才能访问,因此先在 Swagger UI 界面顶部点击 `Authorize` 认证,然后尝试访问
+`GET /apis/todo.plugin.halo.run/v1alpha1/todos` 可以看到如下结果:
+
+```json
+{
+ "page": 0,
+ "size": 0,
+ "total": 0,
+ "items": [],
+ "first": true,
+ "last": true,
+ "hasNext": false,
+ "hasPrevious": false,
+ "totalPages": 1
+}
+```
+
+至此我们完成了一个自定义模型的创建和使用插件生命周期方法实现了自定义模型的注册和删除,下一步我们将编写用户界面,使用这些 APIs 完成 TodoList 功能。
+
+## 编写用户界面
+
+### 目标
+
+我们希望实现如下的用户界面:
+
+- 在左侧菜单添加一个名为 `Todo List` 的菜单项,它属于一个`工具`的组。
+- 内容页为一个简单的 Todo List,它实现以下功能:
+ - 添加 `Todo item`
+ - 将一个 `Todo item` 标记为完成,也可以取消完成状态
+ - 列表有三个 `Tab` 可供切换,用于过滤数据展示
+
+![todo user interface](/img/todo-ui.png)
+
+### 实现
+
+使用模板仓库创建的项目中与 `src` 目录同级有一个 `console` 目录,它即为用户界面的源码目录。
+
+在实现用户界面前我们需要先修改 `console/vite.config.ts` 中的 `pluginName` 为 `plugin.yaml` 中的 `metadata.name`,它用来标识此用户界面所属于插件名 pluginName 标识的插件,以便 Halo 加载 console 目录打包产生的文件。
+
+修改完成后执行
+
+```groovy
+./gradlew build
+```
+
+修改前端项目不需要重启 Halo,只需要 build 然后刷新页面,此时能看到多出来一个菜单项:
+
+![hello-world-in-plugin-list](/img/plugin-hello-world.png)
+
+而我们需要实现的目标中也需要一个菜单项,所以直接修改它即可。
+
+打开 `console/src/index.ts` 文件,修改如下:
+
+```diff
+export default definePlugin({
+- name: "PluginStarter",
++ name: "plugin-hello-world",
+ components: [],
+ routes: [
+ {
+ parentName: "Root",
+ route: {
+- path: "/example",
++ path: "/todos", // TodoList 的路由 path
+ children: [
+ {
+ path: "",
+- name: "Example",
++ name: "TodoList",// 菜单标识名
+ component: DefaultView,
+ meta: {
+- permissions: ["plugin:apples:view"],
+- title: "示例页面",
++ title: "Todo List",//菜单页的浏览器 tab 标题
+ searchable: true,
+ menu: {
+- name: "示例页面",
++ name: "Todo List",// TODO 菜单显示名称
+- group: "示例分组",
+= group: "工具",// 所在组名
+ icon: markRaw(IconGrid),
+ priority: 0,
+ },
+ },
+ },
+ ],
+ },
+ },
+ ],
+ extensionPoints: {},
+ activated() {},
+ deactivated() {},
+});
+```
+
+完成此步骤后 Console 左侧菜单多了一个名 `工具` 的组,其下有 `Todo List`,浏览器标签页名称也是 `Todo List`。
+
+接来下我们需要在右侧内容区域实现 [目标](#目标) 中图示的 Todo 样式,为了快速上手,我们使用 [todomvc](https://todomvc.com/examples/vue/) 提供的 Vue 标准实现。
+编辑 `console/src/views/DefaultView.vue` 文件,清空它的内容,并拷贝 [examples/#todomvc](https://vuejs.org/examples/#todomvc) 的所有代码粘贴到此文件中,并执行以下步骤:
+
+1. `cd console` 切换到 `console` 目录。
+2. ` pnpm install todomvc-app-css `。
+3. 修改 `console/src/views/DefaultView.vue` 最底部的 `style` 标签。
+
+```diff
+-
+```
+
+4. 重新 Build 后刷新页面,便能看到目标图所示效果。
+
+通过以上步骤就实现了一个 Todo List 的用户界面功能,但 `Todo` 数据只是被临时存放到了 `LocalStorage` 中,下一步我们将通过自定义模型生成的 APIs 来让用户界面与服务端交互。
+
+### 与服务端数据交互
+
+本章节我们将通过使用 `Axios` 来完成与插件后端 APIs 进行数据交互,文档参考 [axios-http](https://axios-http.com/docs)。
+
+首先需要安装 `Axios`, 在 console 目录下执行命令:
+
+```shell
+pnpm install axios
+```
+
+为了符合最佳实践,将用 TypeScript 改造之前的 todomvc 示例:
+
+1. 创建 types 文件 `console/src/types/index.ts`
+
+```typescript
+export interface Metadata {
+ name: string;
+ labels?: {
+ [key: string]: string;
+ } | null;
+ annotations?: {
+ [key: string]: string;
+ } | null;
+ version?: number | null;
+ creationTimestamp?: string | null;
+ deletionTimestamp?: string | null;
+}
+
+export interface TodoSpec {
+ title: string;
+ done?: boolean;
+}
+
+/**
+ * 与自定义模型 Todo 对应
+ */
+export interface Todo {
+ spec: TodoSpec;
+ apiVersion: "todo.plugin.halo.run/v1alpha1"; // apiVersion=自定义模型的 group/version
+ kind: "Todo"; // Todo 自定义模型中 @GVK 注解中的 kind
+ metadata: Metadata;
+}
+
+/**
+ * Todo 自定义模型生成 list API 所对应的类型
+ */
+export interface TodoList {
+ page: number;
+ size: number;
+ total: number;
+ items: Array;
+ first: boolean;
+ last: boolean;
+ hasNext: boolean;
+ hasPrevious: boolean;
+ totalPages: number;
+}
+```
+
+编辑 `console/src/views/DefaultView.vue` 文件,将所有内容替换为如下写法:
+
+```typescript
+
+
+
+
+
+
+
+```
+
+这在原先的基础上替换为了 `TypeScipt` 写法,并去除了数据保存到 `LocalStorage` 的逻辑,这也是我们推荐的方式,可读性更强,且有 `TypeScript` 提供类型提示。
+
+至此我们就完成了与插件后端 APIs 实现 Todo List 数据交互的部分。
+
+### 使用 Icon
+
+目前 Todo 的菜单还是默认的网格样式 Icon,在 `console/src/index.ts` 文件中配置有一个 `icon: markRaw(IconGrid)`。以此为例说明该如何使用其他 `Icon`。
+
+1. 安装 [unplugin-icons](https://github.com/antfu/unplugin-icons)。
+
+```shell
+pnpm install -D unplugin-icons
+pnpm install -D @iconify/json
+pnpm install -D @vue/compiler-sfc
+```
+
+2. 编辑 `console/vite.config.ts`,在 `defineConfig` 的 `plugins` 中添加配置,修改如下。
+
+```diff
++ import Icons from "unplugin-icons/vite";
+
+// https://vitejs.dev/config/
+export default defineConfig({
+- plugins: [vue(), vueJsx()],
++ plugins: [vue(), vueJsx(), Icons({ compiler: "vue3" })],
+```
+
+3. 在 `console/tsconfig.app.json` 中加入 `unplugni-icons` 的 `types` 配置。
+
+```diff
+{
+ // ...
+ "compilerOptions": {
+ // ...
+ "paths": {
+ "@/*": ["./src/*"]
+- }
++ },
++ "types": ["unplugin-icons/types/vue"]
+ }
+}
+```
+
+4. 到 [icones](https://icones.js.org/) 搜索你想要使用的图标,并点击它,然后选择 `Unplugin Icons`,会复制到剪贴板。
+
+![unplugin icons selector](/img/unplugin-icons-example.png)
+
+5. 编辑 `console/src/index.ts` 在 `import` 区域粘贴,并 `icon` 属性。
+
+```diff
+- import { IconGrid } from "@halo-dev/components";
++ import VscodeIconsFileTypeLightTodo from "~icons/vscode-icons/file-type-light-todo";
+
+export default definePlugin({
+ routes: [
+ {
+ // ...
+ route: {
+ path: "/todos",
+ children: [
+ {
+ // ...
+ meta: {
+ // ...
+ menu: {
+ // ...
+- icon: markRaw(IconGrid),
++ icon: markRaw(VscodeIconsFileTypeLightTodo),
+ priority: 0,
+ },
+ },
+ },
+ ],
+ },
+ },
+ ],
+ // ...
+});
+```
+
+### 用户界面使用静态资源
+
+如果你想在用户界面中使用图片,你可以放到 `console/src/assets` 中,例如 `logo.png`,并将其作为 Todo 的 Logo 放到标题旁边
+
+![todo logo example](/img/todo-logo-check-48.png)
+
+需要修改 `console/src/views/DefaultView.vue` 示例如下:
+
+```diff
++ import Logo from "../assets/logo.png";
+// ...
+
+
+
+ Heading
+
+
+ Sub heading 1
+
+
+ Some nice text
+
+
+ Sub heading 2
+
+
+ Some even nicer text
+
+```
+
+## 任务列表(Task Lists)
+
+示例:
+
+```markdown
+- [x] Apple
+- [ ] Banana
+```
+
+渲染结果:
+
+```html
+
+```
+
+预览:
+
+- [x] Apple
+- [ ] Banana
+
+## 数学公式(Katex)
+
+我们使用了 Katex 作为数学公式渲染的插件,因为从 1.5.0 开始,我们将直接保存编辑器渲染的内容,在保存的时候就已经保存了渲染好的 Katex 结构。所以在前台无需引入 Katex 插件来进行渲染,但目前仍需要引入 Katex 的样式文件,如果主题没有支持,可以在系统设置的 `自定义内容页 head:` 中加入以下代码:
+
+```html
+
+```
+
+### 行内公式
+
+示例:
+
+```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
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
prevail!
+ John-->>Alice: Great!
+ John->>Bob: How about you?
+ Bob-->>John: Jolly good!
+```
+
+## 短连接
+
+参考 [1.4.17/Markdown 语法](../../versioned_docs/version-1.4/user-guide/markdown.md#短连接)。目前已经在 1.5.x 移除,建议直接使用官方提供的嵌入代码。
diff --git a/versioned_docs/version-2.4/user-guide/pages.md b/versioned_docs/version-2.4/user-guide/pages.md
new file mode 100644
index 0000000..bf0e8bf
--- /dev/null
+++ b/versioned_docs/version-2.4/user-guide/pages.md
@@ -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)章节,此处不再赘述。
diff --git a/versioned_docs/version-2.4/user-guide/plugins.md b/versioned_docs/version-2.4/user-guide/plugins.md
new file mode 100644
index 0000000..c369111
--- /dev/null
+++ b/versioned_docs/version-2.4/user-guide/plugins.md
@@ -0,0 +1,52 @@
+---
+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 接口。
+
+:::info
+你可以点击插件列表指定插件所在行后方的 `···` 更多操作按钮,选择其中的 `重置` 选项将插件提供的设置项恢复为默认值。
+:::
+
+## 启用/禁用插件
+
+点击插件列表或插件详情页中的启用/禁用开关,即可切换插件的启用禁用状态。
+
+![插件启用/禁用](/img/user-guide/plugins/plugin-switch.png)
+
+## 升级插件
+
+点击插件列表指定插件所在行后方的 `···` 更多操作按钮,选择其中的 `升级` 选项即可上传新的插件文件对当前插件进行升级。
+
+## 卸载插件
+
+点击插件列表指定插件所在行后方的 `···` 更多操作按钮,选择其中的 `卸载` 选项即可对当前插件进行卸载。
+![卸载插件](/img/user-guide/plugins/plugin-uninstall.png)
+
+:::info
+目前提供了 `卸载` 及 `卸载并删除配置` 两种卸载方式。
+仅卸载插件时插件的配置信息会进行保留,当重新安装插件后还可以使用之前已保存的配置。
+:::
diff --git a/versioned_docs/version-2.4/user-guide/posts.md b/versioned_docs/version-2.4/user-guide/posts.md
new file mode 100644
index 0000000..a18a2ac
--- /dev/null
+++ b/versioned_docs/version-2.4/user-guide/posts.md
@@ -0,0 +1,150 @@
+---
+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 路径定位,一般不会出现在页面中;
+- **分类目录**:文章所属分类,方便用户区分文章类型进行针对性浏览,一篇文章可以属于多个分类;
+- **标签**:文章添加的标签,方便用户更进一步标识文章信息,一篇文章可以添加多个标签;
+- **自动生成摘要**:文章摘要是对文章内容的概括性描述;
+ - **是**: 系统根据文章内容,自动生成一段摘要文本;
+ - **否**: 用户自行输入文章摘要文本。
+- **允许评论**:是否允许用户在主题端浏览文章时对该文章发起评论;
+- **是否置顶**:文章是否排序在文章列表的最顶部;
+- **可见性**:访问站点主题端时,哪些人可以看到这篇文章;
+ - **公开**:所有用户均可看到这篇文章,包括未登录用户;
+ - **私有**:仅文章作者可以看到这篇文章。
+- **发表时间**:手动指定文章的发表时间,未指定时以实际发布时间为准;
+- **自定义模板**:主题端使用哪一个文章模板进行页面渲染;
+- **封面图**:用户在主题端显示的文章封面图,需要主题支持该功能;
+- **元数据**:供主题、插件使用的额外数据信息。例如 Halo 官网主题仓库中的主题,便可以使用该功能为主题类型的文章增加一条名为下载地址的元数据,提供对应主题的下载链接。
+
+## 发布及取消发布
+
+对于已发布的文章,默认可以通过站点地址进行公开访问,用户可以在文章高级设置中修改可见性。
+
+你可以在上文介绍的文章设置对话框中,修改文章的发布状态。
+
+在文章设置对话框中,若文章当前处于已发布状态,下方会显示取消发布按钮;若文章处于未发布状态,下方怎会显示发布按钮。
+
+## 删除文章
+
+当你不再需要一篇文章时,你可以通过以下方式删除该文章:
+
+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
+文章标签删除后,对应文章的关联将被解除,且该操作不可恢复,请谨慎进行该操作。
+:::
diff --git a/versioned_docs/version-2.4/user-guide/settings.md b/versioned_docs/version-2.4/user-guide/settings.md
new file mode 100644
index 0000000..1e2f662
--- /dev/null
+++ b/versioned_docs/version-2.4/user-guide/settings.md
@@ -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 部分添加 ``;
+- **站点关键词**:格式为以`,`分隔的关键词列表,配置后会在所有页面 HTML 源码的 head 部分添加 ` content="{用户的配置关键词}" />`;
+- **站点描述**:配置后会在所有页面 HTML 源码的 head 部分添加 ` 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 脚本扩展主题端功能。
diff --git a/versioned_docs/version-2.4/user-guide/themes.md b/versioned_docs/version-2.4/user-guide/themes.md
new file mode 100644
index 0000000..7eb7a9e
--- /dev/null
+++ b/versioned_docs/version-2.4/user-guide/themes.md
@@ -0,0 +1,80 @@
+---
+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 为例,该主题提供了布局、样式、侧边栏、页脚及备案设置共五组配置项。
+
+:::info
+你可以点击主题列表指定主题所在行后方的 `···` 更多操作按钮,选择其中的 `重置` 选项将主题提供的设置项恢复为默认值。
+:::
+
+## 预览主题
+
+通过预览功能,你可以在不更改当前启用主题的情况下查看不同主题的效果。点击主题详情页面右上角的 `预览` 按钮预览当前主题,或者进入已安装主题列表,通过后方 `···` 的更多操作中的预览选项预览指定的主题。
+
+![主题设置](/img/user-guide/themes/theme-preview.png)
+
+在主题预览页面你可以切换不同分辨率的设备,模拟主题在不同终端下的显示效果。也可以通过右上角的功能菜单切换预览的主题,或者调整当前主题的设置,查看不同设置下主题所展现的区别。
+
+## 升级主题
+
+点击主题详情页右上角的 `···` 更多操作按钮,选择其中的 `升级` 选项即可上传新的主题包对当前主题进行升级更新。
+
+## 重载主题配置
+
+如果当前主题提供的设置项发生变化,可以通过 `···` 更多操作中的 `重载主题配置` 选项对主题配置项进行更新。
+
+## 卸载主题
+
+进入已安装主题列表,点击指定主题所在行后方的 `···` 更多操作按钮,选择其中的 `卸载` 选项即可对当前主题进行卸载。
+
+![卸载主题](/img/user-guide/themes/theme-uninstall.png)
+
+:::info
+目前提供了 `卸载` 及 `卸载并删除配置` 两种卸载方式。
+仅卸载主题时主题的配置信息会进行保留,当重新安装主题后还可以使用之前已保存的配置。
+:::
diff --git a/versioned_docs/version-2.4/user-guide/users.md b/versioned_docs/version-2.4/user-guide/users.md
new file mode 100644
index 0000000..0af8fe6
--- /dev/null
+++ b/versioned_docs/version-2.4/user-guide/users.md
@@ -0,0 +1,95 @@
+---
+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
+删除角色后,分配了该角色的用户会丧失对应的权限,影响用户使用。此操作不可恢复。
+:::
diff --git a/versioned_sidebars/version-2.4-sidebars.json b/versioned_sidebars/version-2.4-sidebars.json
new file mode 100644
index 0000000..0cc2444
--- /dev/null
+++ b/versioned_sidebars/version-2.4-sidebars.json
@@ -0,0 +1,203 @@
+{
+ "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",
+ "getting-started/install/1panel",
+ {
+ "type": "category",
+ "label": "其他指南",
+ "items": [
+ "getting-started/install/other/oneinstack",
+ "getting-started/install/other/nginxproxymanager",
+ "getting-started/install/other/traefik"
+ ]
+ }
+ ]
+ },
+ "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",
+ "user-guide/faq"
+ ]
+ },
+ {
+ "type": "category",
+ "label": "开发者指南",
+ "link": {
+ "type": "generated-index"
+ },
+ "items": [
+ {
+ "type": "category",
+ "label": "系统开发",
+ "link": {
+ "type": "generated-index"
+ },
+ "items": [
+ "developer-guide/core/prepare",
+ "developer-guide/core/run",
+ "developer-guide/core/build"
+ ]
+ },
+ {
+ "type": "category",
+ "label": "插件开发",
+ "link": {
+ "type": "generated-index"
+ },
+ "items": [
+ "developer-guide/plugin/introduction",
+ "developer-guide/plugin/prepare",
+ "developer-guide/plugin/hello-world",
+ "developer-guide/plugin/publish",
+ {
+ "type": "category",
+ "label": "基础",
+ "link": {
+ "type": "doc",
+ "id": "developer-guide/plugin/structure"
+ },
+ "items": [
+ "developer-guide/plugin/structure",
+ "developer-guide/plugin/runtime-mode",
+ "developer-guide/plugin/lifecycle",
+ "developer-guide/plugin/manifest",
+ "developer-guide/plugin/object-management"
+ ]
+ },
+ {
+ "type": "category",
+ "label": "示例",
+ "link": {
+ "type": "doc",
+ "id": "developer-guide/plugin/examples/todolist"
+ },
+ "items": [
+ "developer-guide/plugin/examples/todolist"
+ ]
+ },
+ {
+ "type": "category",
+ "label": "API 参考",
+ "link": {
+ "type": "doc",
+ "id": "developer-guide/plugin/api-reference/extension"
+ },
+ "items": [
+ "developer-guide/plugin/api-reference/extension",
+ "developer-guide/plugin/api-reference/role-template",
+ "developer-guide/plugin/api-reference/extension-client",
+ "developer-guide/plugin/api-reference/reverseproxy"
+ ]
+ }
+ ]
+ },
+ {
+ "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",
+ "developer-guide/theme/annotations",
+ {
+ "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",
+ "developer-guide/theme/template-variables/author"
+ ]
+ },
+ {
+ "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/global-variables",
+ "developer-guide/theme/template-tag",
+ "developer-guide/theme/code-snippets"
+ ]
+ },
+ "developer-guide/form-schema",
+ "developer-guide/annotations-form"
+ ]
+ },
+ {
+ "type": "category",
+ "label": "参与贡献",
+ "link": {
+ "type": "generated-index"
+ },
+ "items": [
+ "contribution/issue",
+ "contribution/pr"
+ ]
+ },
+ "about"
+ ]
+}
diff --git a/versions.json b/versions.json
index 4fc7370..0aa3b1e 100644
--- a/versions.json
+++ b/versions.json
@@ -1,4 +1,5 @@
[
+ "2.4",
"2.3",
"2.2",
"2.1",