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ʊ],强大易用的开源建站工具。

GitHub release @@ -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"; +// ... +