chore: remove archived docs (#360)

考虑到此仓库的文档版本逐渐变多,会导致编译过慢,所以这个 PR 移除了 2.0-2.9 的历史版本,并将这些版本部署到了 https://v2.archive-docs.halo.run/

<img width="498" alt="image" src="https://github.com/halo-dev/docs/assets/21301288/64281dc1-28c0-4656-a2fc-10ccc050e2a7">

/kind improvement

```release-note
None
```
wan92hen-patch-1
Ryan Wang 6 months ago committed by GitHub
parent d78a16e217
commit 9420d37857
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -94,16 +94,21 @@ const config = {
position: "right", position: "right",
dropdownActiveClassDisabled: true, dropdownActiveClassDisabled: true,
dropdownItemsAfter: [ dropdownItemsAfter: [
{
type: "html",
value: '<hr class="dropdown-separator">',
},
{
type: "html",
className: "dropdown-archived-versions",
value: "<b>Archived versions</b>",
},
...Object.entries(VersionsArchived).map( ...Object.entries(VersionsArchived).map(
([versionName, versionUrl]) => ({ ([versionName, versionUrl]) => ({
label: versionName, label: versionName,
href: versionUrl, href: versionUrl,
}) })
), ),
{
to: "https://v1.legacy-docs.halo.run",
label: "1.x",
},
{ {
to: "/versions", to: "/versions",
label: "All versions", label: "All versions",
@ -231,16 +236,6 @@ const config = {
createRedirects(existingPath) { createRedirects(existingPath) {
if (existingPath.startsWith("/2.16.0-SNAPSHOT/")) { if (existingPath.startsWith("/2.16.0-SNAPSHOT/")) {
return [ return [
existingPath.replace("/2.16.0-SNAPSHOT/", "/2.0.0-SNAPSHOT/"),
existingPath.replace("/2.16.0-SNAPSHOT/", "/2.1.0-SNAPSHOT/"),
existingPath.replace("/2.16.0-SNAPSHOT/", "/2.2.0-SNAPSHOT/"),
existingPath.replace("/2.16.0-SNAPSHOT/", "/2.3.0-SNAPSHOT/"),
existingPath.replace("/2.16.0-SNAPSHOT/", "/2.4.0-SNAPSHOT/"),
existingPath.replace("/2.16.0-SNAPSHOT/", "/2.5.0-SNAPSHOT/"),
existingPath.replace("/2.16.0-SNAPSHOT/", "/2.6.0-SNAPSHOT/"),
existingPath.replace("/2.16.0-SNAPSHOT/", "/2.7.0-SNAPSHOT/"),
existingPath.replace("/2.16.0-SNAPSHOT/", "/2.8.0-SNAPSHOT/"),
existingPath.replace("/2.16.0-SNAPSHOT/", "/2.9.0-SNAPSHOT/"),
existingPath.replace("/2.16.0-SNAPSHOT/", "/2.10.0-SNAPSHOT/"), existingPath.replace("/2.16.0-SNAPSHOT/", "/2.10.0-SNAPSHOT/"),
existingPath.replace("/2.16.0-SNAPSHOT/", "/2.11.0-SNAPSHOT/"), existingPath.replace("/2.16.0-SNAPSHOT/", "/2.11.0-SNAPSHOT/"),
existingPath.replace("/2.16.0-SNAPSHOT/", "/2.12.0-SNAPSHOT/"), existingPath.replace("/2.16.0-SNAPSHOT/", "/2.12.0-SNAPSHOT/"),

@ -21,6 +21,18 @@
"message": "请联系原始链接来源网站的所有者,并告知他们链接已损坏。", "message": "请联系原始链接来源网站的所有者,并告知他们链接已损坏。",
"description": "The 2nd paragraph of the 404 page" "description": "The 2nd paragraph of the 404 page"
}, },
"theme.BackToTopButton.buttonAriaLabel": {
"message": "回到顶部",
"description": "The ARIA label for the back to top button"
},
"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.admonition.note": { "theme.admonition.note": {
"message": "备注", "message": "备注",
"description": "The default label used for the Note admonition (:::note)" "description": "The default label used for the Note admonition (:::note)"
@ -41,18 +53,6 @@
"message": "警告", "message": "警告",
"description": "The default label used for the Caution admonition (:::caution)" "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.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.paginator.navAriaLabel": { "theme.blog.paginator.navAriaLabel": {
"message": "博文列表分页导航", "message": "博文列表分页导航",
"description": "The ARIA label for the blog pagination" "description": "The ARIA label for the blog pagination"
@ -208,22 +208,10 @@
"message": "主导航", "message": "主导航",
"description": "The ARIA label for the main navigation" "description": "The ARIA label for the main navigation"
}, },
"theme.navbar.mobileLanguageDropdown.label": {
"message": "选择语言",
"description": "The label for the mobile language switcher dropdown"
},
"theme.TOCCollapsible.toggleButtonLabel": { "theme.TOCCollapsible.toggleButtonLabel": {
"message": "本页总览", "message": "本页总览",
"description": "The label used by the button on the collapsible TOC component" "description": "The label used by the button on the collapsible TOC component"
}, },
"theme.blog.post.readMore": {
"message": "阅读更多",
"description": "The label used in blog post item excerpts to link to full blog posts"
},
"theme.blog.post.readMoreLabel": {
"message": "阅读 {title} 的全文",
"description": "The ARIA label for the link to full blog posts from excerpts"
},
"theme.blog.post.readingTime.plurals": { "theme.blog.post.readingTime.plurals": {
"message": "阅读需 {readingTime} 分钟", "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)" "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)"
@ -232,6 +220,22 @@
"message": "主页面", "message": "主页面",
"description": "The ARIA label for the home page in the breadcrumbs" "description": "The ARIA label for the home page in the breadcrumbs"
}, },
"theme.docs.sidebar.navAriaLabel": {
"message": "文档侧边栏",
"description": "The ARIA label for the sidebar navigation"
},
"theme.navbar.mobileLanguageDropdown.label": {
"message": "选择语言",
"description": "The label for the mobile language switcher dropdown"
},
"theme.blog.post.readMore": {
"message": "阅读更多",
"description": "The label used in blog post item excerpts to link to full blog posts"
},
"theme.blog.post.readMoreLabel": {
"message": "阅读 {title} 的全文",
"description": "The ARIA label for the link to full blog posts from excerpts"
},
"theme.docs.sidebar.collapseButtonTitle": { "theme.docs.sidebar.collapseButtonTitle": {
"message": "收起侧边栏", "message": "收起侧边栏",
"description": "The title attribute for collapse button of doc sidebar" "description": "The title attribute for collapse button of doc sidebar"
@ -240,10 +244,6 @@
"message": "收起侧边栏", "message": "收起侧边栏",
"description": "The title attribute for collapse button of doc sidebar" "description": "The title attribute for collapse button of doc sidebar"
}, },
"theme.docs.sidebar.navAriaLabel": {
"message": "文档侧边栏",
"description": "The ARIA label for the sidebar navigation"
},
"theme.docs.sidebar.closeSidebarButtonAriaLabel": { "theme.docs.sidebar.closeSidebarButtonAriaLabel": {
"message": "关闭导航栏", "message": "关闭导航栏",
"description": "The ARIA label for close button of mobile sidebar" "description": "The ARIA label for close button of mobile sidebar"
@ -264,137 +264,6 @@
"message": "展开侧边栏", "message": "展开侧边栏",
"description": "The ARIA label and title attribute for expand button of doc sidebar" "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)"
},
"theme.SearchPage.existingResultsTitle": {
"message": "「{query}」的搜索结果",
"description": "The search page title for non-empty query"
},
"theme.SearchPage.emptyResultsTitle": {
"message": "在文档中搜索",
"description": "The search page title for empty query"
},
"theme.SearchPage.inputPlaceholder": {
"message": "在此输入搜索字词",
"description": "The placeholder for search page input"
},
"theme.SearchPage.inputLabel": {
"message": "搜索",
"description": "The ARIA label for search page input"
},
"theme.SearchPage.algoliaLabel": {
"message": "通过 Algolia 搜索",
"description": "The ARIA label for Algolia mention"
},
"theme.SearchPage.noResultsText": {
"message": "未找到任何结果",
"description": "The paragraph for empty search result"
},
"theme.SearchPage.fetchingNewResults": {
"message": "正在获取新的搜索结果...",
"description": "The paragraph for fetching new search results"
},
"theme.SearchBar.label": {
"message": "搜索",
"description": "The ARIA label and placeholder for search button"
},
"theme.SearchModal.searchBox.resetButtonTitle": {
"message": "清除查询",
"description": "The label and ARIA label for search box reset button"
},
"theme.SearchModal.searchBox.cancelButtonText": {
"message": "取消",
"description": "The label and ARIA label for search box cancel button"
},
"theme.SearchModal.startScreen.recentSearchesTitle": {
"message": "最近搜索",
"description": "The title for recent searches"
},
"theme.SearchModal.startScreen.noRecentSearchesText": {
"message": "没有最近搜索",
"description": "The text when no recent searches"
},
"theme.SearchModal.startScreen.saveRecentSearchButtonTitle": {
"message": "保存这个搜索",
"description": "The label for save recent search button"
},
"theme.SearchModal.startScreen.removeRecentSearchButtonTitle": {
"message": "从历史记录中删除这个搜索",
"description": "The label for remove recent search button"
},
"theme.SearchModal.startScreen.favoriteSearchesTitle": {
"message": "收藏",
"description": "The title for favorite searches"
},
"theme.SearchModal.startScreen.removeFavoriteSearchButtonTitle": {
"message": "从收藏列表中删除这个搜索",
"description": "The label for remove favorite search button"
},
"theme.SearchModal.errorScreen.titleText": {
"message": "无法获取结果",
"description": "The title for error screen of search modal"
},
"theme.SearchModal.errorScreen.helpText": {
"message": "你可能需要检查网络连接。",
"description": "The help text for error screen of search modal"
},
"theme.SearchModal.footer.selectText": {
"message": "选中",
"description": "The explanatory text of the action for the enter key"
},
"theme.SearchModal.footer.selectKeyAriaLabel": {
"message": "Enter 键",
"description": "The ARIA label for the Enter key button that makes the selection"
},
"theme.SearchModal.footer.navigateText": {
"message": "导航",
"description": "The explanatory text of the action for the Arrow up and Arrow down key"
},
"theme.SearchModal.footer.navigateUpKeyAriaLabel": {
"message": "向上键",
"description": "The ARIA label for the Arrow up key button that makes the navigation"
},
"theme.SearchModal.footer.navigateDownKeyAriaLabel": {
"message": "向下键",
"description": "The ARIA label for the Arrow down key button that makes the navigation"
},
"theme.SearchModal.footer.closeText": {
"message": "关闭",
"description": "The explanatory text of the action for Escape key"
},
"theme.SearchModal.footer.closeKeyAriaLabel": {
"message": "Esc 键",
"description": "The ARIA label for the Escape key button that close the modal"
},
"theme.SearchModal.footer.searchByText": {
"message": "搜索提供",
"description": "The text explain that the search is making by Algolia"
},
"theme.SearchModal.noResultsScreen.noResultsText": {
"message": "没有结果:",
"description": "The text explains that there are no results for the following search"
},
"theme.SearchModal.noResultsScreen.suggestedQueryText": {
"message": "试试搜索",
"description": "The text for the suggested query when no results are found for the following search"
},
"theme.SearchModal.noResultsScreen.reportMissingResultsText": {
"message": "认为这个查询应该有结果?",
"description": "The text for the question where the user thinks there are missing results"
},
"theme.SearchModal.noResultsScreen.reportMissingResultsLinkText": {
"message": "请告知我们。",
"description": "The text for the link to report missing results"
},
"theme.SearchModal.placeholder": {
"message": "搜索文档",
"description": "The placeholder of the input of the DocSearch pop-up modal"
},
"theme.ErrorPageContent.tryAgain": { "theme.ErrorPageContent.tryAgain": {
"message": "重试", "message": "重试",
"description": "The label of the button to try again rendering when the React error boundary captures an error" "description": "The label of the button to try again rendering when the React error boundary captures an error"

@ -11,6 +11,10 @@
"message": "安装指南", "message": "安装指南",
"description": "The label for category 安装指南 in sidebar tutorial" "description": "The label for category 安装指南 in sidebar tutorial"
}, },
"sidebar.tutorial.category.云平台": {
"message": "云平台",
"description": "The label for category 云平台 in sidebar tutorial"
},
"sidebar.tutorial.category.其他指南": { "sidebar.tutorial.category.其他指南": {
"message": "其他指南", "message": "其他指南",
"description": "The label for category 其他指南 in sidebar tutorial" "description": "The label for category 其他指南 in sidebar tutorial"

@ -1,46 +0,0 @@
{
"version.label": {
"message": "2.0",
"description": "The label for version 2.0"
},
"sidebar.tutorial.category.入门": {
"message": "入门",
"description": "The label for category 入门 in sidebar tutorial"
},
"sidebar.tutorial.category.安装指南": {
"message": "安装指南",
"description": "The label for category 安装指南 in sidebar tutorial"
},
"sidebar.tutorial.category.其他指南": {
"message": "其他指南",
"description": "The label for category 其他指南 in sidebar tutorial"
},
"sidebar.tutorial.category.用户指南": {
"message": "用户指南",
"description": "The label for category 用户指南 in sidebar tutorial"
},
"sidebar.tutorial.category.参与贡献": {
"message": "参与贡献",
"description": "The label for category 参与贡献 in sidebar tutorial"
},
"sidebar.developer.category.系统开发": {
"message": "系统开发",
"description": "The label for category 系统开发 in sidebar developer"
},
"sidebar.developer.category.插件开发": {
"message": "插件开发",
"description": "The label for category 插件开发 in sidebar developer"
},
"sidebar.developer.category.主题开发": {
"message": "主题开发",
"description": "The label for category 主题开发 in sidebar developer"
},
"sidebar.developer.category.模板变量": {
"message": "模板变量",
"description": "The label for category 模板变量 in sidebar developer"
},
"sidebar.developer.category.Finder API": {
"message": "Finder API",
"description": "The label for category Finder API in sidebar developer"
}
}

@ -1,46 +0,0 @@
{
"version.label": {
"message": "2.1",
"description": "The label for version 2.1"
},
"sidebar.tutorial.category.入门": {
"message": "入门",
"description": "The label for category 入门 in sidebar tutorial"
},
"sidebar.tutorial.category.安装指南": {
"message": "安装指南",
"description": "The label for category 安装指南 in sidebar tutorial"
},
"sidebar.tutorial.category.其他指南": {
"message": "其他指南",
"description": "The label for category 其他指南 in sidebar tutorial"
},
"sidebar.tutorial.category.用户指南": {
"message": "用户指南",
"description": "The label for category 用户指南 in sidebar tutorial"
},
"sidebar.tutorial.category.参与贡献": {
"message": "参与贡献",
"description": "The label for category 参与贡献 in sidebar tutorial"
},
"sidebar.developer.category.系统开发": {
"message": "系统开发",
"description": "The label for category 系统开发 in sidebar developer"
},
"sidebar.developer.category.插件开发": {
"message": "插件开发",
"description": "The label for category 插件开发 in sidebar developer"
},
"sidebar.developer.category.主题开发": {
"message": "主题开发",
"description": "The label for category 主题开发 in sidebar developer"
},
"sidebar.developer.category.模板变量": {
"message": "模板变量",
"description": "The label for category 模板变量 in sidebar developer"
},
"sidebar.developer.category.Finder API": {
"message": "Finder API",
"description": "The label for category Finder API in sidebar developer"
}
}

@ -11,6 +11,10 @@
"message": "安装指南", "message": "安装指南",
"description": "The label for category 安装指南 in sidebar tutorial" "description": "The label for category 安装指南 in sidebar tutorial"
}, },
"sidebar.tutorial.category.云平台": {
"message": "云平台",
"description": "The label for category 云平台 in sidebar tutorial"
},
"sidebar.tutorial.category.其他指南": { "sidebar.tutorial.category.其他指南": {
"message": "其他指南", "message": "其他指南",
"description": "The label for category 其他指南 in sidebar tutorial" "description": "The label for category 其他指南 in sidebar tutorial"

@ -1,58 +0,0 @@
{
"version.label": {
"message": "2.2",
"description": "The label for version 2.2"
},
"sidebar.tutorial.category.入门": {
"message": "入门",
"description": "The label for category 入门 in sidebar tutorial"
},
"sidebar.tutorial.category.安装指南": {
"message": "安装指南",
"description": "The label for category 安装指南 in sidebar tutorial"
},
"sidebar.tutorial.category.其他指南": {
"message": "其他指南",
"description": "The label for category 其他指南 in sidebar tutorial"
},
"sidebar.tutorial.category.用户指南": {
"message": "用户指南",
"description": "The label for category 用户指南 in sidebar tutorial"
},
"sidebar.tutorial.category.参与贡献": {
"message": "参与贡献",
"description": "The label for category 参与贡献 in sidebar tutorial"
},
"sidebar.developer.category.系统开发": {
"message": "系统开发",
"description": "The label for category 系统开发 in sidebar developer"
},
"sidebar.developer.category.插件开发": {
"message": "插件开发",
"description": "The label for category 插件开发 in sidebar developer"
},
"sidebar.developer.category.基础": {
"message": "基础",
"description": "The label for category 基础 in sidebar developer"
},
"sidebar.developer.category.示例": {
"message": "示例",
"description": "The label for category 示例 in sidebar developer"
},
"sidebar.developer.category.API 参考": {
"message": "API 参考",
"description": "The label for category API 参考 in sidebar developer"
},
"sidebar.developer.category.主题开发": {
"message": "主题开发",
"description": "The label for category 主题开发 in sidebar developer"
},
"sidebar.developer.category.模板变量": {
"message": "模板变量",
"description": "The label for category 模板变量 in sidebar developer"
},
"sidebar.developer.category.Finder API": {
"message": "Finder API",
"description": "The label for category Finder API in sidebar developer"
}
}

@ -1,58 +0,0 @@
{
"version.label": {
"message": "2.3",
"description": "The label for version 2.3"
},
"sidebar.tutorial.category.入门": {
"message": "入门",
"description": "The label for category 入门 in sidebar tutorial"
},
"sidebar.tutorial.category.安装指南": {
"message": "安装指南",
"description": "The label for category 安装指南 in sidebar tutorial"
},
"sidebar.tutorial.category.其他指南": {
"message": "其他指南",
"description": "The label for category 其他指南 in sidebar tutorial"
},
"sidebar.tutorial.category.用户指南": {
"message": "用户指南",
"description": "The label for category 用户指南 in sidebar tutorial"
},
"sidebar.tutorial.category.参与贡献": {
"message": "参与贡献",
"description": "The label for category 参与贡献 in sidebar tutorial"
},
"sidebar.developer.category.系统开发": {
"message": "系统开发",
"description": "The label for category 系统开发 in sidebar developer"
},
"sidebar.developer.category.插件开发": {
"message": "插件开发",
"description": "The label for category 插件开发 in sidebar developer"
},
"sidebar.developer.category.基础": {
"message": "基础",
"description": "The label for category 基础 in sidebar developer"
},
"sidebar.developer.category.示例": {
"message": "示例",
"description": "The label for category 示例 in sidebar developer"
},
"sidebar.developer.category.API 参考": {
"message": "API 参考",
"description": "The label for category API 参考 in sidebar developer"
},
"sidebar.developer.category.主题开发": {
"message": "主题开发",
"description": "The label for category 主题开发 in sidebar developer"
},
"sidebar.developer.category.模板变量": {
"message": "模板变量",
"description": "The label for category 模板变量 in sidebar developer"
},
"sidebar.developer.category.Finder API": {
"message": "Finder API",
"description": "The label for category Finder API in sidebar developer"
}
}

@ -1,58 +0,0 @@
{
"version.label": {
"message": "2.4",
"description": "The label for version 2.4"
},
"sidebar.tutorial.category.入门": {
"message": "入门",
"description": "The label for category 入门 in sidebar tutorial"
},
"sidebar.tutorial.category.安装指南": {
"message": "安装指南",
"description": "The label for category 安装指南 in sidebar tutorial"
},
"sidebar.tutorial.category.其他指南": {
"message": "其他指南",
"description": "The label for category 其他指南 in sidebar tutorial"
},
"sidebar.tutorial.category.用户指南": {
"message": "用户指南",
"description": "The label for category 用户指南 in sidebar tutorial"
},
"sidebar.tutorial.category.参与贡献": {
"message": "参与贡献",
"description": "The label for category 参与贡献 in sidebar tutorial"
},
"sidebar.developer.category.系统开发": {
"message": "系统开发",
"description": "The label for category 系统开发 in sidebar developer"
},
"sidebar.developer.category.插件开发": {
"message": "插件开发",
"description": "The label for category 插件开发 in sidebar developer"
},
"sidebar.developer.category.基础": {
"message": "基础",
"description": "The label for category 基础 in sidebar developer"
},
"sidebar.developer.category.示例": {
"message": "示例",
"description": "The label for category 示例 in sidebar developer"
},
"sidebar.developer.category.API 参考": {
"message": "API 参考",
"description": "The label for category API 参考 in sidebar developer"
},
"sidebar.developer.category.主题开发": {
"message": "主题开发",
"description": "The label for category 主题开发 in sidebar developer"
},
"sidebar.developer.category.模板变量": {
"message": "模板变量",
"description": "The label for category 模板变量 in sidebar developer"
},
"sidebar.developer.category.Finder API": {
"message": "Finder API",
"description": "The label for category Finder API in sidebar developer"
}
}

@ -1,58 +0,0 @@
{
"version.label": {
"message": "2.5",
"description": "The label for version 2.5"
},
"sidebar.tutorial.category.入门": {
"message": "入门",
"description": "The label for category 入门 in sidebar tutorial"
},
"sidebar.tutorial.category.安装指南": {
"message": "安装指南",
"description": "The label for category 安装指南 in sidebar tutorial"
},
"sidebar.tutorial.category.其他指南": {
"message": "其他指南",
"description": "The label for category 其他指南 in sidebar tutorial"
},
"sidebar.tutorial.category.用户指南": {
"message": "用户指南",
"description": "The label for category 用户指南 in sidebar tutorial"
},
"sidebar.tutorial.category.参与贡献": {
"message": "参与贡献",
"description": "The label for category 参与贡献 in sidebar tutorial"
},
"sidebar.developer.category.系统开发": {
"message": "系统开发",
"description": "The label for category 系统开发 in sidebar developer"
},
"sidebar.developer.category.插件开发": {
"message": "插件开发",
"description": "The label for category 插件开发 in sidebar developer"
},
"sidebar.developer.category.基础": {
"message": "基础",
"description": "The label for category 基础 in sidebar developer"
},
"sidebar.developer.category.示例": {
"message": "示例",
"description": "The label for category 示例 in sidebar developer"
},
"sidebar.developer.category.API 参考": {
"message": "API 参考",
"description": "The label for category API 参考 in sidebar developer"
},
"sidebar.developer.category.主题开发": {
"message": "主题开发",
"description": "The label for category 主题开发 in sidebar developer"
},
"sidebar.developer.category.模板变量": {
"message": "模板变量",
"description": "The label for category 模板变量 in sidebar developer"
},
"sidebar.developer.category.Finder API": {
"message": "Finder API",
"description": "The label for category Finder API in sidebar developer"
}
}

@ -1,58 +0,0 @@
{
"version.label": {
"message": "2.6",
"description": "The label for version 2.6"
},
"sidebar.tutorial.category.入门": {
"message": "入门",
"description": "The label for category 入门 in sidebar tutorial"
},
"sidebar.tutorial.category.安装指南": {
"message": "安装指南",
"description": "The label for category 安装指南 in sidebar tutorial"
},
"sidebar.tutorial.category.其他指南": {
"message": "其他指南",
"description": "The label for category 其他指南 in sidebar tutorial"
},
"sidebar.tutorial.category.用户指南": {
"message": "用户指南",
"description": "The label for category 用户指南 in sidebar tutorial"
},
"sidebar.tutorial.category.参与贡献": {
"message": "参与贡献",
"description": "The label for category 参与贡献 in sidebar tutorial"
},
"sidebar.developer.category.系统开发": {
"message": "系统开发",
"description": "The label for category 系统开发 in sidebar developer"
},
"sidebar.developer.category.插件开发": {
"message": "插件开发",
"description": "The label for category 插件开发 in sidebar developer"
},
"sidebar.developer.category.基础": {
"message": "基础",
"description": "The label for category 基础 in sidebar developer"
},
"sidebar.developer.category.示例": {
"message": "示例",
"description": "The label for category 示例 in sidebar developer"
},
"sidebar.developer.category.API 参考": {
"message": "API 参考",
"description": "The label for category API 参考 in sidebar developer"
},
"sidebar.developer.category.主题开发": {
"message": "主题开发",
"description": "The label for category 主题开发 in sidebar developer"
},
"sidebar.developer.category.模板变量": {
"message": "模板变量",
"description": "The label for category 模板变量 in sidebar developer"
},
"sidebar.developer.category.Finder API": {
"message": "Finder API",
"description": "The label for category Finder API in sidebar developer"
}
}

@ -1,58 +0,0 @@
{
"version.label": {
"message": "2.7",
"description": "The label for version 2.7"
},
"sidebar.tutorial.category.入门": {
"message": "入门",
"description": "The label for category 入门 in sidebar tutorial"
},
"sidebar.tutorial.category.安装指南": {
"message": "安装指南",
"description": "The label for category 安装指南 in sidebar tutorial"
},
"sidebar.tutorial.category.其他指南": {
"message": "其他指南",
"description": "The label for category 其他指南 in sidebar tutorial"
},
"sidebar.tutorial.category.用户指南": {
"message": "用户指南",
"description": "The label for category 用户指南 in sidebar tutorial"
},
"sidebar.tutorial.category.参与贡献": {
"message": "参与贡献",
"description": "The label for category 参与贡献 in sidebar tutorial"
},
"sidebar.developer.category.系统开发": {
"message": "系统开发",
"description": "The label for category 系统开发 in sidebar developer"
},
"sidebar.developer.category.插件开发": {
"message": "插件开发",
"description": "The label for category 插件开发 in sidebar developer"
},
"sidebar.developer.category.基础": {
"message": "基础",
"description": "The label for category 基础 in sidebar developer"
},
"sidebar.developer.category.示例": {
"message": "示例",
"description": "The label for category 示例 in sidebar developer"
},
"sidebar.developer.category.API 参考": {
"message": "API 参考",
"description": "The label for category API 参考 in sidebar developer"
},
"sidebar.developer.category.主题开发": {
"message": "主题开发",
"description": "The label for category 主题开发 in sidebar developer"
},
"sidebar.developer.category.模板变量": {
"message": "模板变量",
"description": "The label for category 模板变量 in sidebar developer"
},
"sidebar.developer.category.Finder API": {
"message": "Finder API",
"description": "The label for category Finder API in sidebar developer"
}
}

@ -1,58 +0,0 @@
{
"version.label": {
"message": "2.8",
"description": "The label for version 2.8"
},
"sidebar.tutorial.category.入门": {
"message": "入门",
"description": "The label for category 入门 in sidebar tutorial"
},
"sidebar.tutorial.category.安装指南": {
"message": "安装指南",
"description": "The label for category 安装指南 in sidebar tutorial"
},
"sidebar.tutorial.category.其他指南": {
"message": "其他指南",
"description": "The label for category 其他指南 in sidebar tutorial"
},
"sidebar.tutorial.category.用户指南": {
"message": "用户指南",
"description": "The label for category 用户指南 in sidebar tutorial"
},
"sidebar.tutorial.category.参与贡献": {
"message": "参与贡献",
"description": "The label for category 参与贡献 in sidebar tutorial"
},
"sidebar.developer.category.系统开发": {
"message": "系统开发",
"description": "The label for category 系统开发 in sidebar developer"
},
"sidebar.developer.category.插件开发": {
"message": "插件开发",
"description": "The label for category 插件开发 in sidebar developer"
},
"sidebar.developer.category.基础": {
"message": "基础",
"description": "The label for category 基础 in sidebar developer"
},
"sidebar.developer.category.示例": {
"message": "示例",
"description": "The label for category 示例 in sidebar developer"
},
"sidebar.developer.category.API 参考": {
"message": "API 参考",
"description": "The label for category API 参考 in sidebar developer"
},
"sidebar.developer.category.主题开发": {
"message": "主题开发",
"description": "The label for category 主题开发 in sidebar developer"
},
"sidebar.developer.category.模板变量": {
"message": "模板变量",
"description": "The label for category 模板变量 in sidebar developer"
},
"sidebar.developer.category.Finder API": {
"message": "Finder API",
"description": "The label for category Finder API in sidebar developer"
}
}

@ -1,58 +0,0 @@
{
"version.label": {
"message": "2.9",
"description": "The label for version 2.9"
},
"sidebar.tutorial.category.入门": {
"message": "入门",
"description": "The label for category 入门 in sidebar tutorial"
},
"sidebar.tutorial.category.安装指南": {
"message": "安装指南",
"description": "The label for category 安装指南 in sidebar tutorial"
},
"sidebar.tutorial.category.其他指南": {
"message": "其他指南",
"description": "The label for category 其他指南 in sidebar tutorial"
},
"sidebar.tutorial.category.用户指南": {
"message": "用户指南",
"description": "The label for category 用户指南 in sidebar tutorial"
},
"sidebar.tutorial.category.参与贡献": {
"message": "参与贡献",
"description": "The label for category 参与贡献 in sidebar tutorial"
},
"sidebar.developer.category.系统开发": {
"message": "系统开发",
"description": "The label for category 系统开发 in sidebar developer"
},
"sidebar.developer.category.插件开发": {
"message": "插件开发",
"description": "The label for category 插件开发 in sidebar developer"
},
"sidebar.developer.category.基础": {
"message": "基础",
"description": "The label for category 基础 in sidebar developer"
},
"sidebar.developer.category.示例": {
"message": "示例",
"description": "The label for category 示例 in sidebar developer"
},
"sidebar.developer.category.API 参考": {
"message": "API 参考",
"description": "The label for category API 参考 in sidebar developer"
},
"sidebar.developer.category.主题开发": {
"message": "主题开发",
"description": "The label for category 主题开发 in sidebar developer"
},
"sidebar.developer.category.模板变量": {
"message": "模板变量",
"description": "The label for category 模板变量 in sidebar developer"
},
"sidebar.developer.category.Finder API": {
"message": "Finder API",
"description": "The label for category Finder API in sidebar developer"
}
}

@ -41,3 +41,12 @@ p > img {
.medium-zoom-image--opened { .medium-zoom-image--opened {
z-index: 999; z-index: 999;
} }
.dropdown-archived-versions {
font-size: 0.875rem;
padding: 0.2rem 0.5rem;
}
.dropdown-separator {
margin: 0.3rem 0;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 798 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 194 KiB

@ -1,16 +0,0 @@
---
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` 等待我们合并即可。

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

@ -1,110 +0,0 @@
---
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)
,请确保所有代码格式化之后再提交。

@ -1,103 +0,0 @@
---
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
```
```bash
git clone https://github.com/halo-dev/console
# 或者使用 ssh 的方式 clone推荐
git clone git@github.com:halo-dev/console.git
# 切换到最新的 tag
cd console
git checkout v2.0.0
```
:::tip
请务必按照以上要求切换到最新的 tag而不是直接使用 main 分支构建main 分支是我们的开发分支。此文档以 `2.0.0` 为例,查看最新的 tag 可使用 `git tag --column` 查看。
:::
## 构建 Console
```bash
cd path/to/console
```
```bash
pnpm install
```
```bash
pnpm build:packages
```
```bash
pnpm build
```
构建完成之后,在 console 目录下产生的 `dist` 目录即为构建完成的文件。最后将 `dist` 目录的所有文件复制到 halo 项目的 `src/main/resources/console` 目录。
## 构建 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 部署](../../getting-started/install/docker-compose.md)

@ -1,30 +0,0 @@
---
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` 配置文件,点击确定即可。
至此,有关代码风格检查工具和格式化配置已经完成。

@ -1,25 +0,0 @@
---
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`:运行日志目录。

@ -1,118 +0,0 @@
---
title: 开发环境运行
description: 开发环境运行的指南
---
:::info
在此之前,我们推荐你先阅读[《准备工作》](./prepare),检查本地环境是否满足要求。
:::
## 项目结构说明
目前如果需要完整的运行 Halo总共需要三个部分
1. Halo 主项目([halo-dev/halo](https://github.com/halo-dev/halo))
2. Console 控制台([halo-dev/console](https://github.com/halo-dev/console))
3. 主题
:::info 说明
当前 Halo 主项目并不会将 Console 的构建资源托管到 Git 版本控制,所以在开发环境是需要同时运行 Console 项目的。当然,在我们的最终发布版本的时候会在 CI 中自动构建 Console 到 Halo 主项目。
:::
## 克隆项目
如果你已经 fork 了相关仓库,请将以下命令中的 halo-dev 替换为你的 GitHub 用户名。
```bash
git clone https://github.com/halo-dev/halo
# 或者使用 ssh 的方式 clone推荐
git clone git@github.com:halo-dev/halo.git
```
```bash
git clone https://github.com/halo-dev/console
# 或者使用 ssh 的方式 clone推荐
git clone git@github.com:halo-dev/console.git
```
## 运行 Console
```bash
cd path/to/console
```
```bash
pnpm install
```
```bash
pnpm build:packages
```
```bash
pnpm dev
```
最终控制台打印了如下信息即代表运行正常:
```bash
VITE v3.1.6 ready in 638 ms
➜ Local: http://localhost:3000/console/
```
:::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
```
:::

@ -1,36 +0,0 @@
---
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`
:::

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

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

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

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

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

@ -1,187 +0,0 @@
---
title: 文章分类
description: 文章分类 - CategoryFinder
---
import CategoryVo from "../vo/CategoryVo.md"
import CategoryTreeVo from "../vo/CategoryTreeVo.md"
## getByName(name)
```js
categoryFinder.getByName(name)
```
### 描述
根据 `metadata.name` 获取文章分类。
### 参数
1. `name:string` - 分类的唯一标识 `metadata.name`
### 返回值
[#CategoryVo](#categoryvo)
### 示例
```html
<div th:with="category = ${categoryFinder.getByName('category-foo')}">
<a th:href="@{${category.status.permalink}}" th:text="${category.spec.displayName}"></a>
</div>
```
## getByNames(names)
```js
categoryFinder.getByNames(names)
```
### 描述
根据一组 `metadata.name` 获取文章分类。
### 参数
1. `names:List<string>` - 分类的唯一标识 `metadata.name` 的集合。
### 返回值
List<[#CategoryVo](#categoryvo)>
### 示例
```html
<div th:with="categories = ${categoryFinder.getByNames(['category-foo', 'category-bar'])}">
<a th:each="category : ${categories}" th:href="@{${category.status.permalink}}" th:text="${category.spec.displayName}"></a>
</div>
```
## list(page,size)
```js
categoryFinder.list(page,size)
```
### 描述
根据分页参数获取分类列表。
### 参数
1. `page:int` - 分页页码,从 1 开始
2. `size:int` - 分页条数
### 返回值
[#ListResult<CategoryVo\>](#listresultcategoryvo)
### 示例
```html
<ul th:with="categories = ${categoryFinder.list(1,10)}">
<li th:each="category : ${categories.items}">
<a th:href="@{${category.status.permalink}}" th:text="${category.spec.displayName}"></a>
</li>
</ul>
```
## listAll()
```js
categoryFinder.listAll()
```
### 描述
获取所有文章分类。
### 参数
### 返回值
List<[#CategoryVo](#categoryvo)>
### 示例
```html
<ul th:with="categories = ${categoryFinder.listAll()}">
<li th:each="category : ${categories}">
<a th:href="@{${category.status.permalink}}" th:text="${category.spec.displayName}"></a>
</li>
</ul>
```
## listAsTree()
```js
categoryFinder.listAsTree()
```
### 描述
获取所有文章分类的多层级结构。
### 参数
### 返回值
List<[#CategoryTreeVo](#categorytreevo)>
### 示例
```html
<div th:with="categories = ${categoryFinder.listAsTree()}">
<ul>
<li th:replace="~{modules/category-tree :: single(categories=${categories})}" />
</ul>
</div>
```
```html title="/templates/category-tree.html"
<ul th:fragment="next (categories)">
<li th:fragment="single (categories)" th:each="category : ${categories}">
<a th:href="@{${category.status.permalink}}">
<span th:text="${category.spec.displayName}"> </span>
</a>
<th:block th:if="${not #lists.isEmpty(category.children)}">
<th:block th:replace="~{modules/category-tree :: next (categories=${category.children})}"></th:block>
</th:block>
</li>
</ul>
```
## 类型定义
### CategoryVo
<CategoryVo />
### ListResult<CategoryVo\>
```json title="ListResult<CategoryVo>"
{
"page": 0, // 当前页码
"size": 0, // 每页条数
"total": 0, // 总条数
"items": "List<#CategoryVo>", // 分类列表数据
"first": true, // 是否为第一页
"last": true, // 是否为最后一页
"hasNext": true, // 是否有下一页
"hasPrevious": true, // 是否有上一页
"totalPages": 0 // 总页数
}
```
- [#CategoryVo](#categoryvo)
### CategoryTreeVo
<CategoryTreeVo />
- [#CategoryTreeVo](#categorytreevo)

@ -1,155 +0,0 @@
---
title: 评论
description: 评论 - CommentFinder
---
import CommentVo from "../vo/CommentVo.md"
import ReplyVo from "../vo/ReplyVo.md"
## getByName(name)
```js
commentFinder.getByName(name)
```
### 描述
根据 `metadata.name` 获取评论。
### 参数
1. `name:string` - 评论的唯一标识 `metadata.name`
### 返回值
[#CommentVo](#commentvo)
### 示例
```html
<div th:with="comment = ${commentFinder.getByName('comment-foo')}">
<span th:text="${comment.spec.owner.displayName}"></span>
<div th:text="${comment.spec.content}"></div>
</div>
```
## list(ref,page,size)
```js
commentFinder.list(ref,page,size)
```
### 描述
根据评论的 `metadata.name` 和分页参数获取回复列表。
### 参数
1. `ref:#Ref` - 评论的唯一标识 `metadata.name`
2. `page:int` - 分页页码,从 1 开始
3. `size:int` - 分页条数
- [#Ref](#ref)
### 返回值
[#ListResult<CommentVo\>](#listresultcommentvo)
### 示例
```html
<ul th:with="comments = ${commentFinder.list({ group: 'content.halo.run', version: 'v1alpha1', kind: 'Post', name: 'post-foo' },1,10)}">
<li th:each="comment : ${comments.items}">
<span th:text="${comment.spec.owner.displayName}"></span>
<div th:text="${comment.spec.content}"></div>
</li>
</ul>
```
## listReply(commentName,page,size)
```js
commentFinder.listReply(commentName,page,size)
```
### 描述
根据评论的 `metadata.name` 和分页参数获取回复列表。
### 参数
1. `commentName:string` - 评论的唯一标识 `metadata.name`
1. `page:int` - 分页页码,从 1 开始
2. `size:int` - 分页条数
### 返回值
[#ListResult<ReplyVo\>](#listresultreplyvo)
### 示例
```html
<ul th:with="replies = ${commentFinder.listReply('comment-foo',1,10)}">
<li th:each="reply : ${replies.items}">
<span th:text="${reply.spec.owner.displayName}"></span>
<div th:text="${reply.spec.content}"></div>
</li>
</ul>
```
## 类型定义
### CommentVo
<CommentVo />
### ListResult<CommentVo\>
```json title="ListResult<CommentVo>"
{
"page": 0, // 当前页码
"size": 0, // 每页条数
"total": 0, // 总条数
"items": "List<#CommentVo>", // 评论列表数据
"first": true, // 是否为第一页
"last": true, // 是否为最后一页
"hasNext": true, // 是否有下一页
"hasPrevious": true, // 是否有上一页
"totalPages": 0 // 总页数
}
```
- [#CommentVo](#commentvo)
### ReplyVo
<ReplyVo />
### ListResult<ReplyVo\>
```json title="ListResult<ReplyVo>"
{
"page": 0, // 当前页码
"size": 0, // 每页条数
"total": 0, // 总条数
"items": "List<#ReplyVo>", // 回复列表数据
"first": true, // 是否为第一页
"last": true, // 是否为最后一页
"hasNext": true, // 是否有下一页
"hasPrevious": true, // 是否有上一页
"totalPages": 0 // 总页数
}
```
- [#ReplyVo](#replyvo)
### Ref
```json title="Ref"
{
"group": "string",
"kind": "string",
"version": "string",
"name": "string"
}
```

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

@ -1,77 +0,0 @@
---
title: 导航菜单
description: 导航菜单 - MenuFinder
---
import MenuItemVo from "../vo/MenuItemVo.md"
import MenuVo from "../vo/MenuVo.md"
## getByName(name)
```js
menuFinder.getByName(name)
```
### 描述
根据 `metadata.name` 获取菜单。
### 参数
1. `name:string` - 菜单的唯一标识 `metadata.name`
### 返回值
[#MenuVo](#menuvo)
### 示例
```html
<div th:with="menu = ${menuFinder.getByName('menu-foo')}">
<ul th:with="menuItems = ${menu.menuItems}">
<li th:each="menuItem : ${menuItems}">
<a th:href="@{${menuItem.status.href}}" th:text="${menuItem.status.displayName}"></a>
</li>
</ul>
</div>
```
## getPrimary()
```js
menuFinder.getPrimary()
```
### 描述
获取主菜单。
### 参数
### 返回值
[#MenuVo](#menuvo)
### 示例
```html
<div th:with="menu = ${menuFinder.getPrimary()}">
<ul th:with="menuItems = ${menu.menuItems}">
<li th:each="menuItem : ${menuItems}">
<a th:href="@{${menuItem.status.href}}" th:text="${menuItem.status.displayName}"></a>
</li>
</ul>
</div>
```
## 类型定义
### MenuVo
<MenuVo />
### MenuItemVo
<MenuItemVo />

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

@ -1,430 +0,0 @@
---
title: 文章
description: 文章 - PostFinder
---
import CategoryVo from "../vo/CategoryVo.md";
import TagVo from "../vo/TagVo.md";
import PostVo from "../vo/PostVo.md";
import ContentVo from "../vo/ContentVo.md"
import Contributor from "../vo/Contributor.md"
import ListedPostVo from "../vo/ListedPostVo.md"
## getByName(postName)
```js
postFinder.getByName(postName);
```
### 描述
根据 `metadata.name` 获取文章。
### 参数
1. `postName:string` - 文章的唯一标识 `metadata.name`
### 返回值
[#PostVo](#postvo)
### 示例
```html
<div th:with="post = ${postFinder.getByName('post-foo')}">
<a th:href="@{${post.status.permalink}}" th:text="${post.spec.title}"></a>
</div>
```
## content(postName)
```js
postFinder.content(postName);
```
### 描述
根据文章的 `metadata.name` 单独获取文章内容。
### 参数
1. `postName:string` - 文章的唯一标识 `metadata.name`
### 返回值
[#ContentVo](#contentvo)
### 示例
```html
<div th:with="content = ${postFinder.content('post-foo')}">
<div th:utext="${content.content}"></div>
</div>
```
## cursor(postName)
```js
postFinder.cursor(postName);
```
### 描述
根据文章的 `metadata.name` 获取相邻的文章(上一篇 / 下一篇)。
### 参数
1. `postName:string` - 文章的唯一标识 `metadata.name`
### 返回值
[#NavigationPostVo](#navigationpostvo)
### 示例
```html title="/templates/post.html"
<div th:with="postCursor = ${postFinder.cursor(post.metadata.name)}">
<a
th:if="${postCursor.hasPrevious()}"
th:href="@{${postCursor.previous.status.permalink}}"
>
<span th:text="${postCursor.previous.spec.title}"></span>
</a>
<a
th:if="${postCursor.hasNext()}"
th:href="@{${postCursor.next.status.permalink}}"
>
<span th:text="${postCursor.next.spec.title}"></span>
</a>
</div>
```
## listAll()
```js
postFinder.listAll();
```
### 描述
获取所有文章。
### 参数
### 返回值
List<[#ListedPostVo](#listedpostvo)>
### 示例
```html
<ul th:with="posts = ${postFinder.listAll()}">
<li th:each="post : ${posts}">
<a th:href="@{${post.status.permalink}}" th:text="${post.spec.title}"></a>
</li>
</ul>
```
## list(page,size)
```js
postFinder.list(page, size);
```
### 描述
根据分页参数获取文章列表。
### 参数
1. `page:int` - 分页页码,从 1 开始
2. `size:int` - 分页条数
### 返回值
[#ListResult<ListedPostVo\>](#listresultlistedpostvo)
### 示例
```html
<ul th:with="posts = ${postFinder.list(1,10)}">
<li th:each="post : ${posts.items}">
<a th:href="@{${post.status.permalink}}" th:text="${post.spec.title}"></a>
</li>
</ul>
```
## listByCategory(page,size,categoryName)
```js
postFinder.listByCategory(page, size, categoryName);
```
### 描述
根据分类标识和分页参数获取文章列表。
### 参数
1. `page:int` - 分页页码,从 1 开始
2. `size:int` - 分页条数
3. `categoryName:string` - 文章分类唯一标识 `metadata.name`
### 返回值
[#ListResult<ListedPostVo\>](#listresultlistedpostvo)
### 示例
```html
<ul th:with="posts = ${postFinder.listByCategory(1,10,'category-foo')}">
<li th:each="post : ${posts.items}">
<a th:href="@{${post.status.permalink}}" th:text="${post.spec.title}"></a>
</li>
</ul>
```
## listByTag(page,size,tag)
```js
postFinder.listByTag(page, size, tag);
```
### 描述
根据标签标识和分页参数获取文章列表。
### 参数
1. `page:int` - 分页页码,从 1 开始
2. `size:int` - 分页条数
3. `tag:string` - 文章分类唯一标识 `metadata.name`
### 返回值
[#ListResult<ListedPostVo\>](#listresultlistedpostvo)
### 示例
```html
<ul th:with="posts = ${postFinder.listByTag(1,10,'tag-foo')}">
<li th:each="post : ${posts.items}">
<a th:href="@{${post.status.permalink}}" th:text="${post.spec.title}"></a>
</li>
</ul>
```
## archives(page,size)
```js
postFinder.archives(page, size);
```
### 描述
根据分页参数获取文章归档列表。
### 参数
1. `page:int` - 分页页码,从 1 开始
2. `size:int` - 分页条数
### 返回值
[#ListResult<PostArchiveVo\>](#listresultpostarchivevo)
### 示例
```html
<th:block th:with="archives = ${postFinder.archives(1,10)}">
<th:block th:each="archive : ${archives.items}">
<h1 th:text="${archive.year}"></h1>
<ul>
<th:block th:each="month : ${archive.months}">
<li th:each="post : ${month.posts}">
<a th:href="@{${post.status.permalink}}" th:text="${post.spec.title}">
</a>
</li>
</th:block>
</ul>
</th:block>
</th:block>
```
## archives(page,size,year)
```js
postFinder.archives(page, size, year);
```
### 描述
根据年份和分页参数获取文章归档列表。
### 参数
1. `page:int` - 分页页码,从 1 开始
2. `size:int` - 分页条数
3. `year:string` - 年份
### 返回值
[#ListResult<PostArchiveVo\>](#listresultpostarchivevo)
### 示例
```html
<th:block th:with="archives = ${postFinder.archives(1,10,'2022')}">
<th:block th:each="archive : ${archives.items}">
<h1 th:text="${archive.year}"></h1>
<ul>
<th:block th:each="month : ${archive.months}">
<li th:each="post : ${month.posts}">
<a th:href="@{${post.status.permalink}}" th:text="${post.spec.title}">
</a>
</li>
</th:block>
</ul>
</th:block>
</th:block>
```
## archives(page,size,year,month)
```js
postFinder.archives(page, size, year, month);
```
### 描述
根据年月和分页参数获取文章归档列表。
### 参数
1. `page:int` - 分页页码,从 1 开始
2. `size:int` - 分页条数
3. `year:string` - 年份
4. `month:string` - 月份
### 返回值
[#ListResult<PostArchiveVo\>](#listresultpostarchivevo)
### 示例
```html
<th:block th:with="archives = ${postFinder.archives(1,10,'2022','11')}">
<th:block th:each="archive : ${archives.items}">
<h1 th:text="${archive.year}"></h1>
<ul>
<th:block th:each="month : ${archive.months}">
<li th:each="post : ${month.posts}">
<a th:href="@{${post.status.permalink}}" th:text="${post.spec.title}">
</a>
</li>
</th:block>
</ul>
</th:block>
</th:block>
```
## 类型定义
### CategoryVo
<CategoryVo />
### TagVo
<TagVo />
### Contributor
<Contributor />
### PostVo
<PostVo />
- [#CategoryVo](#categoryvo)
- [#TagVo](#tagvo)
- [#Contributor](#contributor)
- [#ContentVo](#contentvo)
### ContentVo
<ContentVo />
### NavigationPostVo
```json title="NavigationPostVo"
{
"previous": "#PostVo", // 上一篇文章
"current": "#PostVo", // 当前文章
"next": "#PostVo" // 下一篇文章
}
```
- [#PostVo](#postvo)
### ListedPostVo
<ListedPostVo />
- [#CategoryVo](#categoryvo)
- [#TagVo](#tagvo)
- [#Contributor](#contributor)
### ListResult<ListedPostVo\>
```json title="ListResult<ListedPostVo>"
{
"page": 0, // 当前页码
"size": 0, // 每页条数
"total": 0, // 总条数
"items": "List<#ListedPostVo>", // 文章列表数据
"first": true, // 是否为第一页
"last": true, // 是否为最后一页
"hasNext": true, // 是否有下一页
"hasPrevious": true, // 是否有上一页
"totalPages": 0 // 总页数
}
```
- [#ListedPostVo](#listedpostvo)
### PostArchiveVo
```json title="PostArchiveVo"
{
"year": "string",
"months": [
{
"month": "string",
"posts": "#ListedPostVo"
}
]
}
```
- [#ListedPostVo](#listedpostvo)
### ListResult<PostArchiveVo\>
```json title="ListResult<PostArchiveVo>"
{
"page": 0, // 当前页码
"size": 0, // 每页条数
"total": 0, // 总条数
"items": "List<#PostArchiveVo>", // 文章归档数据
"first": true, // 是否为第一页
"last": true, // 是否为最后一页
"hasNext": true, // 是否有下一页
"hasPrevious": true, // 是否有上一页
"totalPages": 0 // 总页数
}
```
- [#PostArchiveVo](#postarchivevo)

@ -1,131 +0,0 @@
---
title: 独立页面
description: 独立页面 - SinglePageFinder
---
import SinglePageVo from "../vo/SinglePageVo.md"
import ListedSinglePageVo from "../vo/ListedSinglePageVo.md"
import Contributor from "../vo/Contributor.md"
import ContentVo from "../vo/ContentVo.md"
## getByName(pageName)
```js
singlePageFinder.getByName(pageName)
```
### 描述
根据 `metadata.name` 获取独立页面。
### 参数
1. `pageName:string` - 独立页面的唯一标识 `metadata.name`
### 返回值
[#SinglePageVo](#singlepagevo)
### 示例
```html
<div th:with="singlePage = ${singlePageFinder.getByName('page-foo')}">
<a th:href="@{${singlePage.status.permalink}}" th:text="${singlePage.spec.title}"></a>
</div>
```
## content(pageName)
```js
singlePageFinder.content(pageName)
```
### 描述
根据独立页面的 `metadata.name` 单独获取独立页面内容。
### 参数
1. `pageName:string` - 独立页面的唯一标识 `metadata.name`
### 返回值
[#ContentVo](#contentvo)
### 示例
```html
<div th:with="content = ${singlePageFinder.content('page-foo')}">
<div th:utext="${content.content}"></div>
</div>
```
## list(page,size)
```js
singlePageFinder.list(page,size)
```
### 描述
根据分页参数获取独立页面列表。
### 参数
1. `page:int` - 分页页码,从 1 开始
2. `size:int` - 分页条数
### 返回值
[#ListResult<ListedSinglePageVo\>](#listresultlistedsinglepagevo)
### 示例
```html
<ul th:with="singlePages = ${singlePageFinder.list(1,10)}">
<li th:each="singlePage : ${singlePages.items}">
<a th:href="@{${singlePage.status.permalink}}" th:text="${singlePage.spec.title}"></a>
</li>
</ul>
```
## 类型定义
### SinglePageVo
<SinglePageVo />
- [#Contributor](#contributor)
- [#ContentVo](#contentvo)
### ListedSinglePageVo
- [#Contributor](#contributor)
<ListedSinglePageVo />
### ListResult<ListedSinglePageVo\>
```json title="ListResult<ListedSinglePageVo>"
{
"page": 0, // 当前页码
"size": 0, // 每页条数
"total": 0, // 总条数
"items": "List<#ListedSinglePageVo>", // 自定义页面列表数据
"first": true, // 是否为第一页
"last": true, // 是否为最后一页
"hasNext": true, // 是否有下一页
"hasPrevious": true, // 是否有上一页
"totalPages": 0 // 总页数
}
```
- [#ListedSinglePageVo](#listedsinglepagevo)
### ContentVo
<ContentVo />
### Contributor
<Contributor />

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

@ -1,139 +0,0 @@
---
title: 文章标签
description: 文章标签 - TagFinder
---
import TagVo from "../vo/TagVo.md"
## getByName(name)
```js
tagFinder.getByName(name)
```
### 描述
根据 `metadata.name` 获取标签。
### 参数
1. `name:string` - 标签的唯一标识 `metadata.name`
### 返回值
[#TagVo](#tagvo)
### 示例
```html
<div th:with="tag = ${tagFinder.getByName('tag-foo')}">
<a th:href="@{${tag.status.permalink}}" th:text="${tag.spec.displayName}"></a>
</div>
```
## getByNames(names)
```js
tagFinder.getByNames(names)
```
### 描述
根据一组 `metadata.name` 获取标签。
### 参数
1. `names:List<string>` - 标签的唯一标识 `metadata.name` 的集合。
### 返回值
List<[#TagVo](#tagvo)>
### 示例
```html
<div th:with="tags = ${tagFinder.getByNames(['tag-foo', 'tag-bar'])}">
<a th:each="tag : ${tags}" th:href="@{${tag.status.permalink}}" th:text="${tag.spec.displayName}"></a>
</div>
```
## list(page,size)
```js
tagFinder.list(page,size)
```
### 描述
根据分页参数获取标签列表。
### 参数
1. `page:int` - 分页页码,从 1 开始
2. `size:int` - 分页条数
### 返回值
[#ListResult<TagVo\>](#listresulttagvo)
### 示例
```html
<ul th:with="tags = ${tagFinder.list(1,10)}">
<li th:each="tag : ${tags.items}">
<a th:href="@{${tag.status.permalink}}" th:text="${tag.spec.displayName}"></a>
</li>
</ul>
```
## listAll()
```js
tagFinder.listAll()
```
### 描述
获取所有文章标签。
### 参数
### 返回值
List<[#TagVo](#tagvo)>
### 示例
```html
<ul th:with="tags = ${tagFinder.listAll()}">
<li th:each="tag : ${tags}">
<a th:href="@{${tag.status.permalink}}" th:text="${tag.spec.displayName}"></a>
</li>
</ul>
```
## 类型定义
### TagVo
<TagVo />
### ListResult<TagVo\>
```json title="ListResult<TagVo>"
{
"page": 0, // 当前页码
"size": 0, // 每页条数
"total": 0, // 总条数
"items": "List<#TagVo>", // 标签列表数据
"first": true, // 是否为第一页
"last": true, // 是否为最后一页
"hasNext": true, // 是否有下一页
"hasPrevious": true, // 是否有上一页
"totalPages": 0 // 总页数
}
```
- [#TagVo](#tagvo)

@ -1,119 +0,0 @@
---
title: 主题
description: 主题 - ThemeFinder
---
## activation()
```js
themeFinder.activation()
```
### 描述
获取当前激活的主题。
### 参数
### 返回值
[#ThemeVo](#themevo)
### 示例
```html
<div th:with="theme = ${themeFinder.activation()}">
<h1 th:text="${theme.spec.displayName}"></h1>
<p th:text="${theme.spec.version}"></p>
</div>
```
## getByName(themeName)
```js
themeFinder.getByName(themeName)
```
### 描述
根据主题的唯一标识 `metadata.name` 获取主题。
### 参数
- `themeName:string` - 主题名称
### 返回值
[#ThemeVo](#themevo)
### 示例
```html
<div th:with="theme = ${themeFinder.getByName('theme-foo')}">
<h1 th:text="${theme.spec.displayName}"></h1>
<p th:text="${theme.spec.version}"></p>
</div>
```
## 类型定义
### ThemeVo
```json title="ThemeVo"
{
"metadata": {
"name": "string", // 唯一标识
"labels": {
"additionalProp1": "string"
},
"annotations": {
"additionalProp1": "string"
},
"creationTimestamp": "2022-11-20T15:27:15.036Z", // 创建时间
},
"spec": {
"displayName": "string", // 显示名称
"author": {
"name": "string", // 作者名称
"website": "string" // 作者网站
},
"description": "string", // 描述
"logo": "string", // Logo
"website": "string", // 网站
"repo": "string", // 仓库地址
"version": "string", // 版本
"require": "string", // 依赖 Halo 的版本
"settingName": "string", // 表单定义的名称,即 Setting 资源的 metadata.name
"configMapName": "string", // 设置项存储的名称,即 ConfigMap 资源的 metadata.name
"customTemplates": {
"post": [
{
"name": "string",
"description": "string",
"screenshot": "string",
"file": "string"
}
],
"category": [
{
"name": "string",
"description": "string",
"screenshot": "string",
"file": "string"
}
],
"page": [
{
"name": "string",
"description": "string",
"screenshot": "string",
"file": "string"
}
]
}
},
"config": {}
}
```

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

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

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

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

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

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

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

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

@ -1,108 +0,0 @@
---
title: 文章归档
description: archives.html - /archives
---
import CategoryVo from "../vo/CategoryVo.md";
import TagVo from "../vo/TagVo.md";
import Contributor from "../vo/Contributor.md";
import ListedPostVo from "../vo/ListedPostVo.md";
## 路由信息
- 模板路径:`/templates/archives.html`
- 访问路径
- `/archives`
- `/archives/:year`
- `/archives/:year/:month`
## 变量
### archives
#### 变量类型
[#UrlContextListResult<PostArchiveVo\>](#urlcontextlistresultpostarchivevo)
#### 示例
```html title="/templates/archives.html"
<th:block th:each="archive : ${archives.items}">
<h1 th:text="${archive.year}"></h1>
<ul>
<th:block th:each="month : ${archive.months}">
<li th:each="post : ${month.posts}">
<a th:href="@{${post.status.permalink}}" th:text="${post.spec.title}">
</a>
</li>
</th:block>
</ul>
</th:block>
<div th:if="${archives.hasPrevious() || archives.hasNext()}">
<a th:href="@{${archives.prevUrl}}">
<span>上一页</span>
</a>
<span th:text="${archives.page} +' / '+ ${archives.total}"></span>
<a th:href="@{${archives.nextUrl}}">
<span>下一页</span>
</a>
</div>
```
## 类型定义
### CategoryVo
<CategoryVo />
### TagVo
<TagVo />
### Contributor
<Contributor />
### ListedPostVo
<ListedPostVo />
- [#CategoryVo](#categoryvo)
- [#TagVo](#tagvo)
- [#Contributor](#contributor)
### PostArchiveVo
```json title="PostArchiveVo"
{
"year": "string", // 年份
"months": [ // 按月的文章集合
{
"month": "string", // 月份
"posts": "List<#ListedPostVo>" // 文章列表数据
}
]
}
```
- [#ListedPostVo](#listedpostvo)
### UrlContextListResult<PostArchiveVo\>
```json title="UrlContextListResult<PostArchiveVo>"
{
"page": 0, // 当前页码
"size": 0, // 每页条数
"total": 0, // 总条数
"items": "List<#PostArchiveVo>", // 文章归档数据
"first": true, // 是否为第一页
"last": true, // 是否为最后一页
"hasNext": true, // 是否有下一页
"hasPrevious": true, // 是否有上一页
"totalPages": 0, // 总页数
"nextUrl": "string", // 下一页链接
"prevUrl": "string" // 上一页链接
}
```
- [#PostArchiveVo](#postarchivevo)

@ -1,54 +0,0 @@
---
title: 文章分类集合
description: categories.html - /categories
---
import CategoryTreeVo from "../vo/CategoryTreeVo.md"
## 路由信息
- 模板路径:`/templates/categories.html`
- 访问路径:`/categories`
## 变量
### categories
#### 变量类型
List<[#CategoryTreeVo](#categorytreevo)>
#### 示例
```html title="/templates/categories.html"
<ul>
<li th:replace="~{modules/category-tree :: single(categories=${categories})}" />
</ul>
```
```html title="/templates/category-tree.html"
<ul th:fragment="next (categories)">
<li th:fragment="single (categories)" th:each="category : ${categories}">
<a th:href="@{${category.status.permalink}}">
<span th:text="${category.spec.displayName}"> </span>
</a>
<th:block th:if="${not #lists.isEmpty(category.children)}">
<th:block th:replace="~{modules/category-tree :: next (categories=${category.children})}"></th:block>
</th:block>
</li>
</ul>
```
### _templateId
#### 变量值
`categories`
## 类型定义
### CategoryTreeVo
<CategoryTreeVo />
- [#CategoryTreeVo](#categorytreevo)

@ -1,105 +0,0 @@
---
title: 分类归档
description: category.html - /categories/:slug
---
import CategoryVo from "../vo/CategoryVo.md"
import TagVo from "../vo/TagVo.md"
import Contributor from "../vo/Contributor.md";
import ListedPostVo from "../vo/ListedPostVo.md"
## 路由信息
- 模板路径:`/templates/category.html`
- 访问路径:`/categories/:slug`
## 变量
### category
#### 变量类型
[#CategoryVo](#categoryvo)
### posts
#### 变量类型
[#UrlContextListResult<ListedPostVo\>](#urlcontextlistresultlistedpostvo)
#### 示例
```html title="/templates/category.html"
<div>
<h1 th:text="${category.spec.displayName}"></h1>
<ul>
<li th:each="post : ${posts.items}">
<a
th:text="${post.spec.title}"
th:href="${post.status.permalink}"
></a>
</li>
</ul>
<div th:if="${posts.hasPrevious() || posts.hasNext()}">
<a
th:href="@{${posts.prevUrl}}"
>
<span>上一页</span>
</a>
<span th:text="${posts.page} +' / '+ ${posts.total}"></span>
<a
th:href="@{${posts.nextUrl}}"
>
<span>下一页</span>
</a>
</div>
</div>
```
### _templateId
#### 变量值
`category`
## 类型定义
### CategoryVo
<CategoryVo />
### TagVo
<TagVo />
### Contributor
<Contributor />
### ListedPostVo
<ListedPostVo />
- [#CategoryVo](#categoryvo)
- [#TagVo](#tagvo)
- [#Contributor](#contributor)
### UrlContextListResult<ListedPostVo\>
```json title="UrlContextListResult<ListedPostVo>"
{
"page": 0, // 当前页码
"size": 0, // 每页条数
"total": 0, // 总条数
"items": "List<#ListedPostVo>", // 文章列表数据
"first": true, // 是否为第一页
"last": true, // 是否为最后一页
"hasNext": true, // 是否有下一页
"hasPrevious": true, // 是否有上一页
"totalPages": 0, // 总页数
"nextUrl": "string", // 下一页链接
"prevUrl": "string" // 上一页链接
}
```
- [#ListedPostVo](#listedpostvo)

@ -1,98 +0,0 @@
---
title: 首页
description: index.html - /
---
import CategoryVo from "../vo/CategoryVo.md"
import TagVo from "../vo/TagVo.md"
import Contributor from "../vo/Contributor.md";
import ListedPostVo from "../vo/ListedPostVo.md"
## 路由信息
- 模板路径:`/templates/index.html`
- 访问路径:`/`
## 变量
### posts
#### 变量类型
[#UrlContextListResult<ListedPostVo\>](#urlcontextlistresultlistedpostvo)
#### 示例
```html title="/templates/index.html"
<div>
<ul>
<li th:each="post : ${posts.items}">
<a
th:text="${post.spec.title}"
th:href="${post.status.permalink}"
></a>
</li>
</ul>
<div th:if="${posts.hasPrevious() || posts.hasNext()}">
<a
th:href="@{${posts.prevUrl}}"
>
<span>上一页</span>
</a>
<span th:text="${posts.page} +' / '+ ${posts.total}"></span>
<a
th:href="@{${posts.nextUrl}}"
>
<span>下一页</span>
</a>
</div>
</div>
```
### _templateId
#### 变量值
`index`
## 类型定义
### CategoryVo
<CategoryVo />
### TagVo
<TagVo />
### Contributor
<Contributor />
### ListedPostVo
<ListedPostVo />
- [#CategoryVo](#categoryvo)
- [#TagVo](#tagvo)
- [#Contributor](#contributor)
### UrlContextListResult<ListedPostVo\>
```json title="UrlContextListResult<ListedPostVo>"
{
"page": 0, // 当前页码
"size": 0, // 每页条数
"total": 0, // 总条数
"items": "List<#ListedPostVo>", // 文章列表数据
"first": true, // 是否为第一页
"last": true, // 是否为最后一页
"hasNext": true, // 是否有下一页
"hasPrevious": true, // 是否有上一页
"totalPages": 0, // 总页数
"nextUrl": "string", // 下一页链接
"prevUrl": "string" // 上一页链接
}
```
- [#ListedPostVo](#listedpostvo)

@ -1,53 +0,0 @@
---
title: 独立页面
description: page.html - /:slug
---
import SinglePageVo from "../vo/SinglePageVo.md"
import Contributor from "../vo/Contributor.md"
import ContentVo from "../vo/ContentVo.md"
## 路由信息
- 模板路径:`/templates/page.html`
- 访问路径:`/:slug`
## 变量
### singlePage
#### 变量类型
[#SinglePageVo](#singlepagevo)
#### 示例
```html title="/templates/page.html"
<article>
<h1 th:text="${singlePage.spec.title}"></h1>
<div th:utext="${singlePage.content.content}"> </div>
</article>
```
### _templateId
#### 变量值
`page`
## 类型定义
### SinglePageVo
<SinglePageVo />
- [#ContentVo](#contentvo)
- [#Contributor](#contributor)
### ContentVo
<ContentVo />
### Contributor
<Contributor />

@ -1,65 +0,0 @@
---
title: 文章
description: post.html - /archives/:slug
---
import CategoryVo from "../vo/CategoryVo.md"
import TagVo from "../vo/TagVo.md"
import ContentVo from "../vo/ContentVo.md"
import Contributor from "../vo/Contributor.md"
import PostVo from "../vo/PostVo.md"
## 路由信息
- 模板路径:`/templates/post.html`
- 访问路径:`/archives/:slug`
## 变量
### post
#### 变量类型
[#PostVo](#postvo)
#### 示例
```html title="/templates/post.html"
<article>
<h1 th:text="${post.spec.title}"></h1>
<div th:utext="${post.content.content}"> </div>
</article>
```
### _templateId
#### 变量值
`post`
## 类型定义
### CategoryVo
<CategoryVo />
### TagVo
<TagVo />
### Contributor
<Contributor />
### ContentVo
<ContentVo />
### PostVo
<PostVo />
- [#CategoryVo](#categoryvo)
- [#TagVo](#tagvo)
- [#Contributor](#contributor)
- [#ContentVo](#contentvo)

@ -1,105 +0,0 @@
---
title: 标签归档
description: tag.html - /tags/:slug
---
import CategoryVo from "../vo/CategoryVo.md"
import TagVo from "../vo/TagVo.md"
import Contributor from "../vo/Contributor.md";
import ListedPostVo from "../vo/ListedPostVo.md"
## 路由信息
- 模板路径:`/templates/tag.html`
- 访问路径:`/tags/:slug`
## 变量
### tag
#### 变量类型
[#TagVo](#tagvo)
### posts
#### 变量类型
[#UrlContextListResult<ListedPostVo\>](#urlcontextlistresultlistedpostvo)
#### 示例
```html title="/templates/tag.html"
<div>
<h1 th:text="${tag.spec.displayName}"></h1>
<ul>
<li th:each="post : ${posts.items}">
<a
th:text="${post.spec.title}"
th:href="${post.status.permalink}"
></a>
</li>
</ul>
<div th:if="${posts.hasPrevious() || posts.hasNext()}">
<a
th:href="@{${posts.prevUrl}}"
>
<span>上一页</span>
</a>
<span th:text="${posts.page} +' / '+ ${posts.total}"></span>
<a
th:href="@{${posts.nextUrl}}"
>
<span>下一页</span>
</a>
</div>
</div>
```
### _templateId
#### 变量值
`tag`
## 类型定义
### CategoryVo
<CategoryVo />
### TagVo
<TagVo />
### Contributor
<Contributor />
### ListedPostVo
<ListedPostVo />
- [#CategoryVo](#categoryvo)
- [#TagVo](#tagvo)
- [#Contributor](#contributor)
### UrlContextListResult<ListedPostVo\>
```json title="UrlContextListResult<ListedPostVo>"
{
"page": 0, // 当前页码
"size": 0, // 每页条数
"total": 0, // 总条数
"items": "List<#ListedPostVo>", // 文章列表数据
"first": true, // 是否为第一页
"last": true, // 是否为最后一页
"hasNext": true, // 是否有下一页
"hasPrevious": true, // 是否有上一页
"totalPages": 0, // 总页数
"nextUrl": "string", // 下一页链接
"prevUrl": "string" // 上一页链接
}
```
- [#ListedPostVo](#listedpostvo)

@ -1,39 +0,0 @@
---
title: 文章标签集合
description: tags.html - /tags
---
import TagVo from '../vo/TagVo.md'
## 路由信息
- 模板路径:`/templates/tags.html`
- 访问路径:`/tags`
## 变量
### tags
#### 变量类型
List<[#TagVo](#tagvo)>
#### 示例
```html title="/templates/tags.html"
<ul>
<li th:each="tag : ${tags}" th:text="${tag.spec.displayName}" th:href="${tag.status.permalink}" />
</ul>
```
### _templateId
#### 变量值
`tags`
## 类型定义
### TagVo
<TagVo />

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

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

@ -1,53 +0,0 @@
```json title="CommentVo"
{
"metadata": {
"name": "string", // 唯一标识
"labels": {
"additionalProp1": "string"
},
"annotations": {
"additionalProp1": "string"
},
"creationTimestamp": "2022-11-20T12:16:19.788Z" // 创建时间
},
"spec": {
"raw": "string", // 原始文本,一般用于给编辑器使用
"content": "string", // 最终渲染的文本
"owner": { // 创建者关联
"kind": "string",
"name": "string",
"displayName": "string",
"annotations": {
"additionalProp1": "string"
}
},
"userAgent": "string", // 评论者 UserAgent 信息
"ipAddress": "string", // 评论者 IP 地址
"priority": 0, // 排序字段
"top": false, // 是否置顶
"allowNotification": true, // 是否允许通知
"approved": false,
"hidden": false,
"subjectRef": { // 引用关联,比如文章、自定义页面
"group": "string",
"version": "string",
"kind": "string",
"name": "string"
},
"lastReadTime": "2022-11-20T12:16:19.788Z"
},
"status": {
"lastReplyTime": "2022-11-20T12:16:19.788Z",
"replyCount": 0, // 回复数量
"unreadReplyCount": 0,
"hasNewReply": true // 是否有新回复
},
"owner": { // 创建者信息
"kind": "string",
"name": "string",
"displayName": "string",
"avatar": "string",
"email": "string"
}
}
```

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

@ -1,8 +0,0 @@
```json title="Contributor"
{
"name": "string", // 用户名
"displayName": "string", // 显示名称
"avatar": "string", // 头像
"bio": "string" // 描述
}
```

@ -1,64 +0,0 @@
```json title="ListedPostVo"
{
"metadata": {
"name": "string", // 唯一标识
"labels": {
"additionalProp1": "string"
},
"annotations": {
"additionalProp1": "string"
},
"creationTimestamp": "2022-11-20T13:06:38.505Z", // 创建时间
},
"spec": {
"title": "string", // 标题
"slug": "string", // 别名,通常用于生成 status.permalink
"releaseSnapshot": "string",
"headSnapshot": "string",
"baseSnapshot": "string",
"owner": "string", // 创建者名称,即 Contributor 的 metadata.name非显示名称
"template": "string", // 自定义渲染模板
"cover": "string", // 封面图
"deleted": false,
"publish": false,
"publishTime": "2022-11-20T13:06:38.505Z", // 发布时间
"pinned": false, // 是否置顶
"allowComment": true, // 是否允许评论
"visible": "PUBLIC",
"priority": 0,
"excerpt": {
"autoGenerate": true, // 是否自动生成摘要
"raw": "string" // 摘要内容
},
"categories": [ // 分类的名称集合,即 Category 的 metadata.name 的集合
"string"
],
"tags": [ // 标签的名称集合,即 Tag 的 metadata.name 的集合
"string"
],
"htmlMetas": [
{
"additionalProp1": "string"
}
]
},
"status": {
"permalink": "string", // 固定链接
"excerpt": "string", // 最终生成的摘要
"inProgress": true,
"commentsCount": 0, // 评论数
"contributors": [ // 贡献者名称Contributor 的 metadata.name 的集合
"string"
]
},
"categories": "List<#CategoryVo>", // 分类的集合
"tags": "List<#TagVo>", // 标签的集合
"contributors": "List<#Contributor>", // 贡献者的集合
"owner": "#Contributor", // 创建者
"stats": {
"visit": 0, // 访问数量
"upvote": 0, // 点赞数量
"comment": 0 // 评论数量
}
}
```

@ -1,56 +0,0 @@
```json title="ListedSinglePageVo"
{
"metadata": {
"name": "string", // 唯一标识
"labels": {
"additionalProp1": "string"
},
"annotations": {
"additionalProp1": "string"
},
"creationTimestamp": "2022-11-20T14:31:00.876Z" // 创建时间
},
"spec": {
"title": "string", // 标题
"slug": "string", // 别名,通常用于生成 status.permalink
"releaseSnapshot": "string",
"headSnapshot": "string",
"baseSnapshot": "string",
"owner": "string", // 创建者名称,即 Contributor 的 metadata.name非显示名称
"template": "string", // 自定义渲染模板
"cover": "string", // 封面图
"deleted": false,
"publish": false,
"publishTime": "2022-11-20T14:31:00.876Z", // 发布时间
"pinned": false, // 是否置顶
"allowComment": true, // 是否允许评论
"visible": "PUBLIC",
"priority": 0,
"excerpt": {
"autoGenerate": true, // 是否自动生成摘要
"raw": "string" // 摘要内容
},
"htmlMetas": [
{
"additionalProp1": "string"
}
]
},
"status": {
"permalink": "string", // 固定链接
"excerpt": "string", // 最终生成的摘要
"inProgress": true,
"commentsCount": 0, // 评论数
"contributors": [ // 贡献者名称Contributor 的 metadata.name 的集合
"string"
]
},
"stats": {
"visit": 0, // 访问数量
"upvote": 0, // 点赞数量
"comment": 0 // 评论数量
},
"contributors": "List<#Contributor>", // 贡献者的集合
"owner": "#Contributor" // 创建者
}
```

@ -1,34 +0,0 @@
```json title="MenuItemVo"
{
"metadata": {
"name": "string", // 唯一标识
"labels": {
"additionalProp1": "string"
},
"annotations": {
"additionalProp1": "string"
},
"creationTimestamp": "2022-11-20T14:44:58.984Z", // 创建时间
},
"spec": {
"displayName": "string", // 显示名称,但是不要直接使用这个字段进行显示,最终字段为 status.displayName
"href": "string", // 链接,同样不要直接使用这个字段,最终字段为 status.href
"priority": 0, // 排序字段
"children": [ // 下级菜单项,菜单项的 metadata.name 集合
"string"
],
"targetRef": { // 与其他资源比如文章的关联,一般无需直接使用
"group": "string",
"version": "string",
"kind": "string",
"name": "string"
}
},
"status": {
"displayName": "string", // 显示名称
"href": "string" // 链接
},
"children": "List<#MenuItemVo>", // 下级菜单项MenuItemVo 的集合
"parentName": "string",
}
```

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

@ -1,65 +0,0 @@
```json title="PostVo"
{
"metadata": {
"name": "string", // 唯一标识
"labels": {
"additionalProp1": "string"
},
"annotations": {
"additionalProp1": "string"
},
"creationTimestamp": "2022-11-20T12:45:43.888Z", // 创建时间
},
"spec": {
"title": "string", // 标题
"slug": "string", // 别名,通常用于生成 status.permalink
"releaseSnapshot": "string",
"headSnapshot": "string",
"baseSnapshot": "string",
"owner": "string", // 创建者名称,即 Contributor 的 metadata.name非显示名称
"template": "string", // 自定义渲染模板
"cover": "string", // 封面图
"deleted": false,
"publish": false,
"publishTime": "2022-11-20T13:06:38.505Z", // 发布时间
"pinned": false, // 是否置顶
"allowComment": true, // 是否允许评论
"visible": "PUBLIC",
"priority": 0,
"excerpt": {
"autoGenerate": true, // 是否自动生成摘要
"raw": "string" // 摘要内容
},
"categories": [ // 分类的名称集合,即 Category 的 metadata.name 的集合
"string"
],
"tags": [ // 标签的名称集合,即 Tag 的 metadata.name 的集合
"string"
],
"htmlMetas": [
{
"additionalProp1": "string"
}
]
},
"status": {
"permalink": "string", // 固定链接
"excerpt": "string", // 最终生成的摘要
"inProgress": true,
"commentsCount": 0, // 评论数
"contributors": [ // 贡献者名称Contributor 的 metadata.name 的集合
"string"
]
},
"categories": "List<#CategoryVo>", // 分类的集合
"tags": "List<#TagVo>", // 标签的集合
"contributors": "List<#Contributor>", // 贡献者的集合
"owner": "#Contributor", // 创建者
"stats": {
"visit": 0, // 访问数量
"upvote": 0, // 点赞数量
"comment": 0 // 评论数量
},
"content": "#ContentVo" // 内容
}
```

@ -1,42 +0,0 @@
```json title="ReplyVo"
{
"metadata": {
"name": "string", // 唯一标识
"labels": {
"additionalProp1": "string"
},
"annotations": {
"additionalProp1": "string"
},
"creationTimestamp": "2022-11-20T12:25:32.357Z" // 创建时间
},
"spec": {
"raw": "string", // 原始文本,一般用于给编辑器使用
"content": "string", // 最终渲染的文本
"owner": { // 创建者关联
"kind": "string",
"name": "string",
"displayName": "string",
"annotations": {
"additionalProp1": "string"
}
},
"userAgent": "string", // 评论者 UserAgent 信息
"ipAddress": "string", // 评论者 IP 地址
"priority": 0, // 排序字段
"top": false, // 是否置顶
"allowNotification": true, // 是否允许通知
"approved": false,
"hidden": false,
"commentName": "string", // 被回复的评论名称,即 Comment 的 metadata.name
"quoteReply": "string" // 被回复的回复名称,即 Reply 的 metadata.name
},
"owner": { // 创建者信息
"kind": "string",
"name": "string",
"displayName": "string",
"avatar": "string",
"email": "string"
}
}
```

@ -1,57 +0,0 @@
```json title="SinglePageVo"
{
"metadata": {
"name": "string", // 唯一标识
"labels": {
"additionalProp1": "string"
},
"annotations": {
"additionalProp1": "string"
},
"creationTimestamp": "2022-11-20T14:29:44.601Z", // 创建时间
},
"spec": {
"title": "string", // 标题
"slug": "string", // 别名,通常用于生成 status.permalink
"releaseSnapshot": "string",
"headSnapshot": "string",
"baseSnapshot": "string",
"owner": "string", // 创建者名称,即 Contributor 的 metadata.name非显示名称
"template": "string", // 自定义渲染模板
"cover": "string", // 封面图
"deleted": false,
"publish": false,
"publishTime": "2022-11-20T13:06:38.505Z", // 发布时间
"pinned": false, // 是否置顶
"allowComment": true, // 是否允许评论
"visible": "PUBLIC",
"priority": 0,
"excerpt": {
"autoGenerate": true, // 是否自动生成摘要
"raw": "string" // 摘要内容
},
"htmlMetas": [
{
"additionalProp1": "string"
}
]
},
"status": {
"permalink": "string", // 固定链接
"excerpt": "string", // 最终生成的摘要
"inProgress": true,
"commentsCount": 0, // 评论数
"contributors": [ // 贡献者名称Contributor 的 metadata.name 的集合
"string"
]
},
"stats": {
"visit": 0, // 访问数量
"upvote": 0, // 点赞数量
"comment": 0 // 评论数量
},
"contributors": "List<#Contributor>", // 贡献者的集合
"owner": "#Contributor", // 创建者
"content": "#ContentVo" // 内容
}
```

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

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

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

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

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

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

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

@ -1,164 +0,0 @@
---
title: 与 Nginx Proxy Manager 配合使用
description: 使用 Nginx Proxy Manager 管理 Halo 服务的反向代理
---
## Halo 部署
参见 [使用 Docker Compose 部署](../docker-compose)
:::info
`「反向代理」` 部分不进行操作,保证 Halo 服务运行无误即可。
:::
## 简介
顾名思义Nginx Proxy Manager 就是一个 Nginx 的代理管理器,它最大的特点是简单方便。
即使是没有 Nginx 基础的小伙伴,也能轻松地用它来完成反向代理的操作,而且因为自带面板,操作极其简单,非常适合配合 docker 搭建的应用使用。
Nginx Proxy Manager 后台还可以一键申请 SSL 证书,并且会自动续期,方便省心。
下面我们就来介绍如何用 Nginx Proxy Manger 来配合 Halo实现反向代理和 HTTPS 访问。
## 安装 Nginx Proxy Manager
> 说明:默认你的服务器已经安装了 Docker 和 Docker Compose如果你没有安装可以参考[使用 Docker Compose 部署](../docker-compose) 的环境搭建部分来进行安装。
点击下方链接进入 Nginx Proxy Manager以下简称 NPM 官网:<https://nginxproxymanager.com/>
我们可以直接选择 [快速安装](https://nginxproxymanager.com/guide/#quick-setup)。
首先,我们创建一个文件夹来存放 NPM 的 `docker-compose.yml` 文件:
```bash
mkdir -p ~/data/docker_data/nginxproxymanager # 创建一个 npm 的文件夹
cd ~/data/docker_data/nginxproxymanager # 进入该文件夹
vi docker-compose.yml
```
在英文状态的输入法下,按下 `i`,左下角出现 `--INSERT--` 后,粘贴填入下面的内容:
```yaml
version: '3'
services:
app:
image: 'jc21/nginx-proxy-manager:latest'
restart: unless-stopped
ports:
- '80:80' # 不建议修改端口
- '81:81' # 可以把冒号左边的 81 端口修改成你服务器上没有被占用的端口
- '443:443' # 不建议修改端口
volumes:
- ./data:/data # 点号表示当前文件夹,冒号左边的意思是在当前文件夹下创建一个 data 目录,用于存放数据,如果不存在的话,会自动创建
- ./letsencrypt:/etc/letsencrypt # 点号表示当前文件夹,冒号左边的意思是在当前文件夹下创建一个 letsencrypt 目录,用于存放证书,如果不存在的话,会自动创建
```
> 注意:安装了 NPM 之后,就不需要再安装 Nginx 了,否则会端口冲突(不建议修改 NPM 的 80、443 端口)。如果你的服务器安装了宝塔面板,也可以和 NPM 一起使用,只要你到软件后台把宝塔安装的 Nginx 关闭或者卸载即可。
之后,同样在英文输入法下,按一下 `esc`,然后 `:wq` 保存退出。
启动 NPM
```bash
docker-compose up -d # -d 表示后台运行
docker compose up -d # 如果你用的是 docker-compose-plugin 的话,用这条命令
```
不出意外,此时你使用 [http://127.0.0.1:81](http://127.0.0.1:81/) 就可以访问 NPM 的网页端了。(注意把 `127.0.0.1` 替换成你实际服务器的 IP
:::info
1. 不知道服务器 IP可以直接在命令行输入curl ip.sb会显示当前服务器的 IP。
2. 遇到访问不了的情况,请再次检查在宝塔面板的防火墙和服务商的后台防火墙是否打开对应了端口。
:::
默认登陆的用户名:`admin@example.com` 密码:`changeme`
第一次登陆会提示更改用户名和密码,建议修改一个复杂一点的密码。
至此,我们已经完成了 Nginx Proxy Manager 的搭建,之后就可以用它给我们的 Halo 或者其他 Web 应用做反向代理了。
## 配置 Halo 的反向代理
首先我们登陆网页端之后,会弹出修改用户名和密码的对话框,我们根据自己的实际来修改自己的用户名和邮箱。
![Nginx Proxy Manager 1](/img/nginx-proxy-manager/Nginx-Proxy-Manager-1.png)
保存之后,会让我们修改密码(建议用一个复杂的密码)。
![Nginx Proxy Manager 2](/img/nginx-proxy-manager/Nginx-Proxy-Manager-2.png)
接着我们就可以来给 Halo 来添加一个反向代理了。
点击 `Proxy Hosts`
![Nginx Proxy Manager 3](/img/nginx-proxy-manager/Nginx-Proxy-Manager-3.png)
接着点击 `Add Proxy Host`,弹出如下对话框:
![Nginx Proxy Manager 4](/img/nginx-proxy-manager/Nginx-Proxy-Manager-4.png)
看起来都是英文,很复杂,但是其实很简单,我们只要用到其中的几个功能即可,这边稍微解释一下:
- `Domain Names` :填我们 Halo 网站的域名,首先记得做好 DNS 解析,把域名绑定到我们的服务器的 IP 上
- `Scheme` :默认 `http` 即可,除非你有自签名证书
- `Forward Hostname/IP` :填入服务器的 IP或者 Docker 容器内部的 IP如果 NPM 和 Halo 搭建在同一台服务器上的话)
- `Forward Port`:填入 Halo 映射出的端口,这边默认是 `8090`
- `Cache Assets` :缓存,可以选择打开
- `Block Common Exploits` 阻止常见的漏洞,可以选择打开
- `Websockets Support` WS 支持,可以选择打开
- `Access List` 这个是 NPM 自带的一个限制访问功能,这边我们不管,后续可以自行研究。
以下是一个样列:
![Nginx Proxy Manager 5](/img/nginx-proxy-manager/Nginx-Proxy-Manager-5.png)
因为样例的 NPM 和 Halo 搭建在同一台 VPS 上,所以这边的 IP图中填的是 `172.17.0.1`,为 Docker 容器内部的 IP 地址,
可以通过下面的命令查询:
```bash
ip addr show docker0
```
```bash {3}
4: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
link/ether 02:42:e4:a3:b5:b9 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
valid_lft forever preferred_lft forever
```
这边的 IP 是 `172.17.0.1`,填入这个 IP可以不用打开防火墙的 `8090` 端口。
当然,如果你的 NPM 和 Halo 不在同一台服务上,你需要在 IP 部分填入 **你的 Halo 所在的服务器的 IP**,并在服务商(部分服务商如腾讯、阿里)的后台打开 `8090` 端口。
## 一键申请 SSL 证书
接着我们来申请一张 SSL 证书,让我们的网站支持 `https` 访问。
![Nginx Proxy Manager 6](/img/nginx-proxy-manager/Nginx-Proxy-Manager-6.png)
![Nginx Proxy Manager 7](/img/nginx-proxy-manager/Nginx-Proxy-Manager-7.png)
如图所示,记得打开强制 SSL其他四个的功能请自行研究这边不多做讨论。
:::info
1. 申请证书需要你提前将域名解析到 NPM 所在的服务器的 IP 上;
2. 如果你使用的是国内的服务器,默认 `80``443` 端口是关闭的,你需要备案之后才能使用;
3. 如果你使用了 CloudFlare 的 DNS 服务,记得把小黄云关闭(即不开启 CDN
:::
不出意外,你将成功申请到 SSL 证书,证书会三个月自动续期。
再次点开配置,查看一下,将强制 SSL 打开。
![Nginx Proxy Manager 8](/img/nginx-proxy-manager/Nginx-Proxy-Manager-8.png)
至此,你已经成功完成了 Halo 的反向代理,快尝试使用域名访问一下看看吧!
> 同样的,举一反三,试试把你的 NPM 也用一个域名来反向代理一下吧。(小提示:你需要再解析一个域名(可以是二级域名)到 NPM 所在的服务器上,反代页面需要填的 IP 可以填 docker 容器内的 IP 也可以填服务器的 IP端口填 `81` 即可)

@ -1,17 +0,0 @@
| 变量名 | 描述 |
| ---------------------------------------------- | -------------------------------------------------------------------------------- |
| `SPRING_R2DBC_URL` | 数据库连接地址,详细可查阅下方的 `数据库配置` |
| `SPRING_R2DBC_USERNAME` | 数据库用户名 |
| `SPRING_R2DBC_PASSWORD` | 数据库密码 |
| `SPRING_SQL_INIT_PLATFORM` | 数据库平台名称,支持 `postgresql`、`mysql`、`h2`,需要与 `SPRING_R2DBC_URL` 对应 |
| `HALO_EXTERNAL_URL` | 外部访问链接,如果需要在公网访问,需要配置为实际访问地址 |
| `HALO_SECURITY_INITIALIZER_SUPERADMINUSERNAME` | 初始超级管理员用户名 |
| `HALO_SECURITY_INITIALIZER_SUPERADMINPASSWORD` | 初始超级管理员密码 |
数据库配置:
| 链接方式 | 链接地址格式 | `SPRING_SQL_INIT_PLATFORM` |
| ----------- | ---------------------------------------------------------------------------------- | -------------------------- |
| PostgreSQL | `r2dbc:pool:postgresql://{HOST}:{PORT}/{DATABASE}` | postgresql |
| MySQL | `r2dbc:pool:mysql://{HOST}:{PORT}/{DATABASE}` | mysql |
| H2 Database | `r2dbc:h2:file:///${halo.work-dir}/db/halo-next?MODE=MySQL&DB_CLOSE_ON_EXIT=FALSE` | h2 |

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

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

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

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

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

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

@ -1,41 +0,0 @@
---
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)章节,此处不再赘述。

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

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

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

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

@ -1,94 +0,0 @@
---
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
删除角色后,分配了该角色的用户会丧失对应的权限,影响用户使用。此操作不可恢复。
:::

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

Loading…
Cancel
Save