From aee974185eacdfd930fbb2020d89591dfb1af572 Mon Sep 17 00:00:00 2001 From: Ryan Wang Date: Wed, 23 Nov 2022 16:39:29 +0800 Subject: [PATCH] docs: refine theme development documentation (#110) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 完善 Halo 2.0 主题开发的文档。 /kind documentation Fixes https://github.com/halo-dev/docs/issues/109 TODO list: - [x] 准备工作 - 提供从搭建 Halo 开发环境到成功应用一个新的主题的过程。 - [x] 目录结构 - 详细解释一个主题的目录构成,包括每一个目录和文件的作用。 - [x] 配置文件 - 提供 `theme.yaml` 的各个配置的解释。 - [x] 设置选项 - 讲解如何在主题模板中使用主题定义的 `settings.yaml` 配置项。**注意:关于 settings.yaml 或许可以单独出一篇文档,因为插件也需要用到。主题和插件的文档只需要单独讲解如何使用** - [x] 静态资源 - 讲解静态资源的引用。 - [x] 核心路由 - 列出 Halo 核心提供的路由以及模板,其中需要包括 `文章/自定义页面/分类的自定义模板` 的讲解 - [x] 模板变量 - 讲解系统内提供的路由对应模板中包含的变量。 - [x] Finder APIs - 讲解数据获取 API 的使用。 - [x] 常用代码片段 - 提供一些在主题开发中常用的代码片段,比如如何定义一个 `layout.html`,如何结构化渲染一个菜单或者文章分类等。 ```release-note None ``` --- docs/developer-guide/form-schema.md | 271 ++ docs/developer-guide/theme/code-snippets.md | 47 + docs/developer-guide/theme/config-files.md | 261 -- docs/developer-guide/theme/config.md | 83 + docs/developer-guide/theme/finder-apis.md | 10 + .../theme/finder-apis/category.md | 246 ++ .../theme/finder-apis/comment.md | 245 ++ .../theme/finder-apis/contributor.md | 69 + .../developer-guide/theme/finder-apis/menu.md | 145 + .../theme/finder-apis/plugin.md | 33 + .../developer-guide/theme/finder-apis/post.md | 637 ++++ .../theme/finder-apis/single-page.md | 260 ++ .../theme/finder-apis/site-stats.md | 45 + docs/developer-guide/theme/finder-apis/tag.md | 162 + .../theme/finder-apis/theme.md | 119 + docs/developer-guide/theme/global-variable.md | 261 -- docs/developer-guide/theme/page-variable.md | 1354 -------- docs/developer-guide/theme/prepare.md | 39 +- .../theme/public-template-tag.md | 91 - docs/developer-guide/theme/settings.md | 129 + .../developer-guide/theme/static-resources.md | 55 + docs/developer-guide/theme/structure.md | 10 +- .../theme/template-route-mapping.md | 87 + docs/developer-guide/theme/template-tag.md | 2746 ----------------- .../theme/template-variables.md | 7 + .../theme/template-variables/archives.md | 234 ++ .../theme/template-variables/categories.md | 84 + .../theme/template-variables/category.md | 225 ++ .../theme/template-variables/index_.md | 218 ++ .../theme/template-variables/page.md | 109 + .../theme/template-variables/post.md | 182 ++ .../theme/template-variables/tag.md | 225 ++ .../theme/template-variables/tags.md | 61 + docusaurus.config.js | 5 + sidebars.js | 70 +- static/img/formkit/formkit-repeater.png | Bin 0 -> 37687 bytes 36 files changed, 4076 insertions(+), 4749 deletions(-) create mode 100644 docs/developer-guide/form-schema.md create mode 100644 docs/developer-guide/theme/code-snippets.md delete mode 100644 docs/developer-guide/theme/config-files.md create mode 100644 docs/developer-guide/theme/config.md create mode 100644 docs/developer-guide/theme/finder-apis.md create mode 100644 docs/developer-guide/theme/finder-apis/category.md create mode 100644 docs/developer-guide/theme/finder-apis/comment.md create mode 100644 docs/developer-guide/theme/finder-apis/contributor.md create mode 100644 docs/developer-guide/theme/finder-apis/menu.md create mode 100644 docs/developer-guide/theme/finder-apis/plugin.md create mode 100644 docs/developer-guide/theme/finder-apis/post.md create mode 100644 docs/developer-guide/theme/finder-apis/single-page.md create mode 100644 docs/developer-guide/theme/finder-apis/site-stats.md create mode 100644 docs/developer-guide/theme/finder-apis/tag.md create mode 100644 docs/developer-guide/theme/finder-apis/theme.md delete mode 100644 docs/developer-guide/theme/global-variable.md delete mode 100644 docs/developer-guide/theme/page-variable.md delete mode 100644 docs/developer-guide/theme/public-template-tag.md create mode 100644 docs/developer-guide/theme/settings.md create mode 100644 docs/developer-guide/theme/static-resources.md create mode 100644 docs/developer-guide/theme/template-route-mapping.md delete mode 100644 docs/developer-guide/theme/template-tag.md create mode 100644 docs/developer-guide/theme/template-variables.md create mode 100644 docs/developer-guide/theme/template-variables/archives.md create mode 100644 docs/developer-guide/theme/template-variables/categories.md create mode 100644 docs/developer-guide/theme/template-variables/category.md create mode 100644 docs/developer-guide/theme/template-variables/index_.md create mode 100644 docs/developer-guide/theme/template-variables/page.md create mode 100644 docs/developer-guide/theme/template-variables/post.md create mode 100644 docs/developer-guide/theme/template-variables/tag.md create mode 100644 docs/developer-guide/theme/template-variables/tags.md create mode 100644 static/img/formkit/formkit-repeater.png diff --git a/docs/developer-guide/form-schema.md b/docs/developer-guide/form-schema.md new file mode 100644 index 0000000..061798b --- /dev/null +++ b/docs/developer-guide/form-schema.md @@ -0,0 +1,271 @@ +--- +title: 表单定义 +--- + +在 Halo 2.0,在 Console 端的所有表单我们都使用了 [FormKit](https://github.com/formkit/formkit) 的方案。FormKit 不仅支持使用 Vue 组件的形式来构建表单,同时支持使用 Schema 的形式来构建。因此,我们的 [Setting](https://github.com/halo-dev/halo/blob/87ccd61ae5cd35a38324c30502d4e9c0ced41c6a/src/main/java/run/halo/app/core/extension/Setting.java#L20) 资源中的表单定义,都是使用 FormKit Schema 来定义的,最常用的场景即主题和插件的设置表单定义。当然,如果要在 Halo 2.0 的插件中使用,也可以参考 FormKit 的文档使用 Vue 组件的形式使用,但不需要在插件中引入 FormKit。 + +此文档将不会介绍 FormKit 的具体使用教程,因为我们已经很好的集成了 FormKit,并且使用方式基本无异。此文章将介绍 Halo 2.0 中表单定义的一些规范,以及额外的一些输入组件。 + +FormKit 相关文档: + +- Form Schema: + - + - +- FormKit Inputs: + +:::tip +目前不支持 FormKit Pro 中的输入组件,但 Halo 额外提供了部分输入组件,将在下面文档列出。 +::: + +## Setting 资源定义方式 + +```yaml title="settings.yaml" +apiVersion: v1alpha1 +kind: Setting +metadata: + name: foo-setting +spec: + forms: + - group: group_1 + label: 分组 1 + formSchema: + - $formkit: radio + name: color_scheme + label: 默认配色 + value: system + options: + - label: 跟随系统 + value: system + - label: 深色 + value: dark + - label: 浅色 + value: light + + - group: group_2 + label: 分组 2 + formSchema: + - $formkit: text + name: username + label: 用户名 + value: "" + - $formkit: password + name: password + label: 密码 + value: "" +``` + +:::tip +需要注意的是,FormKit Schema 本身应该是 JSON 格式的,但目前我们定义一个表单所使用的是 YAML,可能在参考 FormKit 写法时需要手动转换一下。 +::: + +字段说明: + +1. `metadata.name`:设置资源的名称,建议以 `-setting` 结尾。 +2. `spec.forms`:表单定义,可以定义多个表单,每个表单都有一个 `group` 字段,用于区分不同的表单。 +3. `spec.forms[].label`:表单的标题。 +4. `spec.forms[].formSchema`:表单的定义,使用 FormKit Schema 来定义。虽然我们使用的 YAML,但与 FormKit Schema 完全一致。 + +## 组件类型 + +除了 FormKit 官方提供的常用输入组件之外,Halo 还额外提供了一些输入组件,这些输入组件可以在 Form Schema 中使用。 + +### Repeater + +#### 描述 + +一组重复的输入组件,可以用于定义一组数据,最终得到的数据为一个对象的数组,可以方便地让使用者对其进行增加、移除、排序等操作。 + +#### 示例 + +```yaml +- $formkit: repeater + name: socials + label: 社交账号 + value: [] + children: + - $formkit: text + name: name + label: 名称 + value: "" + - $formkit: text + name: url + label: 地址 + value: "" +``` + +:::tip +使用 `repeater` 类型时,一定要设置默认值,如果不需要默认有任何元素,可以设置为 `[]`。 +::: + +其中 `name` 和 `url` 即数组对象的属性,最终保存表单之后得到的值为以下形式: + +```json +{ + "socials": [ + { + "name": "GitHub", + "url": "https://github.com/halo-dev" + } + ] +} +``` + +UI 效果: + + + +### Attachment + +#### 描述 + +附件类型的输入框,支持直接调用附件库弹框选择附件。 + +#### 示例 + +```yaml +- $formkit: attachment + name: logo + label: Logo + value: "" +``` + +### Code + +#### 描述 + +代码编辑器的输入组件,集成了 [Codemirror](https://codemirror.net/)。 + +#### 参数 + +- `language`:代码语言,目前支持 `yaml` `html` `javascript` `css` `json`。 +- `height`:代码编辑器的高度。 + +#### 示例 + +```yaml +- $formkit: code + name: footer_code + label: 页脚代码注入 + value: "" + language: yaml +``` + +### menuCheckbox + +#### 描述 + +菜单复选框,用于选择系统内的导航菜单。其中选择的值为菜单资源 `metadata.name` 的集合。 + +#### 示例 + +```yaml +- $formkit: menuCheckbox + name: menus + label: 菜单 + value: [] +``` + +### menuRadio + +#### 描述 + +菜单单选框,用于选择系统内的导航菜单。其中选择的值为菜单资源 `metadata.name`。 + +#### 示例 + +```yaml +- $formkit: menuRadio + name: menu + label: 菜单 + value: "" +``` + +### postSelect + +#### 描述 + +文章选择器,用于选择系统内的文章。其中选择的值为文章资源 `metadata.name`。 + +#### 示例 + +```yaml +- $formkit: postSelect + name: post + label: 文章 + value: "" +``` + +### singlePageSelect + +#### 描述 + +单页选择器,用于选择系统内的独立页面。其中选择的值为独立页面资源 `metadata.name`。 + +#### 示例 + +```yaml +- $formkit: singlePageSelect + name: singlePage + label: 单页 + value: "" +``` + +### categorySelect + +#### 描述 + +文章分类选择器,用于选择系统内的文章分类。其中选择的值为文章分类资源 `metadata.name`。 + +#### 示例 + +```yaml +- $formkit: categorySelect + name: category + label: 分类 + value: "" +``` + +### categoryCheckbox + +#### 描述 + +文章分类复选框,用于选择系统内的文章分类。其中选择的值为文章分类资源 `metadata.name` 的集合。 + +#### 示例 + +```yaml +- $formkit: categoryCheckbox + name: categories + label: 分类 + value: [] +``` + +### tagSelect + +#### 描述 + +文章标签选择器,用于选择系统内的文章标签。其中选择的值为文章标签资源 `metadata.name`。 + +#### 示例 + +```yaml +- $formkit: tagSelect + name: tag + label: 标签 + value: "" +``` + +### tagCheckbox + +#### 描述 + +文章标签复选框,用于选择系统内的文章标签。其中选择的值为文章标签资源 `metadata.name` 的集合。 + +#### 示例 + +```yaml +- $formkit: tagCheckbox + name: tags + label: 标签 + value: [] +``` diff --git a/docs/developer-guide/theme/code-snippets.md b/docs/developer-guide/theme/code-snippets.md new file mode 100644 index 0000000..632936c --- /dev/null +++ b/docs/developer-guide/theme/code-snippets.md @@ -0,0 +1,47 @@ +--- +title: 常用代码片段 +description: 本文档介绍了常用的代码片段,以便于开发者快速上手。 +--- + +## 布局模板 + +通常情况下,我们需要一个公共模板来定义页面的布局。 + +```html title="templates/layout.html" + + + + + + + + + + + + + + +
+ +
+ + +``` + +```html title="templates/index.html" + + + + +
    +
  • + +
  • +
+
+ +``` diff --git a/docs/developer-guide/theme/config-files.md b/docs/developer-guide/theme/config-files.md deleted file mode 100644 index 18f8ee4..0000000 --- a/docs/developer-guide/theme/config-files.md +++ /dev/null @@ -1,261 +0,0 @@ ---- -title: 配置文件 -description: 主题配置文件的说明 ---- - -> Halo 的主题模块使用 yaml 来对主题进行配置,`theme.yaml` 里面主要描述主题的名称,开发者的信息,开源地址等等。`settings.yaml` 包含了主题所有的配置选项,需要注意的是,这些选项仅仅是用于构建配置表单,并不起到对主题的配置作用。 - -## theme.yaml - -```yaml -id: 主题id,唯一,不能与其他主题一样。我们建议设置为 `作者名_主题名称` -name: 主题名称 -author: - name: 作者名称 - website: 作者网址 -description: 主题描述 -logo: 主题 Logo 地址 -website: 主题地址,可填写为 git 仓库地址 -repo: 主题 git 仓库地址,如有填写,后台可在线更新 -version: 版本号 -require: 最低支持的 Halo 版本,如:1.3.0,那么如果用户的版本为 1.3.0 以下,则无法安装 - -postMetaField: 文章自定义 meta 变量 - - meta_key - -sheetMetaField: - - meta_key 页面自定义 meta 变量 -``` - -示例: - -```yaml -id: caicai_anatole -name: Anatole -author: - name: Caicai - website: https://www.caicai.me -description: A other Halo theme -logo: https://avatars1.githubusercontent.com/u/1811819?s=460&v=4 -website: https://github.com/halo-dev/halo-theme-anatole -repo: https://github.com/halo-dev/halo-theme-anatole -version: 1.0.0 -require: 1.3.0 -postMetaField: - - music_url # 假设在文章页面需要播放背景音乐,用户可以自己填写音乐地址。 - - download_url # 假设在文章页有一个下载按钮,那么用户也可以自己填写加载地址。 - -sheetMetaField: - - music_url - - download_url -``` - -### 自定义 meta - -这个为 1.2.0 引入的功能,用户可以在文章设置中设置自定义 meta,我们在 `theme.yaml` 中填写的 `postMetaField` 和 `sheetMetaField` 为预设项,当用户激活当前主题之后,在文章设置中即可看到预先设置好的项,然后填写对应的值即可。 - -关于这个 meta 变量如何调用的问题,会在后面的模板变量中阐述。 - -## settings.yaml - -```yaml -# Tab 节点 -group1: - label: 第一个 Tab 名称 - # 表单项 - items: - # 省略 -group2: - label: 第二个 Tab 名称 - # 表单项 - items: - # 省略 -``` - -## settings.yaml#items - -> settings.yaml 的 items 下即为所有表单元素,所支持的表单元素如下 - -```yaml -items: - - # 普通文本框 - item1: - name: item1 // 设置项的 name 值,在页面可通过 ${settings.item1!} 获取值。 - label: item1 // 表单项的 label - type: text // 表单项类型:普通文本框 - placeholder: '' // 表单项的 placeholder,一般给用户提示 - default: '' // 表单项的默认值 - description: '' // 描述,一般用于说明该设置的具体用途 - - # 颜色选择框 - item1: - name: item1 // 设置项的 name 值,在页面可通过 ${settings.item1!} 获取值。 - label: item1 // 表单项的 label - type: color // 表单项类型:颜色选择框 - placeholder: '' // 表单项的 placeholder,一般给用户提示 - default: '' // 表单项的默认值 - description: '' // 描述,一般用于说明该设置的具体用途 - - # 附件选择框 - item1: - name: item1 // 设置项的 name 值,在页面可通过 ${settings.item1!} 获取值。 - label: item1 // 表单项的 label - type: attachment // 表单项类型:颜色选择框 - placeholder: '' // 表单项的 placeholder,一般给用户提示 - default: '' // 表单项的默认值 - description: '' // 描述,一般用于说明该设置的具体用途 - - # 多行文本框 - item2: // 设置项的 name 值,在页面可通过 ${settings.item2!} 获取值。 - name: item2 - label: item2 // 同上 - type: textarea // 表单项类型:多行文本框 - placeholder: '' // 同上 - default: '' // 同上 - description: '' // 描述,一般用于说明该设置的具体用途 - - # 单选框 - item3: - name: item3 // 同上 - label: item3_label // 同上 - type: radio // 表单项类型:单选框 - data-type: bool // 数据类型:bool,string,long,double - default: value1 // 同上 - description: '' // 描述,一般用于说明该设置的具体用途 - options: // 选项 - - value: value1 // 值 - label: label1 // 说明 - - value: value2 - label: label2 - - # 下拉框 - item4: - name: item4 // 同上 - label: item4 // 同上 - type: select // 表单项类型:下拉框 - data-type: bool // 数据类型:bool,string,long,double - default: value1 // 同上 - description: '' // 描述,一般用于说明该设置的具体用途 - options: // 选项 - - value: value1 // 值 - label: label1 // 说明 - - value: value2 - label: label2 -``` - -### 一个例子 - -假设我们的配置文件如下: - -```yaml -general: - label: 基础设置 - items: - index_title: - name: index_title - label: 首页标题 - type: text - description: '注意:将覆盖博客标题' - background_cover: - name: background_cover - label: 首页背景图 - type: attachment - default: '/casper/assets/images/blog-cover.jpg' - description: '设置首页的背景图,你可以点击右边的选择按钮选择图片。' - background_color: - name: background_color - label: 首页背景颜色 - type: color - default: '#fff' - music_enabled: - name: music_enabled - label: 背景音乐 - type: radio - data-type: bool - default: false - description: '是否开启背景音乐,默认为 false' - options: - - value: true - label: 开启 - - value: false - label: 关闭 - code_pretty: - name: code_pretty - label: 文章代码高亮主题 - type: select - default: Default - options: - - value: Default - label: Default - - value: Coy - label: Coy - - value: Dark - label: Dark - - value: Okaidia - label: Okaidia - - value: Solarized Light - label: Solarized Light - - value: Tomorrow Night - label: Tomorrow Night - - value: Twilight - label: Twilight -``` - -页面取值: - -```html -// 获取首页标题 - -<#if settings.index_title?? && settings.index_title != ''> -

${settings.index_title!}

- -``` - -```html -// 获取背景图片 - -<#if settings.background_cover?? && settings.background_cover != ''> - - -``` - -```html -// 获取背景颜色 - - - -或者 - - - -``` - -```html -// 判断是否开启背景音乐 - -<#if settings.music_enabled!false> - do something... - -``` - -```html -// 获取代码高亮主题 - - - -``` - -更多实例可参考:。 diff --git a/docs/developer-guide/theme/config.md b/docs/developer-guide/theme/config.md new file mode 100644 index 0000000..fe92e8f --- /dev/null +++ b/docs/developer-guide/theme/config.md @@ -0,0 +1,83 @@ +--- +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 的运行版本 | 是 | + +## 从 1.x 迁移 + +为了方便主题开发者从 1.x 迁移,我们提供了工具用于迁移配置文件。 + +工具仓库地址: + +```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`。 +::: diff --git a/docs/developer-guide/theme/finder-apis.md b/docs/developer-guide/theme/finder-apis.md new file mode 100644 index 0000000..fc4522f --- /dev/null +++ b/docs/developer-guide/theme/finder-apis.md @@ -0,0 +1,10 @@ +--- +title: Finder API +description: 本文档介绍 Finder API 的使用方法。 +--- + +import DocCardList from '@theme/DocCardList'; + +目前在主题模板中获取数据可以使用对应路由提供的 [模板变量](./template-variables),但为了满足在任意位置获取数据的需求,我们提供了 Finder API。 + + diff --git a/docs/developer-guide/theme/finder-apis/category.md b/docs/developer-guide/theme/finder-apis/category.md new file mode 100644 index 0000000..1af6cb7 --- /dev/null +++ b/docs/developer-guide/theme/finder-apis/category.md @@ -0,0 +1,246 @@ +--- +title: 文章分类 +description: 文章分类 - CategoryFinder +--- + +## getByName(name) + +```js +categoryFinder.getByName(name) +``` + +### 描述 + +根据 `metadata.name` 获取文章分类。 + +### 参数 + +1. `name:string` - 分类的唯一标识 `metadata.name`。 + +### 返回值 + +[#CategoryVo](#categoryvo) + +### 示例 + +```html +
+ +
+``` + +## getByNames(names) + +```js +categoryFinder.getByNames(names) +``` + +### 描述 + +根据一组 `metadata.name` 获取文章分类。 + +### 参数 + +1. `names:List` - 分类的唯一标识 `metadata.name` 的集合。 + +### 返回值 + +List<[#CategoryVo](#categoryvo)> + +### 示例 + +```html +
+ +
+``` + +## list(page,size) + +```js +categoryFinder.list(page,size) +``` + +### 描述 + +根据分页参数获取分类列表。 + +### 参数 + +1. `page:int` - 分页页码,从 1 开始 +2. `size:int` - 分页条数 + +### 返回值 + +[#ListResult](#listresultcategoryvo) + +### 示例 + +```html +
    +
  • + +
  • +
+``` + +## listAll() + +```js +categoryFinder.listAll() +``` + +### 描述 + +获取所有文章分类。 + +### 参数 + +无 + +### 返回值 + +List<[#CategoryVo](#categoryvo)> + +### 示例 + +```html +
    +
  • + +
  • +
+``` + +## listAsTree() + +```js +categoryFinder.listAsTree() +``` + +### 描述 + +获取所有文章分类的多层级结构。 + +### 参数 + +无 + +### 返回值 + +List<[#CategoryTreeVo](#categorytreevo)> + +### 示例 + +```html +
+
    +
  • +
+
+``` + +```html title="/templates/category-tree.html" + +``` + +## 类型定义 + +### CategoryVo + +```json title="CategoryVo" +{ + "metadata": { + "name": "string", + "labels": { + "additionalProp1": "string" + }, + "annotations": { + "additionalProp1": "string" + }, + "creationTimestamp": "2022-11-20T13:06:38.512Z", + }, + "spec": { + "displayName": "string", + "slug": "string", + "description": "string", + "cover": "string", + "template": "string", + "priority": 0, + "children": [ + "string" + ] + }, + "status": { + "permalink": "string", + "postCount": 0, + "visiblePostCount": 0 + }, + "postCount": 0 +} +``` + +### ListResult + +```json title="ListResult" +{ + "page": 0, + "size": 0, + "total": 0, + "items": "List<#CategoryVo>", + "first": true, + "last": true, + "hasNext": true, + "hasPrevious": true, + "totalPages": 0 +} +``` + +- [#CategoryVo](#categoryvo) + +### CategoryTreeVo + +```json title="CategoryTreeVo" +{ + "metadata": { + "name": "string", + "labels": { + "additionalProp1": "string" + }, + "annotations": { + "additionalProp1": "string" + }, + "creationTimestamp": "2022-11-20T14:18:49.230Z", + }, + "spec": { + "displayName": "string", + "slug": "string", + "description": "string", + "cover": "string", + "template": "string", + "priority": 0, + "children": [ + "string" + ] + }, + "status": { + "permalink": "string", + "postCount": 0, + "visiblePostCount": 0 + }, + "children": "List<#CategoryTreeVo>", + "parentName": "string", + "postCount": 0 +} +``` + +- [#CategoryTreeVo](#categorytreevo) diff --git a/docs/developer-guide/theme/finder-apis/comment.md b/docs/developer-guide/theme/finder-apis/comment.md new file mode 100644 index 0000000..cf15914 --- /dev/null +++ b/docs/developer-guide/theme/finder-apis/comment.md @@ -0,0 +1,245 @@ +--- +title: 评论 +description: 评论 - CommentFinder +--- + +## getByName(name) + +```js +commentFinder.getByName(name) +``` + +### 描述 + +根据 `metadata.name` 获取评论。 + +### 参数 + +1. `name:string` - 评论的唯一标识 `metadata.name`。 + +### 返回值 + +[#CommentVo](#commentvo) + +### 示例 + +```html +
+ +
+
+``` + +## 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](#listresultcommentvo) + +### 示例 + +```html +
    +
  • + +
    +
  • +
+``` + +## 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](#listresultreplyvo) + +### 示例 + +```html +
    +
  • + +
    +
  • +
+``` + +## 类型定义 + +### CommentVo + +```json title="CommentVo" +{ + "metadata": { + "name": "string", + "labels": { + "additionalProp1": "string" + }, + "annotations": { + "additionalProp1": "string" + }, + "creationTimestamp": "2022-11-20T12:16:19.788Z" + }, + "spec": { + "raw": "string", + "content": "string", + "owner": { + "kind": "string", + "name": "string", + "displayName": "string", + "annotations": { + "additionalProp1": "string" + } + }, + "userAgent": "string", + "ipAddress": "string", + "priority": 0, + "top": false, + "allowNotification": true, + "approved": false, + "hidden": false, + "subjectRef": { + "group": "string", + "version": "string", + "kind": "string", + "name": "string" + }, + "lastReadTime": "2022-11-20T12:16:19.788Z" + }, + "status": { + "lastReplyTime": "2022-11-20T12:16:19.788Z", + "replyCount": 0, + "unreadReplyCount": 0, + "hasNewReply": true + }, + "owner": { + "kind": "string", + "name": "string", + "displayName": "string", + "avatar": "string", + "email": "string" + } +} +``` + +### ListResult + +```json title="ListResult" +{ + "page": 0, + "size": 0, + "total": 0, + "items": "List<#CommentVo>", + "first": true, + "last": true, + "hasNext": true, + "hasPrevious": true, + "totalPages": 0 +} +``` + +- [#CommentVo](#commentvo) + +### ReplyVo + +```json title="ReplyVo" +{ + "metadata": { + "name": "string", + "labels": { + "additionalProp1": "string" + }, + "annotations": { + "additionalProp1": "string" + }, + "creationTimestamp": "2022-11-20T12:25:32.357Z" + }, + "spec": { + "raw": "string", + "content": "string", + "owner": { + "kind": "string", + "name": "string", + "displayName": "string", + "annotations": { + "additionalProp1": "string" + } + }, + "userAgent": "string", + "ipAddress": "string", + "priority": 0, + "top": false, + "allowNotification": true, + "approved": false, + "hidden": false, + "commentName": "string", + "quoteReply": "string" + }, + "owner": { + "kind": "string", + "name": "string", + "displayName": "string", + "avatar": "string", + "email": "string" + } +} +``` + +### ListResult + +```json title="ListResult" +{ + "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" +} +``` diff --git a/docs/developer-guide/theme/finder-apis/contributor.md b/docs/developer-guide/theme/finder-apis/contributor.md new file mode 100644 index 0000000..517ece6 --- /dev/null +++ b/docs/developer-guide/theme/finder-apis/contributor.md @@ -0,0 +1,69 @@ +--- +title: 作者 +description: 作者 - ContributorFinder +--- + +## getContributor(name) + +```js +contributorFinder.getContributor(name) +``` + +### 描述 + +根据 `metadata.name` 获取作者。 + +### 参数 + +1. `name:string` - 作者的唯一标识 `metadata.name`。 + +### 返回值 + +[#Contributor](#contributor) + +### 示例 + +```html +
+

+
+``` + +## getContributors(names) + +```js +contributorFinder.getContributors(names) +``` + +### 描述 + +根据一组 `metadata.name` 获取作者。 + +### 参数 + +1. `names:List` - 作者的唯一标识 `metadata.name` 的集合。 + +### 返回值 + +List<[#Contributor](#contributor)> + +### 示例 + +```html +
+ +
+``` + +## 类型定义 + +### Contributor + +```json title="Contributor" +{ + "name": "string", + "displayName": "string", + "avatar": "string", + "bio": "string" +} +``` diff --git a/docs/developer-guide/theme/finder-apis/menu.md b/docs/developer-guide/theme/finder-apis/menu.md new file mode 100644 index 0000000..a77072d --- /dev/null +++ b/docs/developer-guide/theme/finder-apis/menu.md @@ -0,0 +1,145 @@ +--- +title: 导航菜单 +description: 导航菜单 - MenuFinder +--- + +## getByName(name) + +```js +menuFinder.getByName(name) +``` + +### 描述 + +根据 `metadata.name` 获取菜单。 + +### 参数 + +1. `name:string` - 菜单的唯一标识 `metadata.name`。 + +### 返回值 + +[#MenuVo](#menuvo) + +### 示例 + +```html +
+
    +
  • + +
  • +
+
+``` + +## getPrimary() + +```js +menuFinder.getPrimary() +``` + +### 描述 + +获取主菜单。 + +### 参数 + +无 + +### 返回值 + +[#MenuVo](#menuvo) + +### 示例 + +```html +
+
    +
  • + +
  • +
+
+``` + +## 类型定义 + +### MenuVo + +```json title="MenuVo" +{ + "metadata": { + "name": "string", + "labels": { + "additionalProp1": "string" + }, + "annotations": { + "additionalProp1": "string" + }, + "creationTimestamp": "2022-11-20T14:44:58.984Z", + }, + "spec": { + "displayName": "string", + "menuItems": [ + "string" + ] + }, + "menuItems": "List<#MenuItemVo>" +} +``` + +### MenuItemVo + +```json title="MenuItemVo" +{ + "metadata": { + "name": "string", + "labels": { + "additionalProp1": "string" + }, + "annotations": { + "additionalProp1": "string" + }, + "creationTimestamp": "2022-11-20T14:44:58.984Z", + }, + "spec": { + "displayName": "string", + "href": "string", + "priority": 0, + "children": [ + "string" + ], + "categoryRef": { + "group": "string", + "version": "string", + "kind": "string", + "name": "string" + }, + "tagRef": { + "group": "string", + "version": "string", + "kind": "string", + "name": "string" + }, + "postRef": { + "group": "string", + "version": "string", + "kind": "string", + "name": "string" + }, + "singlePageRef": { + "group": "string", + "version": "string", + "kind": "string", + "name": "string" + } + }, + "status": { + "displayName": "string", + "href": "string" + }, + "children": "List<#MenuItemVo>", + "parentName": "string", +} +``` diff --git a/docs/developer-guide/theme/finder-apis/plugin.md b/docs/developer-guide/theme/finder-apis/plugin.md new file mode 100644 index 0000000..a77ec2d --- /dev/null +++ b/docs/developer-guide/theme/finder-apis/plugin.md @@ -0,0 +1,33 @@ +--- +title: 插件 +description: 插件 - PluginFinder +--- + +## available(pluginName) + +```js +pluginFinder.available(pluginName) +``` + +### 描述 + +判断一个插件是否可用,会同时判断插件是否安装和启用。 + +### 参数 + +1. `pluginName:string` - 插件的唯一标识 `metadata.name`。 + +### 返回值 + +`boolean` - 插件是否可用 + +### 示例 + +```html + +
  • + + 搜索 + +
  • +``` diff --git a/docs/developer-guide/theme/finder-apis/post.md b/docs/developer-guide/theme/finder-apis/post.md new file mode 100644 index 0000000..70b91be --- /dev/null +++ b/docs/developer-guide/theme/finder-apis/post.md @@ -0,0 +1,637 @@ +--- +title: 文章 +description: 文章 - PostFinder +--- + +## getByName(postName) + +```js +postFinder.getByName(postName) +``` + +### 描述 + +根据 `metadata.name` 获取文章。 + +### 参数 + +1. `postName:string` - 文章的唯一标识 `metadata.name`。 + +### 返回值 + +[#PostVo](#postvo) + +### 示例 + +```html +
    + +
    +``` + +## content(postName) + +```js +postFinder.content(postName) +``` + +### 描述 + +根据文章的 `metadata.name` 单独获取文章内容。 + +### 参数 + +1. `postName:string` - 文章的唯一标识 `metadata.name`。 + +### 返回值 + +[#ContentVo](#contentvo) + +### 示例 + +```html +
    +
    +
    +``` + +## cursor(postName) + +```js +postFinder.cursor(postName) +``` + +### 描述 + +根据文章的 `metadata.name` 获取相邻的文章(上一篇 / 下一篇)。 + +### 参数 + +1. `postName:string` - 文章的唯一标识 `metadata.name`。 + +### 返回值 + +[#NavigationPostVo](#navigationpostvo) + +### 示例 + +```html title="/templates/post.html" + +``` + +## listAll() + +```js +postFinder.listAll() +``` + +### 描述 + +获取所有文章。 + +### 参数 + +无 + +### 返回值 + +List<[#ListedPostVo](#listedpostvo)> + +### 示例 + +```html +
      +
    • + +
    • +
    +``` + +## list(page,size) + +```js +postFinder.list(page,size) +``` + +### 描述 + +根据分页参数获取文章列表。 + +### 参数 + +1. `page:int` - 分页页码,从 1 开始 +2. `size:int` - 分页条数 + +### 返回值 + +[#ListResult](#listresultlistedpostvo) + +### 示例 + +```html +
      +
    • + +
    • +
    +``` + +## listByCategory(page,size,categoryName) + +```js +postFinder.listByCategory(page,size,categoryName) +``` + +### 描述 + +根据分类标识和分页参数获取文章列表。 + +### 参数 + +1. `page:int` - 分页页码,从 1 开始 +2. `size:int` - 分页条数 +3. `categoryName:string` - 文章分类唯一标识 `metadata.name` + +### 返回值 + +[#ListResult](#listresultlistedpostvo) + +### 示例 + +```html +
      +
    • + +
    • +
    +``` + +## listByTag(page,size,tag) + +```js +postFinder.listByTag(page,size,tag) +``` + +### 描述 + +根据标签标识和分页参数获取文章列表。 + +### 参数 + +1. `page:int` - 分页页码,从 1 开始 +2. `size:int` - 分页条数 +3. `tag:string` - 文章分类唯一标识 `metadata.name` + +### 返回值 + +[#ListResult](#listresultlistedpostvo) + +### 示例 + +```html +
      +
    • + +
    • +
    +``` + +## archives(page,size) + +```js +postFinder.archives(page,size) +``` + +### 描述 + +根据分页参数获取文章归档列表。 + +### 参数 + +1. `page:int` - 分页页码,从 1 开始 +2. `size:int` - 分页条数 + +### 返回值 + +[#ListResult](#listresultpostarchivevo) + +### 示例 + +```html + + +

    +
      + +
    • + + +
    • +
      +
    +
    +
    +``` + +## archives(page,size,year) + +```js +postFinder.archives(page,size,year) +``` + +### 描述 + +根据年份和分页参数获取文章归档列表。 + +### 参数 + +1. `page:int` - 分页页码,从 1 开始 +2. `size:int` - 分页条数 +2. `year:string` - 年份 + +### 返回值 + +[#ListResult](#listresultpostarchivevo) + +### 示例 + +```html + + +

    +
      + +
    • + + +
    • +
      +
    +
    +
    +``` + +## archives(page,size,year,month) + +```js +postFinder.archives(page,size,year,month) +``` + +### 描述 + +根据年月和分页参数获取文章归档列表。 + +### 参数 + +1. `page:int` - 分页页码,从 1 开始 +2. `size:int` - 分页条数 +2. `year:string` - 年份 +2. `month:string` - 月份 + +### 返回值 + +[#ListResult](#listresultpostarchivevo) + +### 示例 + +```html + + +

    +
      + +
    • + + +
    • +
      +
    +
    +
    +``` + +## 类型定义 + +### CategoryVo + +```json title="CategoryVo" +{ + "metadata": { + "name": "string", + "labels": { + "additionalProp1": "string" + }, + "annotations": { + "additionalProp1": "string" + }, + "creationTimestamp": "2022-11-20T13:06:38.512Z", + }, + "spec": { + "displayName": "string", + "slug": "string", + "description": "string", + "cover": "string", + "template": "string", + "priority": 0, + "children": [ + "string" + ] + }, + "status": { + "permalink": "string", + "postCount": 0, + "visiblePostCount": 0 + }, + "postCount": 0 +} +``` + +### TagVo + +```json title="TagVo" +{ + "metadata": { + "name": "string", + "labels": { + "additionalProp1": "string" + }, + "annotations": { + "additionalProp1": "string" + }, + "creationTimestamp": "2022-11-20T13:06:38.512Z", + }, + "spec": { + "displayName": "string", + "slug": "string", + "color": "#F9fEB1", + "cover": "string" + }, + "status": { + "permalink": "string", + "visiblePostCount": 0, + "postCount": 0 + }, + "postCount": 0 +} +``` + +### PostVo + +```json title="PostVo" +{ + "metadata": { + "name": "string", + "labels": { + "additionalProp1": "string" + }, + "annotations": { + "additionalProp1": "string" + }, + "creationTimestamp": "2022-11-20T12:45:43.888Z", + }, + "spec": { + "title": "string", + "slug": "string", + "releaseSnapshot": "string", + "headSnapshot": "string", + "baseSnapshot": "string", + "owner": "string", + "template": "string", + "cover": "string", + "deleted": false, + "publish": false, + "publishTime": "2022-11-20T12:45:43.888Z", + "pinned": false, + "allowComment": true, + "visible": "PUBLIC", + "priority": 0, + "excerpt": { + "autoGenerate": true, + "raw": "string" + }, + "categories": [ + "string" + ], + "tags": [ + "string" + ], + "htmlMetas": [ + { + "additionalProp1": "string" + } + ] + }, + "status": { + "permalink": "string", + "excerpt": "string", + "commentsCount": 0, + "contributors": [ + "string" + ] + }, + "categories": "List<#CategoryVo>", + "tags": "List<#TagVo>", + "contributors": [ + { + "name": "string", + "displayName": "string", + "avatar": "string", + "bio": "string" + } + ], + "owner": { + "name": "string", + "displayName": "string", + "avatar": "string", + "bio": "string" + }, + "stats": { + "visit": 0, + "upvote": 0, + "comment": 0 + }, + "content": { + "raw": "string", + "content": "string" + } +} +``` + +- [#CategoryVo](#categoryvo) +- [#TagVo](#tagvo) + +### ContentVo + +```json title="ContentVo" +{ + "raw": "string", + "content": "string" +} +``` + +### NavigationPostVo + +```json title="NavigationPostVo" +{ + "previous": "#PostVo", + "current": "#PostVo", + "next": "#PostVo" +} +``` + +- [#PostVo](#postvo) + +### ListedPostVo + +```json title="ListedPostVo" +{ + "metadata": { + "name": "string", + "labels": { + "additionalProp1": "string" + }, + "annotations": { + "additionalProp1": "string" + }, + "creationTimestamp": "2022-11-20T13:06:38.505Z", + }, + "spec": { + "title": "string", + "slug": "string", + "releaseSnapshot": "string", + "headSnapshot": "string", + "baseSnapshot": "string", + "owner": "string", + "template": "string", + "cover": "string", + "deleted": false, + "publish": false, + "publishTime": "2022-11-20T13:06:38.505Z", + "pinned": false, + "allowComment": true, + "visible": "PUBLIC", + "priority": 0, + "excerpt": { + "autoGenerate": true, + "raw": "string" + }, + "categories": [ + "string" + ], + "tags": [ + "string" + ], + "htmlMetas": [ + { + "additionalProp1": "string" + } + ] + }, + "status": { + "permalink": "string", + "excerpt": "string", + "inProgress": true, + "commentsCount": 0, + "contributors": [ + "string" + ] + }, + "categories": "List<#CategoryVo>", + "tags": "List<#TagVo>", + "contributors": [ + { + "name": "string", + "displayName": "string", + "avatar": "string", + "bio": "string" + } + ], + "owner": { + "name": "string", + "displayName": "string", + "avatar": "string", + "bio": "string" + }, + "stats": { + "visit": 0, + "upvote": 0, + "comment": 0 + } +} +``` + +- [#CategoryVo](#categoryvo) +- [#TagVo](#tagvo) + +### ListResult + +```json title="ListResult" +{ + "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 + +```json title="ListResult" +{ + "page": 0, + "size": 0, + "total": 0, + "items": "List<#PostArchiveVo>", + "first": true, + "last": true, + "hasNext": true, + "hasPrevious": true, + "totalPages": 0 +} +``` + +- [#PostArchiveVo](#postarchivevo) diff --git a/docs/developer-guide/theme/finder-apis/single-page.md b/docs/developer-guide/theme/finder-apis/single-page.md new file mode 100644 index 0000000..0194f97 --- /dev/null +++ b/docs/developer-guide/theme/finder-apis/single-page.md @@ -0,0 +1,260 @@ +--- +title: 独立页面 +description: 独立页面 - SinglePageFinder +--- + +## getByName(pageName) + +```js +singlePageFinder.getByName(pageName) +``` + +### 描述 + +根据 `metadata.name` 获取独立页面。 + +### 参数 + +1. `pageName:string` - 独立页面的唯一标识 `metadata.name`。 + +### 返回值 + +[#SinglePageVo](#singlepagevo) + +### 示例 + +```html +
    + +
    +``` + +## content(pageName) + +```js +singlePageFinder.content(pageName) +``` + +### 描述 + +根据独立页面的 `metadata.name` 单独获取独立页面内容。 + +### 参数 + +1. `pageName:string` - 独立页面的唯一标识 `metadata.name`。 + +### 返回值 + +[#ContentVo](#contentvo) + +### 示例 + +```html +
    +
    +
    +``` + +## list(page,size) + +```js +singlePageFinder.list(page,size) +``` + +### 描述 + +根据分页参数获取独立页面列表。 + +### 参数 + +1. `page:int` - 分页页码,从 1 开始 +2. `size:int` - 分页条数 + +### 返回值 + +[#ListResult](#listresultlistedsinglepagevo) + +### 示例 + +```html +
      +
    • + +
    • +
    +``` + +## 类型定义 + +### SinglePageVo + +```json title="SinglePageVo" +{ + "metadata": { + "name": "string", + "labels": { + "additionalProp1": "string" + }, + "annotations": { + "additionalProp1": "string" + }, + "creationTimestamp": "2022-11-20T14:29:44.601Z", + }, + "spec": { + "title": "string", + "slug": "string", + "releaseSnapshot": "string", + "headSnapshot": "string", + "baseSnapshot": "string", + "owner": "string", + "template": "string", + "cover": "string", + "deleted": false, + "publish": false, + "publishTime": "2022-11-20T14:29:44.601Z", + "pinned": false, + "allowComment": true, + "visible": "PUBLIC", + "priority": 0, + "excerpt": { + "autoGenerate": true, + "raw": "string" + }, + "htmlMetas": [ + { + "additionalProp1": "string" + } + ] + }, + "status": { + "permalink": "string", + "excerpt": "string", + "inProgress": true, + "commentsCount": 0, + "contributors": [ + "string" + ] + }, + "stats": { + "visit": 0, + "upvote": 0, + "comment": 0 + }, + "contributors": [ + { + "name": "string", + "displayName": "string", + "avatar": "string", + "bio": "string" + } + ], + "owner": { + "name": "string", + "displayName": "string", + "avatar": "string", + "bio": "string" + }, + "content": { + "raw": "string", + "content": "string" + } +} +``` + +### ListedSinglePageVo + +```json title="ListedSinglePageVo" +{ + "metadata": { + "name": "string", + "labels": { + "additionalProp1": "string" + }, + "annotations": { + "additionalProp1": "string" + }, + "creationTimestamp": "2022-11-20T14:31:00.876Z" + }, + "spec": { + "title": "string", + "slug": "string", + "releaseSnapshot": "string", + "headSnapshot": "string", + "baseSnapshot": "string", + "owner": "string", + "template": "string", + "cover": "string", + "deleted": false, + "publish": false, + "publishTime": "2022-11-20T14:31:00.876Z", + "pinned": false, + "allowComment": true, + "visible": "PUBLIC", + "priority": 0, + "excerpt": { + "autoGenerate": true, + "raw": "string" + }, + "htmlMetas": [ + { + "additionalProp1": "string" + } + ] + }, + "status": { + "permalink": "string", + "excerpt": "string", + "inProgress": true, + "commentsCount": 0, + "contributors": [ + "string" + ] + }, + "stats": { + "visit": 0, + "upvote": 0, + "comment": 0 + }, + "contributors": [ + { + "name": "string", + "displayName": "string", + "avatar": "string", + "bio": "string" + } + ], + "owner": { + "name": "string", + "displayName": "string", + "avatar": "string", + "bio": "string" + } +} +``` + +### ListResult + +```json title="ListResult" +{ + "page": 0, + "size": 0, + "total": 0, + "items": "List<#ListedSinglePageVo>", + "first": true, + "last": true, + "hasNext": true, + "hasPrevious": true, + "totalPages": 0 +} +``` + +- [#ListedSinglePageVo](#listedsinglepagevo) + +### ContentVo + +```json title="ContentVo" +{ + "raw": "string", + "content": "string" +} +``` diff --git a/docs/developer-guide/theme/finder-apis/site-stats.md b/docs/developer-guide/theme/finder-apis/site-stats.md new file mode 100644 index 0000000..edb52e5 --- /dev/null +++ b/docs/developer-guide/theme/finder-apis/site-stats.md @@ -0,0 +1,45 @@ +--- +title: 站点统计 +description: 站点统计 - SiteStatsFinder +--- + +## getStats() + +```js +siteStatsFinder.getStats() +``` + +### 描述 + +获取站点的统计信息。 + +### 参数 + +无 + +### 返回值 + +[#SiteStatsVo](#sitestatsvo) + +### 示例 + +```html +
      +
    • +
    • +
    +``` + +## 类型定义 + +### SiteStatsVo + +```json title="SiteStatsVo" +{ + "visit": 0, + "upvote": 0, + "comment": 0, + "post": 0, + "category": 0 +} +``` diff --git a/docs/developer-guide/theme/finder-apis/tag.md b/docs/developer-guide/theme/finder-apis/tag.md new file mode 100644 index 0000000..bfa0559 --- /dev/null +++ b/docs/developer-guide/theme/finder-apis/tag.md @@ -0,0 +1,162 @@ +--- +title: 文章标签 +description: 文章标签 - TagFinder +--- + +## getByName(name) + +```js +tagFinder.getByName(name) +``` + +### 描述 + +根据 `metadata.name` 获取标签。 + +### 参数 + +1. `name:string` - 标签的唯一标识 `metadata.name`。 + +### 返回值 + +[#TagVo](#tagvo) + +### 示例 + +```html +
    + +
    +``` + +## getByNames(names) + +```js +tagFinder.getByNames(names) +``` + +### 描述 + +根据一组 `metadata.name` 获取标签。 + +### 参数 + +1. `names:List` - 标签的唯一标识 `metadata.name` 的集合。 + +### 返回值 + +List<[#TagVo](#tagvo)> + +### 示例 + +```html +
    + +
    +``` + +## list(page,size) + +```js +tagFinder.list(page,size) +``` + +### 描述 + +根据分页参数获取标签列表。 + +### 参数 + +1. `page:int` - 分页页码,从 1 开始 +2. `size:int` - 分页条数 + +### 返回值 + +[#ListResult](#listresulttagvo) + +### 示例 + +```html +
      +
    • + +
    • +
    +``` + +## listAll() + +```js +tagFinder.listAll() +``` + +### 描述 + +获取所有文章标签。 + +### 参数 + +无 + +### 返回值 + +List<[#TagVo](#tagvo)> + +### 示例 + +```html +
      +
    • + +
    • +
    +``` + +## 类型定义 + +### TagVo + +```json title="TagVo" +{ + "metadata": { + "name": "string", + "labels": { + "additionalProp1": "string" + }, + "annotations": { + "additionalProp1": "string" + }, + "creationTimestamp": "2022-11-20T13:06:38.512Z", + }, + "spec": { + "displayName": "string", + "slug": "string", + "color": "#F9fEB1", + "cover": "string" + }, + "status": { + "permalink": "string", + "visiblePostCount": 0, + "postCount": 0 + }, + "postCount": 0 +} +``` + +### ListResult + +```json title="ListResult" +{ + "page": 0, + "size": 0, + "total": 0, + "items": "List<#TagVo>", + "first": true, + "last": true, + "hasNext": true, + "hasPrevious": true, + "totalPages": 0 +} +``` + +- [#TagVo](#tagvo) diff --git a/docs/developer-guide/theme/finder-apis/theme.md b/docs/developer-guide/theme/finder-apis/theme.md new file mode 100644 index 0000000..9fbb1a2 --- /dev/null +++ b/docs/developer-guide/theme/finder-apis/theme.md @@ -0,0 +1,119 @@ +--- +title: 主题 +description: 主题 - ThemeFinder +--- + +## activation() + +```js +themeFinder.activation() +``` + +### 描述 + +获取当前激活的主题。 + +### 参数 + +无 + +### 返回值 + +[#ThemeVo](#themevo) + +### 示例 + +```html +
    +

    +

    +
    +``` + +## getByName(themeName) + +```js +themeFinder.getByName(themeName) +``` + +### 描述 + +根据主题的唯一标识 `metadata.name` 获取主题。 + +### 参数 + +- `themeName:string` - 主题名称 + +### 返回值 + +[#ThemeVo](#themevo) + +### 示例 + +```html +
    +

    +

    +
    +``` + +## 类型定义 + +### ThemeVo + +```json title="ThemeVo" +{ + "metadata": { + "name": "string", + "labels": { + "additionalProp1": "string" + }, + "annotations": { + "additionalProp1": "string" + }, + "creationTimestamp": "2022-11-20T15:27:15.036Z", + }, + "spec": { + "displayName": "string", + "author": { + "name": "string", + "website": "string" + }, + "description": "string", + "logo": "string", + "website": "string", + "repo": "string", + "version": "string", + "require": "string", + "settingName": "string", + "configMapName": "string", + "customTemplates": { + "post": [ + { + "name": "string", + "description": "string", + "screenshot": "string", + "file": "string" + } + ], + "category": [ + { + "name": "string", + "description": "string", + "screenshot": "string", + "file": "string" + } + ], + "page": [ + { + "name": "string", + "description": "string", + "screenshot": "string", + "file": "string" + } + ] + } + }, + "config": {} +} +``` diff --git a/docs/developer-guide/theme/global-variable.md b/docs/developer-guide/theme/global-variable.md deleted file mode 100644 index c9785a7..0000000 --- a/docs/developer-guide/theme/global-variable.md +++ /dev/null @@ -1,261 +0,0 @@ ---- -title: 全局变量 -description: 系统提供的一些全局变量 ---- - -## 博客地址 - -```html -${blog_url!} -``` - -此变量与后台博客设置中的 `博客地址` 相对应。 - -## 网站根路径 - -```html -${context!} -``` - -需要注意的是,此变量和 `blog_url` 不同的是,这个变量有两种值,一种为相对路径形式,一种为绝对路径形式。 - -那么,当在后台博客设置中将 `全局绝对路径` 的选项打开时,`context` 变量值为 `${blog_url}/`,关闭时,`context` 的变量值为 `/`。 - -假设,我设置的 `博客地址` 为 `https://halo.run`,那么: - -- 全局绝对路径为开启的状态下: -- 全局绝对路径为关闭的状态下:/ - -## 主题资源根路径 - -```html -${theme_base!} -``` - -假设你的主题在 `~/halo-dev/templates/themes/anatole` 这个目录,那么 `theme_base` 为 `https://yourdomain/themes/anatole` - -举个例子,你当前开发的主题为 `anatole`,当你要获取主题下 `css/style.css` 这个文件的路径,那么: - -```html -${theme_base!}/css/style.css -``` - -## 主题信息 - -主题名称: - -```html -${theme.name!} -``` - -主题 git 仓库地址: - -```html -${theme.repo!} -``` - -主题版本号: - -```html -${theme.version!} -``` - -## 博客标题 - -```html -${blog_title!} -``` - -此变量与后台博客设置中的 `博客标题` 相对应。 - -## 博客 Logo - -```html -${blog_logo!} -``` - -此变量与后台博客设置中的 `Logo` 相对应。 - -## Halo 版本 - -```html -${version!} -``` - -当前 Halo 的版本,如:1.3.0 - -## 博主信息 - -昵称: - -```html -${user.nickname!} -``` - -邮箱地址: - -```html -${user.email!} -``` - -描述: - -```html -${user.description!} -``` - -头像地址: - -```html -${user.avatar!} -``` - -上次登录时间: - -```html -${user.expireTime!} -``` - -## SEO 关键词 - -```html -${meta_keywords!} -``` - -需要注意的是,虽然这个变量在任何页面都可以使用,但是其值可能在不同的页面是不一样的。会根据用户的设置,生成对应的值。 - -假设在文章页面: - -- 如果用户为文章设置了标签,而没有设置 `自定义关键词`,系统会自动将标签设置为页面关键词。 -- 如果用户设置了 `自定义关键词`,那么则会取用户设置的值。 - -## SEO 描述 - -```html -${meta_description!} -``` - -需要注意的是,虽然这个变量在任何页面都可以使用,但是其值可能在不同的页面是不一样的。会根据用户的设置,生成对应的值。 - -## RSS 2.0 订阅地址 - -```html -${rss_url!} -``` - -如:`https://yourdomain/rss.xml` - -## Atom 格式的订阅地址 - -```html -${atom_url!} -``` - -如:`https://yourdomain/atom.xml` - -## XML 格式的网站地图地址 - -```html -${sitemap_xml_url!} -``` - -如:`https://yourdomain/sitemap.xml` - -## HTML 格式的网站地图地址 - -```html -${sitemap_html_url!} -``` - -如:`https://yourdomain/sitemap.html` - -## 友情链接页面地址 - -```html -${links_url!} -``` - -- **全局绝对路径为开启的状态下**:`https://yourdomain.com/{links_prefix}` -- **全局绝对路径为关闭的状态下**:`/{links_prefix}` - -`{links_prefix}` 是用户可设定的值,用户可以在后台修改 `友情链接` 的前缀,默认为 `links`。 - -## 图库页面地址 - -```html -${photos_url!} -``` - -- **全局绝对路径为开启的状态下**:`https://yourdomain.com/{photos_prefix}` -- **全局绝对路径为关闭的状态下**:`/{photos_prefix}` - -`{photos_prefix}` 是用户可设定的值,用户可以在后台修改 `图库页面` 的前缀,默认为 `photos`。 - -## 日志页面地址 - -```html -${journals_url!} -``` - -- **全局绝对路径为开启的状态下**:`https://yourdomain.com/{journals_prefix}` -- **全局绝对路径为关闭的状态下**:`/{journals_prefix}` - -`{journals_prefix}` 是用户可设定的值,用户可以在后台修改 `日志页面` 的前缀,默认为 `journals`。 - -## 文章归档页面地址 - -```html -${archives_url!} -``` - -- **全局绝对路径为开启的状态下**:`https://yourdomain.com/{archives_prefix}` -- **全局绝对路径为关闭的状态下**:`/{archives_prefix}` - -`{archives_prefix}` 是用户可设定的值,用户可以在后台修改 `归档` 的前缀,默认为 `archives`。 - -## 分类列表页面地址 - -```html -${categories_url!} -``` - -- **全局绝对路径为开启的状态下**:`https://yourdomain.com/{categories_prefix}` -- **全局绝对路径为关闭的状态下**:`/{categories_prefix}` - -`{categories_prefix}` 是用户可设定的值,用户可以在后台修改 `分类` 的前缀,默认为 `categories`。 - -## 标签列表页面地址 - -```html -${tags_url!} -``` - -- **全局绝对路径为开启的状态下**:`https://yourdomain.com/{tags_prefix}` -- **全局绝对路径为关闭的状态下**:`/{tags_prefix}` - -`{tags_prefix}` 是用户可设定的值,用户可以在后台修改 `标签` 的前缀,默认为 `tags`。 - -## 页面判断 - -判断当前页面是否是特定的页面。 - -- **is_index**:首页 -- **is_post**:文章页 -- **is_sheet**:自定义页面 -- **is_archives**:归档页面 -- **is_categories**:分类列表页面 -- **is_category**:单个分类页面 -- **is_tags**:标签列表页面 -- **is_tag**:单个标签页面 -- **is_search**:搜索结果页面 -- **is_journals**:日志页面 -- **is_photos**:图库页面 -- **is_links**:友情链接页面 - -用法: - -```html -<#if is_index??> - 当前页面是首页 - -``` diff --git a/docs/developer-guide/theme/page-variable.md b/docs/developer-guide/theme/page-variable.md deleted file mode 100644 index 555be22..0000000 --- a/docs/developer-guide/theme/page-variable.md +++ /dev/null @@ -1,1354 +0,0 @@ ---- -title: 页面变量 -description: 每个页面所返回的变量 ---- - -## 首页(index.ftl) - -访问路径:`http://yourdomain` - -### posts(List) - -#### 语法 - -```html -<#list posts.content as post> -// do something - -``` - -#### 参数 - -```json -[{ - "content": [{ - "categories": [{ - "createTime": "2020-10-13T13:23:39.143Z", - "description": "string", - "fullPath": "string", - "id": 0, - "name": "string", - "parentId": 0, - "slug": "string", - "thumbnail": "string" - }], - "commentCount": 0, - "createTime": "2020-10-13T13:23:39.143Z", - "disallowComment": true, - "editTime": "2020-10-13T13:23:39.143Z", - "editorType": "MARKDOWN", - "fullPath": "string", - "id": 0, - "likes": 0, - "metaDescription": "string", - "metaKeywords": "string", - "metas": {}, - "password": "string", - "slug": "string", - "status": "PUBLISHED", - "summary": "string", - "tags": [{ - "createTime": "2020-10-13T13:23:39.143Z", - "fullPath": "string", - "id": 0, - "name": "string", - "slug": "string", - "thumbnail": "string" - }], - "template": "string", - "thumbnail": "string", - "title": "string", - "topPriority": 0, - "topped": true, - "updateTime": "2020-10-13T13:23:39.143Z", - "visits": 0, - "wordCount": 0 - }], - "empty": true, - "first": true, - "last": true, - "number": 0, - "numberOfElements": 0, - "pageable": { - "page": 0, - "size": 0, - "sort": [ - "string" - ] - }, - "size": 0, - "sort": { - "sort": [ - "string" - ] - }, - "totalElements": 0, - "totalPages": 0 -}] -``` - -#### 示例 - -遍历输出首页的文章: - -```html -<#list posts.content as post> - ${post.title!} - -``` - -输出: - -```html -title1 -title2 -title3 -``` - -## 文章页面(post.ftl) - -访问路径不固定,视固定链接配置而定,目前可以配置的有: - -- `http://yourdomain/archives/{slug}`(默认) -- `http://yourdomain/1970/01/{slug}` -- `http://yourdomain/1970/01/01/{slug}` -- `http://yourdomain/?p={id}` -- `http://yourdomain/archives/{id}` - -### post(Object) - -#### 语法 - -```html -${post.attribute} -``` - -注:attribute 代表具体属性。 - -#### 参数 - -```json -{ - "categories": [ - { - "createTime": "2021-02-25T13:15:58.589Z", - "description": "string", - "fullPath": "string", - "id": 0, - "name": "string", - "parentId": 0, - "password": "string", - "slug": "string", - "thumbnail": "string" - } - ], - "categoryIds": [ - 0 - ], - "commentCount": 0, - "createTime": "2021-02-25T13:15:58.589Z", - "disallowComment": true, - "editTime": "2021-02-25T13:15:58.589Z", - "editorType": "MARKDOWN", - "formatContent": "string", - "fullPath": "string", - "id": 0, - "likes": 0, - "metaDescription": "string", - "metaIds": [ - 0 - ], - "metaKeywords": "string", - "metas": [ - { - "createTime": "2021-02-25T13:15:58.589Z", - "id": 0, - "key": "string", - "postId": 0, - "value": "string" - } - ], - "originalContent": "string", - "password": "string", - "slug": "string", - "status": "DRAFT", - "summary": "string", - "tagIds": [ - 0 - ], - "tags": [ - { - "createTime": "2021-02-25T13:15:58.589Z", - "fullPath": "string", - "id": 0, - "name": "string", - "slug": "string", - "thumbnail": "string" - } - ], - "template": "string", - "thumbnail": "string", - "title": "string", - "topPriority": 0, - "topped": true, - "updateTime": "2021-02-25T13:15:58.589Z", - "visits": 0, - "wordCount": 0 -} -``` - -#### 示例 - -获取文章标题: - -```html -${post.title!} -``` - -输出: - -```html -示例文章 -``` - -### prevPost(Object) - -#### 语法 - -```html -${prevPost.attribute} -``` - -注:attribute 代表具体属性。 - -#### 参数 - -```json -{ - "categories": [ - { - "createTime": "2021-02-25T13:15:58.589Z", - "description": "string", - "fullPath": "string", - "id": 0, - "name": "string", - "parentId": 0, - "password": "string", - "slug": "string", - "thumbnail": "string" - } - ], - "categoryIds": [ - 0 - ], - "commentCount": 0, - "createTime": "2021-02-25T13:15:58.589Z", - "disallowComment": true, - "editTime": "2021-02-25T13:15:58.589Z", - "editorType": "MARKDOWN", - "formatContent": "string", - "fullPath": "string", - "id": 0, - "likes": 0, - "metaDescription": "string", - "metaIds": [ - 0 - ], - "metaKeywords": "string", - "metas": [ - { - "createTime": "2021-02-25T13:15:58.589Z", - "id": 0, - "key": "string", - "postId": 0, - "value": "string" - } - ], - "originalContent": "string", - "password": "string", - "slug": "string", - "status": "DRAFT", - "summary": "string", - "tagIds": [ - 0 - ], - "tags": [ - { - "createTime": "2021-02-25T13:15:58.589Z", - "fullPath": "string", - "id": 0, - "name": "string", - "slug": "string", - "thumbnail": "string" - } - ], - "template": "string", - "thumbnail": "string", - "title": "string", - "topPriority": 0, - "topped": true, - "updateTime": "2021-02-25T13:15:58.589Z", - "visits": 0, - "wordCount": 0 -} -``` - -#### 示例 - -获取上一篇文章的信息: - -```html -<#if prevPost??> - 上一篇:${prevPost.title!} - -``` - -输出: - -```html -上一篇:title1 -``` - -### nextPost(Object) - -#### 语法 - -```html -${nextPost.attribute} -``` - -注:attribute 代表具体属性。 - -#### 参数 - -```json -{ - "categories": [ - { - "createTime": "2021-02-25T13:15:58.589Z", - "description": "string", - "fullPath": "string", - "id": 0, - "name": "string", - "parentId": 0, - "password": "string", - "slug": "string", - "thumbnail": "string" - } - ], - "categoryIds": [ - 0 - ], - "commentCount": 0, - "createTime": "2021-02-25T13:15:58.589Z", - "disallowComment": true, - "editTime": "2021-02-25T13:15:58.589Z", - "editorType": "MARKDOWN", - "formatContent": "string", - "fullPath": "string", - "id": 0, - "likes": 0, - "metaDescription": "string", - "metaIds": [ - 0 - ], - "metaKeywords": "string", - "metas": [ - { - "createTime": "2021-02-25T13:15:58.589Z", - "id": 0, - "key": "string", - "postId": 0, - "value": "string" - } - ], - "originalContent": "string", - "password": "string", - "slug": "string", - "status": "DRAFT", - "summary": "string", - "tagIds": [ - 0 - ], - "tags": [ - { - "createTime": "2021-02-25T13:15:58.589Z", - "fullPath": "string", - "id": 0, - "name": "string", - "slug": "string", - "thumbnail": "string" - } - ], - "template": "string", - "thumbnail": "string", - "title": "string", - "topPriority": 0, - "topped": true, - "updateTime": "2021-02-25T13:15:58.589Z", - "visits": 0, - "wordCount": 0 -} -``` - -#### 示例 - -获取下一篇文章的信息: - -```html -<#if nextPost??> - 下一篇:${nextPost.title!} - -``` - -输出: - -```html -下一篇:title3 -``` - -### categories(List) - -#### 语法 - -```html -<#list categories as category> -// do something - -``` - -#### 参数 - -```json -[{ - "createTime": "2021-02-25T13:32:11.189Z", - "description": "string", - "fullPath": "string", - "id": 0, - "name": "string", - "parentId": 0, - "password": "string", - "slug": "string", - "thumbnail": "string" -}] -``` - -#### 示例 - -获取文章的分类列表: - -```html -<#list categories as category> - ${category.name!} - -``` - -输出: - -```html -name1 -name2 -``` - -### tags(List) - -#### 语法 - -```html -<#list tags as tag> -// do something - -``` - -#### 参数 - -```json -[{ - "createTime": "2021-02-25T13:34:48.779Z", - "fullPath": "string", - "id": 0, - "name": "string", - "slug": "string", - "thumbnail": "string" -}] -``` - -#### 示例 - -获取文章的标签列表: - -```html -<#list tags as tag> - ${tag.name!} - -``` - -输出: - -```html -name1 -name2 -``` - -### metas(Object) - -#### 语法 - -```html -${metas.key} -``` - -注:attribute 代表具体 key 值。 - -#### 参数 - -无 - -#### 示例 - -获取用户设置的音乐链接: - -```html - -``` - -输出: - -```html - -``` - -## 自定义页面(sheet.ftl) - -访问路径不固定,视固定链接配置而定,默认为:`http://yourdomain/s/{slug}` - -### sheet(Object) - -#### 语法 - -```html -${sheet.attribute} -``` - -注:attribute 代表具体属性。 - -#### 参数 - -```json -{ - "commentCount": 0, - "createTime": "2021-02-25T13:37:29.775Z", - "disallowComment": true, - "editTime": "2021-02-25T13:37:29.775Z", - "editorType": "MARKDOWN", - "formatContent": "string", - "fullPath": "string", - "id": 0, - "likes": 0, - "metaDescription": "string", - "metaIds": [ - 0 - ], - "metaKeywords": "string", - "metas": [ - { - "createTime": "2021-02-25T13:37:29.775Z", - "id": 0, - "key": "string", - "postId": 0, - "value": "string" - } - ], - "originalContent": "string", - "password": "string", - "slug": "string", - "status": "DRAFT", - "summary": "string", - "template": "string", - "thumbnail": "string", - "title": "string", - "topPriority": 0, - "topped": true, - "updateTime": "2021-02-25T13:37:29.775Z", - "visits": 0, - "wordCount": 0 -} -``` - -#### 示例 - -获取页面标题: - -```html -${sheet.title!} -``` - -输出: - -```html -示例页面 -``` - -### metas(Object) - -#### 语法 - -```html -${metas.key} -``` - -注:attribute 代表具体 key 值。 - -#### 参数 - -无 - -#### 示例 - -获取用户设置的音乐链接: - -```html - -``` - -输出: - -```html - -``` - -## 文章归档页面(archives.ftl) - -访问路径不固定,视固定链接配置而定,默认为:`http://yourdomain/archives` - -### posts(List) - -#### 语法 - -```html -<#list posts.content as post> -// do something - -``` - -#### 参数 - -```json -[{ - "content": [{ - "categories": [{ - "createTime": "2020-10-13T13:23:39.143Z", - "description": "string", - "fullPath": "string", - "id": 0, - "name": "string", - "parentId": 0, - "slug": "string", - "thumbnail": "string" - }], - "commentCount": 0, - "createTime": "2020-10-13T13:23:39.143Z", - "disallowComment": true, - "editTime": "2020-10-13T13:23:39.143Z", - "editorType": "MARKDOWN", - "fullPath": "string", - "id": 0, - "likes": 0, - "metaDescription": "string", - "metaKeywords": "string", - "metas": {}, - "password": "string", - "slug": "string", - "status": "PUBLISHED", - "summary": "string", - "tags": [{ - "createTime": "2020-10-13T13:23:39.143Z", - "fullPath": "string", - "id": 0, - "name": "string", - "slug": "string", - "thumbnail": "string" - }], - "template": "string", - "thumbnail": "string", - "title": "string", - "topPriority": 0, - "topped": true, - "updateTime": "2020-10-13T13:23:39.143Z", - "visits": 0, - "wordCount": 0 - }], - "empty": true, - "first": true, - "last": true, - "number": 0, - "numberOfElements": 0, - "pageable": { - "page": 0, - "size": 0, - "sort": [ - "string" - ] - }, - "size": 0, - "sort": { - "sort": [ - "string" - ] - }, - "totalElements": 0, - "totalPages": 0 -}] -``` - -#### 示例 - -遍历输出归档页面的文章(无年份分组): - -```html -<#list posts.content as post> - ${post.title!} - -``` - -输出: - -```html -title1 -title2 -title3 -``` - -### archives(List) - -#### 语法 - -```html -<#list archives.content as archive> -// do something - -``` - -#### 参数 - -```json -{ - "content": [{ - "posts": [{ - "categories": [{ - "createTime": "2021-03-07T05:45:06.271Z", - "description": "string", - "fullPath": "string", - "id": 0, - "name": "string", - "parentId": 0, - "password": "string", - "slug": "string", - "thumbnail": "string" - }], - "commentCount": 0, - "createTime": "2021-03-07T05:45:06.271Z", - "disallowComment": true, - "editTime": "2021-03-07T05:45:06.271Z", - "editorType": "MARKDOWN", - "fullPath": "string", - "id": 0, - "likes": 0, - "metaDescription": "string", - "metaKeywords": "string", - "metas": {}, - "password": "string", - "slug": "string", - "status": "DRAFT", - "summary": "string", - "tags": [{ - "createTime": "2021-03-07T05:45:06.271Z", - "fullPath": "string", - "id": 0, - "name": "string", - "slug": "string", - "thumbnail": "string" - }], - "template": "string", - "thumbnail": "string", - "title": "string", - "topPriority": 0, - "topped": true, - "updateTime": "2021-03-07T05:45:06.271Z", - "visits": 0, - "wordCount": 0 - }], - "year": 0 - }], - "hasContent": true, - "hasNext": true, - "hasPrevious": true, - "isEmpty": true, - "isFirst": true, - "page": 0, - "pages": 0, - "rpp": 0, - "total": 0 -} -``` - -#### 示例 - -遍历输出归档页面的文章(有年份分组): - -```html -<#list archives.content as archive> -

    ${archive.year?c}

    - <#list archive.posts as post> - ${post.title!} - - -``` - -输出: - -```html -

    2021

    -title1 -title2 -title3 -

    2020

    -title4 -title5 -title6 -``` - -## 分类目录页面(categories.ftl) - -访问路径不固定,视固定链接配置而定,默认为:`http://yourdomain/categories` - -此页面无页面变量,可以使用 [模板标签](/developer-guide/theme/template-tag) 获取分类列表。 - -## 单个分类所属文章页面(category.ftl) - -访问路径不固定,视固定链接配置而定,默认为:`http://yourdomain/categories/{slug}` - -### posts(List) - -#### 语法 - -```html -<#list posts.content as post> -// do something - -``` - -#### 参数 - -```json -[{ - "content": [{ - "categories": [{ - "createTime": "2020-10-13T13:23:39.143Z", - "description": "string", - "fullPath": "string", - "id": 0, - "name": "string", - "parentId": 0, - "slug": "string", - "thumbnail": "string" - }], - "commentCount": 0, - "createTime": "2020-10-13T13:23:39.143Z", - "disallowComment": true, - "editTime": "2020-10-13T13:23:39.143Z", - "editorType": "MARKDOWN", - "fullPath": "string", - "id": 0, - "likes": 0, - "metaDescription": "string", - "metaKeywords": "string", - "metas": {}, - "password": "string", - "slug": "string", - "status": "PUBLISHED", - "summary": "string", - "tags": [{ - "createTime": "2020-10-13T13:23:39.143Z", - "fullPath": "string", - "id": 0, - "name": "string", - "slug": "string", - "thumbnail": "string" - }], - "template": "string", - "thumbnail": "string", - "title": "string", - "topPriority": 0, - "topped": true, - "updateTime": "2020-10-13T13:23:39.143Z", - "visits": 0, - "wordCount": 0 - }], - "empty": true, - "first": true, - "last": true, - "number": 0, - "numberOfElements": 0, - "pageable": { - "page": 0, - "size": 0, - "sort": [ - "string" - ] - }, - "size": 0, - "sort": { - "sort": [ - "string" - ] - }, - "totalElements": 0, - "totalPages": 0 -}] -``` - -#### 示例 - -遍历输出某个分类的文章: - -```html -<#list posts.content as post> - ${post.title!} - -``` - -输出: - -```html -title1 -title2 -title3 -``` - -### category(Object) - -#### 语法 - -```html -${category.attribute} -``` - -注:attribute 代表具体属性。 - -#### 参数 - -```json -{ - "createTime": "2020-10-11T05:59:40.622Z", - "description": "string", - "fullPath": "string", - "id": 0, - "name": "string", - "parentId": 0, - "slug": "string", - "thumbnail": "string", - "postCount": 0 -} -``` - -#### 示例 - -```html -分类:${category.name!} -``` - -输出: - -```html -分类:name1 -``` - -## 标签页面(tags.ftl) - -访问路径不固定,视固定链接配置而定,默认为:`http://yourdomain/tags` - -此页面无页面变量,可以使用 [模板标签](/developer-guide/theme/template-tag) 获取标签列表。 - -## 单个标签所属文章页面(tag.ftl) - -访问路径不固定,视固定链接配置而定,默认为:`http://yourdomain/tags/{slug}` - -### posts(List) - -#### 语法 - -```html -<#list posts.content as post> -// do something - -``` - -#### 参数 - -```json -[{ - "content": [{ - "categories": [{ - "createTime": "2020-10-13T13:23:39.143Z", - "description": "string", - "fullPath": "string", - "id": 0, - "name": "string", - "parentId": 0, - "slug": "string", - "thumbnail": "string" - }], - "commentCount": 0, - "createTime": "2020-10-13T13:23:39.143Z", - "disallowComment": true, - "editTime": "2020-10-13T13:23:39.143Z", - "editorType": "MARKDOWN", - "fullPath": "string", - "id": 0, - "likes": 0, - "metaDescription": "string", - "metaKeywords": "string", - "metas": {}, - "password": "string", - "slug": "string", - "status": "PUBLISHED", - "summary": "string", - "tags": [{ - "createTime": "2020-10-13T13:23:39.143Z", - "fullPath": "string", - "id": 0, - "name": "string", - "slug": "string", - "thumbnail": "string" - }], - "template": "string", - "thumbnail": "string", - "title": "string", - "topPriority": 0, - "topped": true, - "updateTime": "2020-10-13T13:23:39.143Z", - "visits": 0, - "wordCount": 0 - }], - "empty": true, - "first": true, - "last": true, - "number": 0, - "numberOfElements": 0, - "pageable": { - "page": 0, - "size": 0, - "sort": [ - "string" - ] - }, - "size": 0, - "sort": { - "sort": [ - "string" - ] - }, - "totalElements": 0, - "totalPages": 0 -}] -``` - -#### 示例 - -遍历输出某个标签的文章: - -```html -<#list posts.content as post> - ${post.title!} - -``` - -输出: - -```html -title1 -title2 -title3 -``` - -### tag(Object) - -#### 语法 - -```html -${tag.attribute} -``` - -注:attribute 代表具体属性。 - -#### 参数 - -```json -{ - "createTime": "2020-10-11T06:14:30.595Z", - "fullPath": "string", - "id": 0, - "name": "string", - "slug": "string", - "thumbnail": "string" -} -``` - -#### 示例 - -```html -标签:${tag.name!} -``` - -输出: - -```html -标签:name1 -``` - -## 文章搜索结果页面(search.ftl) - -访问路径:`http://yourdomain/search?keyword=keyword` - -### keyword(String) - -#### 语法 - -```html -${keyword!} -``` - -#### 参数 - -无 - -#### 示例 - -```html -搜索关键字为:${keyword!} -``` - -### posts(List) - -#### 语法 - -```html -<#list posts.content as post> -// do something - -``` - -#### 参数 - -```json -[{ - "content": [{ - "categories": [{ - "createTime": "2020-10-13T13:23:39.143Z", - "description": "string", - "fullPath": "string", - "id": 0, - "name": "string", - "parentId": 0, - "slug": "string", - "thumbnail": "string" - }], - "commentCount": 0, - "createTime": "2020-10-13T13:23:39.143Z", - "disallowComment": true, - "editTime": "2020-10-13T13:23:39.143Z", - "editorType": "MARKDOWN", - "fullPath": "string", - "id": 0, - "likes": 0, - "metaDescription": "string", - "metaKeywords": "string", - "metas": {}, - "password": "string", - "slug": "string", - "status": "PUBLISHED", - "summary": "string", - "tags": [{ - "createTime": "2020-10-13T13:23:39.143Z", - "fullPath": "string", - "id": 0, - "name": "string", - "slug": "string", - "thumbnail": "string" - }], - "template": "string", - "thumbnail": "string", - "title": "string", - "topPriority": 0, - "topped": true, - "updateTime": "2020-10-13T13:23:39.143Z", - "visits": 0, - "wordCount": 0 - }], - "empty": true, - "first": true, - "last": true, - "number": 0, - "numberOfElements": 0, - "pageable": { - "page": 0, - "size": 0, - "sort": [ - "string" - ] - }, - "size": 0, - "sort": { - "sort": [ - "string" - ] - }, - "totalElements": 0, - "totalPages": 0 -}] -``` - -#### 示例 - -遍历输出某个搜索结果的文章: - -```html -<#list posts.content as post> - ${post.title!} - -``` - -输出: - -```html -title1 -title2 -title3 -``` - -## 友情链接页面(links.ftl) - -访问路径不固定,视固定链接配置而定,默认为:`http://yourdomain/links` - -此页面无页面变量,可以使用 [模板标签](/developer-guide/theme/template-tag) 获取友情链接列表。 - -## 图库页面(photos.ftl) - -访问路径不固定,视固定链接配置而定,默认为:`http://yourdomain/photos` - -### photos(List) - -#### 语法 - -```html -<#list photos.content as photo> -// do something - -``` - -#### 参数 - -```json -{ - "content": [{ - "description": "string", - "id": 0, - "location": "string", - "name": "string", - "takeTime": "2021-03-07T05:28:12.352Z", - "team": "string", - "thumbnail": "string", - "url": "string" - }], - "hasContent": true, - "hasNext": true, - "hasPrevious": true, - "isEmpty": true, - "isFirst": true, - "page": 0, - "pages": 0, - "rpp": 0, - "total": 0 -} -``` - -#### 示例 - -```html -<#list photos.content as photo> - ${photo.description!} - -``` - -输出: - -```html -山川 -河流 -绿树 -``` - -## 日志页面(journals.ftl) - -访问路径不固定,视固定链接配置而定,默认为:`http://yourdomain/journals` - -### journals(List) - -#### 语法 - -```html -<#list journals.content as journal> -// do something - -``` - -#### 参数 - -```json -{ - "content": [{ - "commentCount": 0, - "content": "string", - "createTime": "2021-03-07T05:32:06.365Z", - "id": 0, - "likes": 0, - "sourceContent": "string", - "type": "INTIMATE" - }], - "hasContent": true, - "hasNext": true, - "hasPrevious": true, - "isEmpty": true, - "isFirst": true, - "page": 0, - "pages": 0, - "rpp": 0, - "total": 0 -} -``` - -#### 示例 - -```html -
      - <#list journals.content as journal> -
    • - ${journal.createTime?string('yyyy年MM月dd日')}:${journal.content!} -
    • - -
    -``` - -输出: - -```html -
      -
    • - 2021年3月7日:内容1 -
    • -
    • - 2021年3月7日:内容2 -
    • -
    -``` diff --git a/docs/developer-guide/theme/prepare.md b/docs/developer-guide/theme/prepare.md index 22b9426..15285cd 100644 --- a/docs/developer-guide/theme/prepare.md +++ b/docs/developer-guide/theme/prepare.md @@ -11,47 +11,34 @@ Halo 在本地开发环境的运行可参考[开发环境运行](../core/run.md) ## 新建一个主题 -Halo 的主题存放于工作目录的 `themes` 目录下,即 `~/halo-next/themes`,在该目录下新建一个文件夹,例如 `theme-foo`。当前一个最小可被系统加载的主题必须在主题根目录下包含 `theme.yaml` 配置文件。 - -以 [theme-default](https://github.com/halo-sigs/theme-default) 为例: +Halo 的主题存放于工作目录的 `themes` 目录下,即 `~/halo2-dev/themes`,在该目录下新建一个文件夹,例如 `theme-foo`。当前一个最小可被系统加载的主题必须在主题根目录下包含 `theme.yaml` 配置文件。 ```yaml title="theme.yaml" apiVersion: theme.halo.run/v1alpha1 kind: Theme metadata: - name: theme-default + name: theme-foo spec: - displayName: Default + displayName: 示例主题 author: name: halo-dev website: https://halo.run - description: Default theme for Halo 2.0 + description: 一个示例主题 logo: https://halo.run/logo - website: https://github.com/halo-sigs/theme-default - repo: https://github.com/halo-sigs/theme-default.git - settingName: "theme-default-setting" - configMapName: "theme-default-configMap" + 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 ``` -| 字段 | 描述 | 是否必填 | -| --------------------- | ----------------------------------------------------------- | -------- | -| `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.version` | 主题版本 | 是 | -| `spec.require` | 所需 Halo 的运行版本 | 是 | +:::info 提示 +主题的配置文件详细文档请参考 [配置文件](./config.md)。 +::: :::info 提示 -关于主题项目的目录结构请参考[主题目录结构](./structure.md)。 +主题项目的目录结构请参考 [主题目录结构](./structure.md)。 ::: ## 通过模板创建 @@ -69,7 +56,7 @@ spec: ## 创建第一个页面模板 -Halo 使用 [Thymeleaf](https://www.thymeleaf.org/) 作为后端模板引擎,后缀为 `.html`,与单纯编写 HTMl 一致。在 Halo 的主题中,主题的模板文件存放于 `templates` 目录下,例如 `~/halo-next/themes/theme-foo/templates`。为了此文档方便演示,我们先在 `templates` 创建一个首页的模板文件 `index.html`: +Halo 使用 [Thymeleaf](https://www.thymeleaf.org/) 作为后端模板引擎,后缀为 `.html`,与单纯编写 HTML 一致。在 Halo 的主题中,主题的模板文件存放于 `templates` 目录下,例如 `~/halo2-dev/themes/theme-foo/templates`。为了此文档方便演示,我们先在 `templates` 创建一个首页的模板文件 `index.html`: ```html title="templates/index.html" diff --git a/docs/developer-guide/theme/public-template-tag.md b/docs/developer-guide/theme/public-template-tag.md deleted file mode 100644 index 515a72a..0000000 --- a/docs/developer-guide/theme/public-template-tag.md +++ /dev/null @@ -1,91 +0,0 @@ ---- -title: 公共宏模板 -description: 系统提供的一些宏模板 ---- - -> 为了减少重复代码,我们将某些常见的全局变量封装成了一个公共模板,我们只需要引入该模板,然后调用其中的宏模板即可。 - -## 公共 head 模板 - -> 需要注意的是,为了保证系统功能的完整性,我们强制要求在每个页面的 `` 标签下必须包含此模板。 - -```html -<@global.head /> -``` - -等同于: - -```html -<#if options.seo_spider_disabled!false> - - - -<@global.favicon /> -<@global.custom_head /> -<@global.custom_content_head /> -``` - -## 公共底部 - -> 需要注意的是,为了保证系统功能的完整性,我们强制要求在每个页面的尾部必须包含此模板。 - -```html -<@global.footer /> -``` - -等同于: - -```html -<@global.statistics /> -<@global.footer_info /> -```` - -## 相对时间 - -```html -<@global.timeline datetime="时间" /> - -// 输出 -x 年前/x 个月前/x 天前/昨天/x 小时前/x 分钟前/x 秒前/刚刚 -``` - -## 评论模块 - -```html -<@global.comment target= type="" /> -``` - -等同于: - -```html -<#if !post.disallowComment!false> - - - - -``` - -参数说明: - -- target:post / sheet / journal 对象 -- type:评论类型,可为:post / sheet / journal - -例子: - -在文章页面(post.ftl or post_xxx.ftl): - -```html -<@global.comment target=post type="post" /> -``` - -在自定义页面(sheet.ftl or post_sheet.ftl): - -```html -<@global.comment target=sheet type="sheet" /> -``` - -在日志页面(journals.ftl): - -```html -<@global.comment target=journal type="journal" /> -``` diff --git a/docs/developer-guide/theme/settings.md b/docs/developer-guide/theme/settings.md new file mode 100644 index 0000000..4f10b01 --- /dev/null +++ b/docs/developer-guide/theme/settings.md @@ -0,0 +1,129 @@ +--- +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 + + + +``` + +```html +
      + +
    + +
    + +
    +``` + +## 从 1.x 迁移 + +为了方便主题开发者从 1.x 迁移,我们提供了工具用于迁移设置表单配置文件。 + +工具仓库地址: + +```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` 字段。 +::: diff --git a/docs/developer-guide/theme/static-resources.md b/docs/developer-guide/theme/static-resources.md new file mode 100644 index 0000000..b9ec636 --- /dev/null +++ b/docs/developer-guide/theme/static-resources.md @@ -0,0 +1,55 @@ +--- +title: 静态资源 +description: 本文档介绍主题的静态资源的引用方法。 +--- + +通过 [目录结构](./structure.md) 的讲解我们可以知道,目前主题的静态资源统一托管在 `/templates/assets/` 目录下,下面讲解一下如何在模板中使用,大致会分为两种引入方式。 + +## 模板标签引用 + +```html + + + + +``` + +其中 `@{/assets/dist/style.css}` 表示引用 `/templates/assets/dist/style.css` 文件。最终会被渲染为: + +```html + +``` + +## API 引用 + +以上方式仅支持在 HTML 标签中使用,且必须使用 `@{}` 包裹才能渲染为正确的路径。如果需要在非 HTML 标签中得到正确的路径,我们提供了 `#theme.assets()` API。 + +:::info 注意 +需要注意的是,调用 `#theme.assets()` 的时候,资源地址不需要添加 `/assets/`。 +::: + +比如我们需要在 JavaScript 中异步获取一些资源: + +```html {3} + +``` + +:::info 提示 +关于在 JavaScript 中使用 Thymeleaf 语法可以参考 Thymeleaf 官方文档:[JavaScript inlining](https://www.thymeleaf.org/doc/tutorials/3.1/usingthymeleaf.html#javascript-inlining) +::: diff --git a/docs/developer-guide/theme/structure.md b/docs/developer-guide/theme/structure.md index bc65c32..ac5efe9 100644 --- a/docs/developer-guide/theme/structure.md +++ b/docs/developer-guide/theme/structure.md @@ -5,7 +5,7 @@ description: 主题的目录结构介绍 Halo 2.0 的主题基本目录结构如下: -```bash title="~/halo-next/themes/my-theme" +```bash title="~/halo2-dev/themes/my-theme" my-theme ├── templates/ │ ├── assets/ @@ -27,7 +27,7 @@ my-theme 详细说明: -1. `/templates/` - 主题模板目录,存放主题模板文件,所有模板都需要放在这个目录。 -2. `/templates/assets/` - 主题静态资源目录,存放主题的静态资源文件,目前静态资源文件只能放在这个目录。 -3. `/theme.yaml` - 主题配置文件,配置主题的基本信息,如主题名称、版本、作者等。 -4. `/settings.yaml` - 主题设置定义文件,配置主题的设置项表单。 +1. `/templates/` - 主题模板目录,存放主题模板文件,所有模板都需要放在这个目录。关于模板的详细说明,请查阅 [模板路由](./template-route-mapping)。 +2. `/templates/assets/` - 主题静态资源目录,存放主题的静态资源文件,目前静态资源文件只能放在这个目录,引用方式请查阅 [静态资源](./static-resources)。 +3. `/theme.yaml` - 主题配置文件,配置主题的基本信息,如主题名称、版本、作者等。详细文档请查阅 [配置文件](./config)。 +4. `/settings.yaml` - 主题设置定义文件,配置主题的设置项表单。详细文档请查阅 [设置选项](./settings)。 diff --git a/docs/developer-guide/theme/template-route-mapping.md b/docs/developer-guide/theme/template-route-mapping.md new file mode 100644 index 0000000..95b0ddc --- /dev/null +++ b/docs/developer-guide/theme/template-route-mapping.md @@ -0,0 +1,87 @@ +--- +title: 模板路由 +description: 本文档介绍路由与模板的映射关系,以及自定义模板。 +--- + +此文档讲解系统内部提供的路由与模板映射。 + +## 主要模板 + +### index.html + +站点的首页模板,访问地址为 `/`。 + +### post.html + +文章详情页面的模板,访问地址默认为 `/archives/:slug`。 + +### page.html + +独立页面详情的模板,访问地址默认为 `/:slug`。 + +### archives.html + +文章归档页面的模板,访问地址包括: + +- `/archives` +- `/archives/:year` +- `/archives/:year/:month` + +### tags.html + +标签集合页面的模板,访问地址默认为 `/tags`。 + +### tag.html + +标签归档页面的模板,访问地址默认为 `/tags/:slug`。 + +### categories.html + +分类集合页面的模板,访问地址默认为 `/categories`。 + +### category.html + +分类归档页面的模板,访问地址默认为 `/categories/:slug`。 + +## 自定义模板 {#custom-templates} + +一般情况下,上文提到的模板已经能够满足大部分的需求,但如果需要针对某个特定的页面进行自定义,可以通过自定义模板来实现。目前系统支持为 **文章**、**独立页面**和**分类归档** 设置自定义模板: + +在 `theme.yaml` 的 `spec` 节点下添加如下配置: + +```yaml +customTemplates: + {type}: + - name: {name} + description: {description} + screenshot: {screenshot} + file: {file}.html +``` + +示例: + +```yaml +customTemplates: + post: + - name: 文档 + description: 文档类型的文章 + screenshot: + file: post_documentation.html +``` + +字段说明: + +- `type`:模板类型,目前支持 `post` `page` `category`。 +- `name`:模板名称 +- `description`:模板描述 +- `screenshot`:模板预览图 +- `file`:模板文件名,需要在 `/templates/` 目录下创建 + +最终使用者即可在文章设置、独立页面设置、分类设置中选择自定义模板。 + +:::info 提示 + +1. 自定义模板与默认模板的功能相同,区别仅在于可以让使用者选择不同于默认模板风格的模板。 +2. 自定义模板的文件名需要以 `.html` 结尾,且需要在 `/templates/` 目录下创建。 + +::: diff --git a/docs/developer-guide/theme/template-tag.md b/docs/developer-guide/theme/template-tag.md deleted file mode 100644 index 9d45043..0000000 --- a/docs/developer-guide/theme/template-tag.md +++ /dev/null @@ -1,2746 +0,0 @@ ---- -title: 模板标签 -description: 用于获取数据的模板标签 ---- - -:::note -模板标签可以运用在页面的任何地方。 -::: - -## 文章(postTag) - -### 获取最新文章(latest) - -#### 语法 - -```html -<@postTag method="latest" top="获取条数"> -// do something - -``` - -参数: - -1. method:latest -2. top:所需要获取的条数 - -#### 返回参数 - -posts: - -```json -[{ - "categories": [{ - "createTime": "2020-10-11T05:22:08.264Z", - "description": "string", - "fullPath": "string", - "id": 0, - "name": "string", - "parentId": 0, - "slug": "string", - "thumbnail": "string" - }], - "commentCount": 0, - "createTime": "2020-10-11T05:22:08.264Z", - "disallowComment": true, - "editTime": "2020-10-11T05:22:08.264Z", - "editorType": "MARKDOWN", - "fullPath": "string", - "id": 0, - "likes": 0, - "metaDescription": "string", - "metaKeywords": "string", - "metas": {}, - "password": "string", - "slug": "string", - "status": "PUBLISHED", - "summary": "string", - "tags": [{ - "createTime": "2020-10-11T05:22:08.264Z", - "fullPath": "string", - "id": 0, - "name": "string", - "slug": "string", - "thumbnail": "string" - }], - "template": "string", - "thumbnail": "string", - "title": "string", - "topPriority": 0, - "topped": true, - "updateTime": "2020-10-11T05:22:08.264Z", - "visits": 0, - "wordCount": 0 -}] -``` - -#### 示例 - -```html -<@postTag method="latest" top="3"> - <#list posts as post> - ${post.title!} - - -``` - -输出: - -```html -title1 -title2 -title3 -``` - -### 获取所有文章的数量(count) - -#### 语法 - -```html -<@postTag method="count"> -// do something - -``` - -参数: - -1. method:count - -#### 返回参数 - -```json -count: long -``` - -#### 示例 - -```html -<@postTag method="count"> -文章数量:${count!0} - -``` - -输出: - -```html -文章数量:20 -``` - -### 根据年份归档(archiveYear) - -#### 语法 - -```html -<@postTag method="archiveYear"> -// do something - -``` - -参数: - -1. method:archiveYear - -#### 返回参数 - -archives: - -```json -[{ - "posts": [{ - "categories": [{ - "createTime": "2020-10-11T05:30:45.245Z", - "description": "string", - "fullPath": "string", - "id": 0, - "name": "string", - "parentId": 0, - "slug": "string", - "thumbnail": "string" - }], - "commentCount": 0, - "createTime": "2020-10-11T05:30:45.245Z", - "disallowComment": true, - "editTime": "2020-10-11T05:30:45.245Z", - "editorType": "MARKDOWN", - "fullPath": "string", - "id": 0, - "likes": 0, - "metaDescription": "string", - "metaKeywords": "string", - "metas": {}, - "password": "string", - "slug": "string", - "status": "PUBLISHED", - "summary": "string", - "tags": [{ - "createTime": "2020-10-11T05:30:45.245Z", - "fullPath": "string", - "id": 0, - "name": "string", - "slug": "string", - "thumbnail": "string" - }], - "template": "string", - "thumbnail": "string", - "title": "string", - "topPriority": 0, - "topped": true, - "updateTime": "2020-10-11T05:30:45.245Z", - "visits": 0, - "wordCount": 0 - }], - "year": 0 -}] -``` - -#### 示例 - -```html -<@postTag method="archiveYear"> - <#list archives as archive> -

    年份: ${archive.year?c}

    -
      - <#list archive.posts?sort_by("createTime")?reverse as post> -
    • - ${post.title!} -
    • - -
    - - -``` - -输出: - -```html -

    2019

    - -

    2018

    - -``` - -### 根据年月归档(archiveMonth) - -#### 语法 - -```html -<@postTag method="archiveMonth"> -// do something - -``` - -参数: - -1. method:archiveMonth - -#### 返回参数 - -archives: - -```json -[{ - "month": 0, - "posts": [{ - "categories": [{ - "createTime": "2020-10-11T05:35:01.835Z", - "description": "string", - "fullPath": "string", - "id": 0, - "name": "string", - "parentId": 0, - "slug": "string", - "thumbnail": "string" - }], - "commentCount": 0, - "createTime": "2020-10-11T05:35:01.835Z", - "disallowComment": true, - "editTime": "2020-10-11T05:35:01.835Z", - "editorType": "MARKDOWN", - "fullPath": "string", - "id": 0, - "likes": 0, - "metaDescription": "string", - "metaKeywords": "string", - "metas": {}, - "password": "string", - "slug": "string", - "status": "PUBLISHED", - "summary": "string", - "tags": [{ - "createTime": "2020-10-11T05:35:01.835Z", - "fullPath": "string", - "id": 0, - "name": "string", - "slug": "string", - "thumbnail": "string" - }], - "template": "string", - "thumbnail": "string", - "title": "string", - "topPriority": 0, - "topped": true, - "updateTime": "2020-10-11T05:35:01.835Z", - "visits": 0, - "wordCount": 0 - }], - "year": 0 -}] -``` - -#### 示例 - -```html -<@postTag method="archiveMonth"> - <#list archives as archive> -

    ${archive.year?c}-${archive.month?c}

    -
      - <#list archive.posts?sort_by("createTime")?reverse as post> -
    • - ${post.title!} -
    • - -
    - - -``` - -输出: - -```html -

    2019-01

    - -

    2018-12

    - -``` - -### 归档(archive) - -#### 语法 - -```html -<@postTag method="archive" type="year or month"> -// do something - -``` - -参数: - -1. method:archive -2. type: `year` 或者 `month` - -#### 返回参数 - -archives(year): - -```json -[{ - "posts": [{ - "categories": [{ - "createTime": "2020-10-11T05:30:45.245Z", - "description": "string", - "fullPath": "string", - "id": 0, - "name": "string", - "parentId": 0, - "slug": "string", - "thumbnail": "string" - }], - "commentCount": 0, - "createTime": "2020-10-11T05:30:45.245Z", - "disallowComment": true, - "editTime": "2020-10-11T05:30:45.245Z", - "editorType": "MARKDOWN", - "fullPath": "string", - "id": 0, - "likes": 0, - "metaDescription": "string", - "metaKeywords": "string", - "metas": {}, - "password": "string", - "slug": "string", - "status": "PUBLISHED", - "summary": "string", - "tags": [{ - "createTime": "2020-10-11T05:30:45.245Z", - "fullPath": "string", - "id": 0, - "name": "string", - "slug": "string", - "thumbnail": "string" - }], - "template": "string", - "thumbnail": "string", - "title": "string", - "topPriority": 0, - "topped": true, - "updateTime": "2020-10-11T05:30:45.245Z", - "visits": 0, - "wordCount": 0 - }], - "year": 0 -}] -``` - -archives(month): - -```json -[{ - "month": 0, - "posts": [{ - "categories": [{ - "createTime": "2020-10-11T05:35:01.835Z", - "description": "string", - "fullPath": "string", - "id": 0, - "name": "string", - "parentId": 0, - "slug": "string", - "thumbnail": "string" - }], - "commentCount": 0, - "createTime": "2020-10-11T05:35:01.835Z", - "disallowComment": true, - "editTime": "2020-10-11T05:35:01.835Z", - "editorType": "MARKDOWN", - "fullPath": "string", - "id": 0, - "likes": 0, - "metaDescription": "string", - "metaKeywords": "string", - "metas": {}, - "password": "string", - "slug": "string", - "status": "PUBLISHED", - "summary": "string", - "tags": [{ - "createTime": "2020-10-11T05:35:01.835Z", - "fullPath": "string", - "id": 0, - "name": "string", - "slug": "string", - "thumbnail": "string" - }], - "template": "string", - "thumbnail": "string", - "title": "string", - "topPriority": 0, - "topped": true, - "updateTime": "2020-10-11T05:35:01.835Z", - "visits": 0, - "wordCount": 0 - }], - "year": 0 -}] -``` - -#### 示例 - -```html -<@postTag method="archive" type="month"> - <#list archives as archive> -

    ${archive.year?c}-${archive.month?c}

    -
      - <#list archive.posts?sort_by("createTime")?reverse as post> -
    • - ${post.title!} -
    • - -
    - - -``` - -输出: - -```html -

    2019-01

    - -

    2018-12

    - -``` - -### 根据分类 id 获取文章(listByCategoryId) - -#### 语法 - -```html -<@postTag method="listByCategoryId" categoryId="分类 id"> -// do something - -``` - -参数: - -1. method:listByCategoryId -2. categoryId:分类 id - -#### 返回参数 - -posts: - -```json -[{ - "categories": [{ - "createTime": "2020-10-11T05:35:01.811Z", - "description": "string", - "fullPath": "string", - "id": 0, - "name": "string", - "parentId": 0, - "slug": "string", - "thumbnail": "string" - }], - "commentCount": 0, - "createTime": "2020-10-11T05:35:01.811Z", - "disallowComment": true, - "editTime": "2020-10-11T05:35:01.811Z", - "editorType": "MARKDOWN", - "fullPath": "string", - "id": 0, - "likes": 0, - "metaDescription": "string", - "metaKeywords": "string", - "metas": {}, - "password": "string", - "slug": "string", - "status": "PUBLISHED", - "summary": "string", - "tags": [{ - "createTime": "2020-10-11T05:35:01.811Z", - "fullPath": "string", - "id": 0, - "name": "string", - "slug": "string", - "thumbnail": "string" - }], - "template": "string", - "thumbnail": "string", - "title": "string", - "topPriority": 0, - "topped": true, - "updateTime": "2020-10-11T05:35:01.811Z", - "visits": 0, - "wordCount": 0 -}] -``` - -#### 示例 - -```html -<@postTag method="listByCategoryId" top="${category.id?c}"> - 分类 ${category.name!} 下的文章: - <#list posts as post> - ${post.title!} - - -``` - -输出: - -```html -title1 -title2 -title3 -``` - -### 根据分类 slug 获取文章(listByCategorySlug) - -#### 语法 - -```html -<@postTag method="listByCategorySlug" categorySlug="分类 slug"> -// do something - -``` - -参数: - -1. method:listByCategorySlug -2. categorySlug:分类 slug - -#### 返回参数 - -posts: - -```json -[{ - "categories": [{ - "createTime": "2020-10-11T05:35:01.811Z", - "description": "string", - "fullPath": "string", - "id": 0, - "name": "string", - "parentId": 0, - "slug": "string", - "thumbnail": "string" - }], - "commentCount": 0, - "createTime": "2020-10-11T05:35:01.811Z", - "disallowComment": true, - "editTime": "2020-10-11T05:35:01.811Z", - "editorType": "MARKDOWN", - "fullPath": "string", - "id": 0, - "likes": 0, - "metaDescription": "string", - "metaKeywords": "string", - "metas": {}, - "password": "string", - "slug": "string", - "status": "PUBLISHED", - "summary": "string", - "tags": [{ - "createTime": "2020-10-11T05:35:01.811Z", - "fullPath": "string", - "id": 0, - "name": "string", - "slug": "string", - "thumbnail": "string" - }], - "template": "string", - "thumbnail": "string", - "title": "string", - "topPriority": 0, - "topped": true, - "updateTime": "2020-10-11T05:35:01.811Z", - "visits": 0, - "wordCount": 0 -}] -``` - -#### 示例 - -```html -<@postTag method="listByCategorySlug" categorySlug="${category.slug!}"> - 分类 ${category.name!} 下的文章: - <#list posts as post> - ${post.title!} - - -``` - -输出: - -```html -title1 -title2 -title3 -``` - -### 根据标签 id 获取文章(listByTagId) - -#### 语法 - -```html -<@postTag method="listByTagId" tagId="标签 id"> -// do something - -``` - -参数: - -1. method:listByTagId -2. tagId:标签 id - -#### 返回参数 - -posts: - -```json -[{ - "categories": [{ - "createTime": "2020-10-11T05:35:01.811Z", - "description": "string", - "fullPath": "string", - "id": 0, - "name": "string", - "parentId": 0, - "slug": "string", - "thumbnail": "string" - }], - "commentCount": 0, - "createTime": "2020-10-11T05:35:01.811Z", - "disallowComment": true, - "editTime": "2020-10-11T05:35:01.811Z", - "editorType": "MARKDOWN", - "fullPath": "string", - "id": 0, - "likes": 0, - "metaDescription": "string", - "metaKeywords": "string", - "metas": {}, - "password": "string", - "slug": "string", - "status": "PUBLISHED", - "summary": "string", - "tags": [{ - "createTime": "2020-10-11T05:35:01.811Z", - "fullPath": "string", - "id": 0, - "name": "string", - "slug": "string", - "thumbnail": "string" - }], - "template": "string", - "thumbnail": "string", - "title": "string", - "topPriority": 0, - "topped": true, - "updateTime": "2020-10-11T05:35:01.811Z", - "visits": 0, - "wordCount": 0 -}] -``` - -#### 示例 - -```html -<@postTag method="listByTagId" tagId="${tag.id?c}"> - 标签 ${tag.name!} 下的文章: - <#list posts as post> - ${post.title!} - - -``` - -输出: - -```html -title1 -title2 -title3 -``` - -### 根据标签 slug 获取文章(listByTagSlug) - -#### 语法 - -```html -<@postTag method="listByTagSlug" tagSlug="标签 slug"> -// do something - -``` - -参数: - -1. method:listByTagSlug -2. tagSlug:标签 slug - -#### 返回参数 - -posts: - -```json -[{ - "categories": [{ - "createTime": "2020-10-11T05:35:01.811Z", - "description": "string", - "fullPath": "string", - "id": 0, - "name": "string", - "parentId": 0, - "slug": "string", - "thumbnail": "string" - }], - "commentCount": 0, - "createTime": "2020-10-11T05:35:01.811Z", - "disallowComment": true, - "editTime": "2020-10-11T05:35:01.811Z", - "editorType": "MARKDOWN", - "fullPath": "string", - "id": 0, - "likes": 0, - "metaDescription": "string", - "metaKeywords": "string", - "metas": {}, - "password": "string", - "slug": "string", - "status": "PUBLISHED", - "summary": "string", - "tags": [{ - "createTime": "2020-10-11T05:35:01.811Z", - "fullPath": "string", - "id": 0, - "name": "string", - "slug": "string", - "thumbnail": "string" - }], - "template": "string", - "thumbnail": "string", - "title": "string", - "topPriority": 0, - "topped": true, - "updateTime": "2020-10-11T05:35:01.811Z", - "visits": 0, - "wordCount": 0 -}] -``` - -#### 示例 - -```html -<@postTag method="listByTagSlug" tagSlug="${tag.slug!}"> - 标签 ${tag.name!} 下的文章: - <#list posts as post> - ${post.title!} - - -``` - -输出: - -```html -title1 -title2 -title3 -``` - -## 评论(commentTag) - -### 获取最新评论(latest) - -#### 语法 - -```html -<@commentTag method="latest" top="获取条数"> -// do something - -``` - -参数: - -1. method:latest -2. top:所需要获取的条数 - -#### 返回参数 - -comments: - -```json -[{ - "allowNotification": true, - "author": "string", - "authorUrl": "string", - "content": "string", - "createTime": "2020-10-13T12:35:54.974Z", - "email": "string", - "gravatarMd5": "string", - "id": 0, - "ipAddress": "string", - "isAdmin": true, - "parentId": 0, - "post": { - "createTime": "2020-10-13T12:35:54.974Z", - "editTime": "2020-10-13T12:35:54.974Z", - "editorType": "MARKDOWN", - "fullPath": "string", - "id": 0, - "metaDescription": "string", - "metaKeywords": "string", - "slug": "string", - "status": "PUBLISHED", - "title": "string", - "updateTime": "2020-10-13T12:35:54.974Z" - }, - "status": "PUBLISHED", - "userAgent": "string" -}] -``` - -#### 示例 - -```html -<@commentTag method="latest" top="获取条数"> -
      - <#list comments.content as comment> -
    • ${comment.author!}:${comment.content!}
    • - -
    - -``` - -输出: - -```html -
      -
    • author1:content1
    • -
    • author2:content2
    • -
    -``` - -### 获取所有评论的数量(count) - -#### 语法 - -```html -<@commentTag method="count"> -// do something - -``` - -参数: - -1. method:count - -#### 返回参数 - -```json -count: long -``` - -#### 示例 - -```html -<@commentTag method="count"> -评论数量:${count!0} - -``` - -输出: - -```html -文章数量:20 -``` - -## 分类目录(categoryTag) - -### 获取所有分类目录(list) - -#### 语法 - -```html -<@categoryTag method="list"> -// do something - -``` - -参数: - -1. method:list - -#### 返回参数 - -categories: - -```json -[{ - "createTime": "2020-10-11T05:59:40.622Z", - "description": "string", - "fullPath": "string", - "id": 0, - "name": "string", - "parentId": 0, - "slug": "string", - "thumbnail": "string", - "postCount": 0 -}] -``` - -#### 示例 - -```html -<@categoryTag method="list"> - <#list categories as category> - ${category.name!}(${category.postCount!}) - - -``` - -输出: - -```html -name1(2) -name2(12) -``` - -### 获取分类目录树结构(tree) - -#### 语法 - -```html -<@categoryTag method="tree"> -// do something - -``` - -参数: - -1. method:tree - -#### 返回参数 - -categories: - -```json -[ - { - "children": [ - { - "children": [], - "createTime": "2022-02-12T14:11:06.376Z", - "description": "string", - "fullPath": "string", - "id": 0, - "name": "string", - "parentId": 0, - "password": "string", - "slug": "string", - "thumbnail": "string" - } - ], - "createTime": "2022-02-12T14:11:06.376Z", - "description": "string", - "fullPath": "string", - "id": 0, - "name": "string", - "parentId": 0, - "password": "string", - "slug": "string", - "thumbnail": "string" - } -] -``` - -#### 示例 - -```html -<@categoryTag method="tree"> -
      - <#list categories as category> -
    • - - ${category.name!} - -
    • - - <#if category.children?? && category.children?size gt 0> - <@renderCategories category.children> - - -
    - - -<#macro renderCategories categories> -
      - <#list categories as category> -
    • - - ${(category.name)!} - - <#if category.children?? && category.children?size gt 0> - <@renderCategories category.children> - -
    • - -
    - -``` - -输出: - -```html - -``` - -### 获取文章的所有分类(listByPostId) - -#### 语法 - -```html -<@categoryTag method="listByPostId" postId="文章 id"> -// do something - -``` - -参数: - -1. method:listByPostId -2. postId:文章 id - -#### 返回参数 - -categories: - -```json -[{ - "createTime": "2020-10-11T05:59:40.622Z", - "description": "string", - "fullPath": "string", - "id": 0, - "name": "string", - "parentId": 0, - "slug": "string", - "thumbnail": "string" -}] -``` - -#### 示例 - -```html -<@categoryTag method="listByPostId" postId="${post.id?c}"> - <#list categories as category> - ${category.name} - - -``` - -输出: - -```html -name1 -name2 -``` - -### 获取所有分类的数量(count) - -#### 语法 - -```html -<@categoryTag method="count"> -// do something - -``` - -参数: - -1. method:count - -#### 返回参数 - -```json -count: long -``` - -#### 示例 - -```html -<@categoryTag method="count"> -分类数量:${count!0} - -``` - -输出: - -```html -分类数量:20 -``` - -## 标签(tagTag) - -### 获取所有标签(list) - -#### 语法 - -```html -<@tagTag method="list"> -// 返回参数:tags - -``` - -参数: - -1. method:list - -#### 返回参数 - -tags: - -```json -[{ - "createTime": "2020-10-11T06:14:30.595Z", - "fullPath": "string", - "id": 0, - "name": "string", - "slug": "string", - "thumbnail": "string", - "postCount": 0 -}] -``` - -#### 示例 - -```html -<@tagTag method="list"> - <#list tags as tag> - ${tag.name!}(${tag.postCount!}) - - -``` - -输出: - -```html -name1(1) -name2(20) -``` - -### 获取文章的所有标签(listByPostId) - -#### 语法 - -```html -<@tagTag method="listByPostId" postId="文章 id"> -// do something - -``` - -参数: - -1. method:listByPostId -2. postId:文章 id - -#### 返回参数 - -tags: - -```json -[{ - "createTime": "2020-10-11T06:14:30.595Z", - "fullPath": "string", - "id": 0, - "name": "string", - "slug": "string", - "thumbnail": "string" -}] -``` - -#### 示例 - -```html -<@tagTag method="listByPostId" postId="${post.id?c}"> - <#list tags as tag> - ${tag.name} - - -``` - -输出: - -```html -name1 -name2 -``` - -### 获取所有标签的数量(count) - -#### 语法 - -```html -<@tagTag method="count"> -// do something - -``` - -参数: - -1. method:count - -#### 返回参数 - -```json -count: long -``` - -#### 示例 - -```html -<@tagTag method="count"> -标签数量:${count!0} - -``` - -输出: - -```html -标签数量:20 -``` - -## 菜单(menuTag) - -### 获取所有菜单(list) - -#### 语法 - -```html -<@menuTag method="list"> -// do something - -``` - -参数: - -1. method:list - -#### 返回参数 - -menus: - -```json -[{ - "icon": "string", - "id": 0, - "name": "string", - "parentId": 0, - "priority": 0, - "target": "string", - "team": "string", - "url": "string" -}] -``` - -#### 示例 - -```html -<@menuTag method="list"> - - -``` - -输出: - -```html - -``` - -### 获取多级菜单(tree) - -#### 语法 - -```html -<@menuTag method="tree"> -// do something - -``` - -参数: - -1. method:tree - -#### 返回参数 - -menus: - -```json -[{ - "children": [{ - "children": [{}], - "icon": "string", - "id": 0, - "name": "string", - "parentId": 0, - "priority": 0, - "target": "string", - "team": "string", - "url": "string" - }], - "icon": "string", - "id": 0, - "name": "string", - "parentId": 0, - "priority": 0, - "target": "string", - "team": "string", - "url": "string" -}] -``` - -#### 示例 - -```html -<@menuTag method="tree"> -
      - <#list menus as menu> -
    • - ${menu.name!} - <#if menu.children?? && menu.children?size gt 0> - - -
    • - -
    - -``` - -输出: - -```html - -``` - -### 根据分组获取菜单(listByTeam) - -#### 语法 - -```html -<@menuTag method="listByTeam" team="team 名称"> -// do something - -``` - -参数: - -1. method:listByTeam -2. team:team 名称 - -#### 返回参数 - -menus: - -```json -[{ - "icon": "string", - "id": 0, - "name": "string", - "parentId": 0, - "priority": 0, - "target": "string", - "team": "string", - "url": "string" -}] -``` - -#### 示例 - -```html -<@menuTag method="listByTeam" team="main"> - - -``` - -输出: - -```html - -``` - -### 根据分组获取多级菜单(treeByTeam) - -#### 语法 - -```html -<@menuTag method="treeByTeam" team="team 名称"> -// do something - -``` - -参数: - -1. method:treeByTeam -2. team:team 名称 - -#### 返回参数 - -menus: - -```json -[{ - "children": [{ - "children": [{}], - "icon": "string", - "id": 0, - "name": "string", - "parentId": 0, - "priority": 0, - "target": "string", - "team": "string", - "url": "string" - }], - "icon": "string", - "id": 0, - "name": "string", - "parentId": 0, - "priority": 0, - "target": "string", - "team": "string", - "url": "string" -}] -``` - -#### 示例 - -```html -<@menuTag method="treeByTeam" team="main"> -
      - <#list menus as menu> -
    • - ${menu.name!} - <#if menu.children?? && menu.children?size gt 0> - - -
    • - -
    - -``` - -输出: - -```html - -``` - -## 友情链接(linkTag) - -### 获取所有友情链接(list) - -#### 语法 - -```html -<@linkTag method="list"> -// do something - -``` - -参数: - -1. method:list - -#### 返回参数 - -links: - -```json -[{ - "id": 0, - "name": "string", - "url": "string", - "logo": "string", - "description": "string", - "team": "string", - "priority": 0, - "createTime": "2021-01-10 20:48:00", - "updateTime": "2021-01-10 20:48:00" -}] -``` - -#### 示例 - -```html - -``` - -输出: - -```html - -``` - -### 乱序获取所有友情链接(listByRandom) - -#### 语法 - -```html -<@linkTag method="listByRandom"> -// do something - -``` - -参数: - -1. method:listByRandom - -#### 返回参数 - -```json -[{ - "id": 0, - "name": "string", - "url": "string", - "logo": "string", - "description": "string", - "team": "string", - "priority": 0, - "createTime": "2021-01-10 20:48:00", - "updateTime": "2021-01-10 20:48:00" -}] -``` - -#### 示例 - -```html - -``` - -输出: - -```html - -``` - -### 获取分组友情链接(listTeams) - -#### 语法 - -```html -<@linkTag method="listTeams"> -// do something - -``` - -参数: - -1. method:listTeams - -#### 返回参数 - -teams: - -```json -[{ - "team": "string", - "links": [{ - "id": 0, - "name": "string", - "url": "string", - "logo": "string", - "description": "string", - "team": "string", - "priority": 0 - }] -}] -``` - -#### 示例 - -```html -<@linkTag method="listTeams"> - <#list teams as team> -

    ${team.team}

    - - - -``` - -输出: - -```html -

    Halo 相关

    - -

    网友们

    - -``` - -### 乱序获取分组友情链接(listTeamsByRandom) - -#### 语法 - -```html -<@linkTag method="listTeamsByRandom"> -// do something - -``` - -参数: - -1. method:listTeamsByRandom - -#### 返回参数 - -teams: - -```json -[{ - "team": "string", - "links": [{ - "id": 0, - "name": "string", - "url": "string", - "logo": "string", - "description": "string", - "team": "string", - "priority": 0 - }] -}] -``` - -#### 示例 - -```html -<@linkTag method="listTeamsByRandom"> - <#list teams as team> -

    ${team.team}

    - - - -``` - -输出: - -```html -

    Halo 相关

    - -

    网友们

    - -``` - -### 获取所有友情链接的数量(count) - -#### 语法 - -```html -<@linkTag method="count"> -// do something - -``` - -参数: - -1. method:count - -#### 返回参数 - -```json -count: long -``` - -#### 示例 - -```html -<@linkTag method="count"> -友情链接数量:${count!0} - -``` - -输出: - -```html -友情链接数量:20 -``` - -## 图库(photoTag) - -### 获取所有图片(list) - -#### 语法 - -```html -<@photoTag method="list"> -// do something - -``` - -参数: - -1. method:list - -#### 返回参数 - -photos: - -```json -[{ - "id": 0, - "name": "string", - "description": "string", - "takeTime": "2021-01-10 20:48:00", - "location": "string", - "thumbnail": "string", - "url": "string", - "team": "string", - "createTime": "2021-01-10 20:48:00", - "updateTime": "2021-01-10 20:48:00" -}] -``` - -#### 示例 - -```html -<@photoTag method="list"> - <#list photos as photo> - ${photo.description} - - -``` - -输出: - -```html -山川 -河流 -绿树 -``` - -### 获取所有分组图片(listTeams) - -#### 语法 - -```html -<@photoTag method="listTeams"> -// do something - -``` - -参数: - -1. method:listTeams - -#### 返回参数 - -teams: - -```json -[{ - "team": "string", - "photos": [{ - "id": 0, - "name": "string", - "thumbnail": "string", - "takeTime": "2021-01-10 20:48:00", - "url": "string", - "team": "string", - "location": "string", - "description": "string" - }] -}] -``` - -#### 示例 - -```html -<@photoTag method="listTeams"> - <#list teams as team> -

    ${team.team}

    - <#list team.photos as photo> - ${photo.description} - - - -``` - -输出: - -```html -

    风景

    -山川 -河流 -绿树 -

    旅行

    -四川 -重庆 -深圳 -``` - -### 根据分组获取图片(listByTeam) - -#### 语法 - -```html -<@photoTag method="listByTeam" team="team 名称"> -// do something - -``` - -参数: - -1. method:listByTeam -2. team:team 名称 - -#### 返回参数 - -photos: - -```json -[{ - "id": 0, - "name": "string", - "description": "string", - "takeTime": "2021-01-10 20:48:00", - "location": "string", - "thumbnail": "string", - "url": "string", - "team": "string", - "createTime": "2021-01-10 20:48:00", - "updateTime": "2021-01-10 20:48:00" -}] -``` - -#### 示例 - -```html -<@photoTag method="listByTeam" team="风景"> - <#list photos as photo> - ${photo.description} - - -``` - -输出: - -```html -山川 -河流 -绿树 -``` - -### 获取所有图片的数量(count) - -#### 语法 - -```html -<@photoTag method="count"> -// do something - -``` - -参数: - -1. method:count - -#### 返回参数 - -```json -count: long -``` - -#### 示例 - -```html -<@linkTag method="count"> -图片数量:${count!0} - -``` - -输出: - -```html -图片数量:20 -``` - -## 分页(paginationTag) - -### 获取首页文章列表的分页数据(index) - -#### 语法 - -```html -<@paginationTag method="index" page="${posts.number}" total="${posts.totalPages}" display="3"> -// do something - -``` - -参数: - -1. method:index -2. page:当前页,通过 `${posts.number}` 得到 -3. total:总页数,通过 `${posts.totalPages}` 得到 -4. display:页码展示数量 - -#### 返回参数 - -pagination: - -```json -{ - "nextPageFullPath": "string", - "prevPageFullPath": "string", - "hasPrev": true, - "hasNext": true, - "rainbowPages": [{ - "page": 0, - "fullPath": "string", - "isCurrent": true - }] -} -``` - -#### 示例 - -```html -
      - <@paginationTag method="index" page="${posts.number}" total="${posts.totalPages}" display="3"> - <#if pagination.hasPrev> -
    • - 上一页 -
    • - - <#list pagination.rainbowPages as number> - <#if number.isCurrent> -
    • - ${number.page!} -
    • - <#else> -
    • - ${number.page!} -
    • - - - <#if pagination.hasNext> -
    • - 下一页 -
    • - - -
    -``` - -输出: - -```html - -``` - -### 获取文章归档列表的分页数据(archives) - -#### 语法 - -```html -<@paginationTag method="archives" page="${posts.number}" total="${posts.totalPages}" display="3"> -// do something - -``` - -参数: - -1. method:archives -2. page:当前页,通过 `${posts.number}` 得到 -3. total:总页数,通过 `${posts.totalPages}` 得到 -4. display:页码展示数量 - -#### 返回参数 - -pagination: - -```json -{ - "nextPageFullPath": "string", - "prevPageFullPath": "string", - "hasPrev": true, - "hasNext": true, - "rainbowPages": [{ - "page": 0, - "fullPath": "string", - "isCurrent": true - }] -} -``` - -#### 示例 - -```html -
      - <@paginationTag method="archives" page="${posts.number}" total="${posts.totalPages}" display="3"> - <#if pagination.hasPrev> -
    • - 上一页 -
    • - - <#list pagination.rainbowPages as number> - <#if number.isCurrent> -
    • - ${number.page!} -
    • - <#else> -
    • - ${number.page!} -
    • - - - <#if pagination.hasNext> -
    • - 下一页 -
    • - - -
    -``` - -输出: - -```html - -``` - -### 获取搜索结果文章列表的分页数据(search) - -#### 语法 - -```html -<@paginationTag method="search" page="${posts.number}" total="${posts.totalPages}" keyword="${keyword}" display="3"> -// do something - -``` - -参数: - -1. method:search -2. page:当前页,通过 `${posts.number}` 得到 -3. total:总页数,通过 `${posts.totalPages}` 得到 -4. keyword: 关键词 -5. display:页码展示数量 - -#### 返回参数 - -pagination: - -```json -{ - "nextPageFullPath": "string", - "prevPageFullPath": "string", - "hasPrev": true, - "hasNext": true, - "rainbowPages": [{ - "page": 0, - "fullPath": "string", - "isCurrent": true - }] -} -``` - -#### 示例 - -```html -
      - <@paginationTag method="search" page="${posts.number}" total="${posts.totalPages}" keyword="${keyword}" display="3"> - <#if pagination.hasPrev> -
    • - 上一页 -
    • - - <#list pagination.rainbowPages as number> - <#if number.isCurrent> -
    • - ${number.page!} -
    • - <#else> -
    • - ${number.page!} -
    • - - - <#if pagination.hasNext> -
    • - 下一页 -
    • - - -
    -``` - -输出: - -```html - -``` - -### 获取标签下文章列表的分页数据(tagPosts) - -#### 语法 - -```html -<@paginationTag method="tagPosts" slug="${tag.slug!}" page="${posts.number}" total="${posts.totalPages}" display="3"> -// do something - -``` - -参数: - -1. method:tagPosts -2. page:当前页,通过 `${posts.number}` 得到 -3. total:总页数,通过 `${posts.totalPages}` 得到 -4. display:页码展示数量 -5. slug:标签 slug - -#### 返回参数 - -pagination: - -```json -{ - "nextPageFullPath": "string", - "prevPageFullPath": "string", - "hasPrev": true, - "hasNext": true, - "rainbowPages": [{ - "page": 0, - "fullPath": "string", - "isCurrent": true - }] -} -``` - -#### 示例 - -```html -
      - <@paginationTag method="tagPosts" slug="${tag.slug!}" page="${posts.number}" total="${posts.totalPages}" display="3"> - <#if pagination.hasPrev> -
    • - 上一页 -
    • - - <#list pagination.rainbowPages as number> - <#if number.isCurrent> -
    • - ${number.page!} -
    • - <#else> -
    • - ${number.page!} -
    • - - - <#if pagination.hasNext> -
    • - 下一页 -
    • - - -
    -``` - -输出: - -```html - -``` - -### 获取分类下文章列表的分页数据(categoryPosts) - -#### 语法 - -```html -<@paginationTag method="categoryPosts" slug="${category.slug!}" page="${posts.number}" total="${posts.totalPages}" display="3"> -// do something - -``` - -参数: - -1. method:categoryPosts -2. page:当前页,通过 `${posts.number}` 得到 -3. total:总页数,通过 `${posts.totalPages}` 得到 -4. display:页码展示数量 -5. slug:标签 slug - -#### 返回参数 - -pagination: - -```json -{ - "nextPageFullPath": "string", - "prevPageFullPath": "string", - "hasPrev": true, - "hasNext": true, - "rainbowPages": [{ - "page": 0, - "fullPath": "string", - "isCurrent": true - }] -} -``` - -#### 示例 - -```html -
      - <@paginationTag method="categoryPosts" slug="${category.slug!}" page="${posts.number}" total="${posts.totalPages}" display="3"> - <#if pagination.hasPrev> -
    • - 上一页 -
    • - - <#list pagination.rainbowPages as number> - <#if number.isCurrent> -
    • - ${number.page!} -
    • - <#else> -
    • - ${number.page!} -
    • - - - <#if pagination.hasNext> -
    • - 下一页 -
    • - - -
    -``` - -输出: - -```html - -``` - -### 获取图库页面图片列表的分页数据(photos) - -#### 语法 - -```html -<@paginationTag method="photos" page="${photos.number}" total="${photos.totalPages}" display="3"> -// do something - -``` - -参数: - -1. method:photos -2. page:当前页,通过 `${photos.number}` 得到 -3. total:总页数,通过 `${photos.totalPages}` 得到 -4. display:页码展示数量 - -#### 返回参数 - -pagination: - -```json -{ - "nextPageFullPath": "string", - "prevPageFullPath": "string", - "hasPrev": true, - "hasNext": true, - "rainbowPages": [{ - "page": 0, - "fullPath": "string", - "isCurrent": true - }] -} -``` - -#### 示例 - -```html -
      - <@paginationTag method="photos" page="${photos.number}" total="${photos.totalPages}" display="3"> - <#if pagination.hasPrev> -
    • - 上一页 -
    • - - <#list pagination.rainbowPages as number> - <#if number.isCurrent> -
    • - ${number.page!} -
    • - <#else> -
    • - ${number.page!} -
    • - - - <#if pagination.hasNext> -
    • - 下一页 -
    • - - -
    -``` - -输出: - -```html - -``` - -### 获取日志页面日志列表的分页数据(journals) - -#### 语法 - -```html -<@paginationTag method="journals" page="${journals.number}" total="${journals.totalPages}" display="3"> -// do something - -``` - -参数: - -1. method:journals -2. page:当前页,通过 `${journals.number}` 得到 -3. total:总页数,通过 `${journals.totalPages}` 得到 -4. display:页码展示数量 - -#### 返回参数 - -pagination: - -```json -{ - "nextPageFullPath": "string", - "prevPageFullPath": "string", - "hasPrev": true, - "hasNext": true, - "rainbowPages": [{ - "page": 0, - "fullPath": "string", - "isCurrent": true - }] -} -``` - -#### 示例 - -```html -
      - <@paginationTag method="journals" page="${journals.number}" total="${journals.totalPages}" display="3"> - <#if pagination.hasPrev> -
    • - 上一页 -
    • - - <#list pagination.rainbowPages as number> - <#if number.isCurrent> -
    • - ${number.page!} -
    • - <#else> -
    • - ${number.page!} -
    • - - - <#if pagination.hasNext> -
    • - 下一页 -
    • - - -
    -``` - -输出: - -```html - -``` diff --git a/docs/developer-guide/theme/template-variables.md b/docs/developer-guide/theme/template-variables.md new file mode 100644 index 0000000..84cb618 --- /dev/null +++ b/docs/developer-guide/theme/template-variables.md @@ -0,0 +1,7 @@ +--- +title: 模板变量 +--- + +import DocCardList from '@theme/DocCardList'; + + diff --git a/docs/developer-guide/theme/template-variables/archives.md b/docs/developer-guide/theme/template-variables/archives.md new file mode 100644 index 0000000..b7b80d1 --- /dev/null +++ b/docs/developer-guide/theme/template-variables/archives.md @@ -0,0 +1,234 @@ +--- +title: 文章归档 +description: archives.html - /archives +--- + +## 路由信息 + +- 模板路径:`/templates/archives.html` +- 访问路径 + - `/archives` + - `/archives/:year` + - `/archives/:year/:month` + +## 变量 + +### archives + +#### 变量类型 + +[#UrlContextListResult](#urlcontextlistresultpostarchivevo) + +#### 示例 + +```html title="/templates/archives.html" + +

    +
      + +
    • + + +
    • +
      +
    +
    + +``` + +## 类型定义 + +### CategoryVo + +```json title="CategoryVo" +{ + "metadata": { + "name": "string", + "labels": { + "additionalProp1": "string" + }, + "annotations": { + "additionalProp1": "string" + }, + "creationTimestamp": "2022-11-20T13:06:38.512Z", + }, + "spec": { + "displayName": "string", + "slug": "string", + "description": "string", + "cover": "string", + "template": "string", + "priority": 0, + "children": [ + "string" + ] + }, + "status": { + "permalink": "string", + "postCount": 0, + "visiblePostCount": 0 + }, + "postCount": 0 +} +``` + +### TagVo + +```json title="TagVo" +{ + "metadata": { + "name": "string", + "labels": { + "additionalProp1": "string" + }, + "annotations": { + "additionalProp1": "string" + }, + "creationTimestamp": "2022-11-20T13:06:38.512Z", + }, + "spec": { + "displayName": "string", + "slug": "string", + "color": "#F9fEB1", + "cover": "string" + }, + "status": { + "permalink": "string", + "visiblePostCount": 0, + "postCount": 0 + }, + "postCount": 0 +} +``` + +### ListedPostVo + +```json title="ListedPostVo" +{ + "metadata": { + "name": "string", + "labels": { + "additionalProp1": "string" + }, + "annotations": { + "additionalProp1": "string" + }, + "creationTimestamp": "2022-11-20T13:06:38.505Z", + }, + "spec": { + "title": "string", + "slug": "string", + "releaseSnapshot": "string", + "headSnapshot": "string", + "baseSnapshot": "string", + "owner": "string", + "template": "string", + "cover": "string", + "deleted": false, + "publish": false, + "publishTime": "2022-11-20T13:06:38.505Z", + "pinned": false, + "allowComment": true, + "visible": "PUBLIC", + "priority": 0, + "excerpt": { + "autoGenerate": true, + "raw": "string" + }, + "categories": [ + "string" + ], + "tags": [ + "string" + ], + "htmlMetas": [ + { + "additionalProp1": "string" + } + ] + }, + "status": { + "permalink": "string", + "excerpt": "string", + "inProgress": true, + "commentsCount": 0, + "contributors": [ + "string" + ] + }, + "categories": "List<#CategoryVo>", + "tags": "List<#TagVo>", + "contributors": [ + { + "name": "string", + "displayName": "string", + "avatar": "string", + "bio": "string" + } + ], + "owner": { + "name": "string", + "displayName": "string", + "avatar": "string", + "bio": "string" + }, + "stats": { + "visit": 0, + "upvote": 0, + "comment": 0 + } +} +``` + +- [#CategoryVo](#categoryvo) +- [#TagVo](#tagvo) + +### PostArchiveVo + +```json title="PostArchiveVo" +{ + "year": "string", + "months": [ + { + "month": "string", + "posts": "#ListedPostVo" + } + ] +} +``` + +- [#ListedPostVo](#listedpostvo) + +### UrlContextListResult + +```json title="UrlContextListResult" +{ + "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) diff --git a/docs/developer-guide/theme/template-variables/categories.md b/docs/developer-guide/theme/template-variables/categories.md new file mode 100644 index 0000000..18e6bf0 --- /dev/null +++ b/docs/developer-guide/theme/template-variables/categories.md @@ -0,0 +1,84 @@ +--- +title: 文章分类集合 +description: categories.html - /categories +--- + +## 路由信息 + +- 模板路径:`/templates/categories.html` +- 访问路径:`/categories` + +## 变量 + +### categories + +#### 变量类型 + +List<[#CategoryTreeVo](#categorytreevo)> + +#### 示例 + +```html title="/templates/categories.html" +
      +
    • +
    +``` + +```html title="/templates/category-tree.html" + +``` + +### _templateId + +#### 变量值 + +`categories` + +## 类型定义 + +### CategoryTreeVo + +```json title="CategoryTreeVo" +{ + "metadata": { + "name": "string", + "labels": { + "additionalProp1": "string" + }, + "annotations": { + "additionalProp1": "string" + }, + "creationTimestamp": "2022-11-20T14:18:49.230Z", + }, + "spec": { + "displayName": "string", + "slug": "string", + "description": "string", + "cover": "string", + "template": "string", + "priority": 0, + "children": [ + "string" + ] + }, + "status": { + "permalink": "string", + "postCount": 0, + "visiblePostCount": 0 + }, + "children": "List<#CategoryTreeVo>", + "parentName": "string", + "postCount": 0 +} +``` + +- [#CategoryTreeVo](#categorytreevo) diff --git a/docs/developer-guide/theme/template-variables/category.md b/docs/developer-guide/theme/template-variables/category.md new file mode 100644 index 0000000..e5d571a --- /dev/null +++ b/docs/developer-guide/theme/template-variables/category.md @@ -0,0 +1,225 @@ +--- +title: 分类归档 +description: category.html - /categories/:slug +--- + +## 路由信息 + +- 模板路径:`/templates/category.html` +- 访问路径:`/categories/:slug` + +## 变量 + +### category + +#### 变量类型 + +[#CategoryVo](#categoryvo) + +### posts + +#### 变量类型 + +[#UrlContextListResult](#urlcontextlistresultlistedpostvo) + +#### 示例 + +```html title="/templates/category.html" +
    +

    +
      +
    • + +
    • +
    + +
    +``` + +### _templateId + +#### 变量值 + +`category` + +## 类型定义 + +### CategoryVo + +```json title="CategoryVo" +{ + "metadata": { + "name": "string", + "labels": { + "additionalProp1": "string" + }, + "annotations": { + "additionalProp1": "string" + }, + "creationTimestamp": "2022-11-20T13:06:38.512Z", + }, + "spec": { + "displayName": "string", + "slug": "string", + "description": "string", + "cover": "string", + "template": "string", + "priority": 0, + "children": [ + "string" + ] + }, + "status": { + "permalink": "string", + "postCount": 0, + "visiblePostCount": 0 + }, + "postCount": 0 +} +``` + +### TagVo + +```json title="TagVo" +{ + "metadata": { + "name": "string", + "labels": { + "additionalProp1": "string" + }, + "annotations": { + "additionalProp1": "string" + }, + "creationTimestamp": "2022-11-20T13:06:38.512Z", + }, + "spec": { + "displayName": "string", + "slug": "string", + "color": "#F9fEB1", + "cover": "string" + }, + "status": { + "permalink": "string", + "visiblePostCount": 0, + "postCount": 0 + }, + "postCount": 0 +} +``` + +### ListedPostVo + +```json title="ListedPostVo" +{ + "metadata": { + "name": "string", + "labels": { + "additionalProp1": "string" + }, + "annotations": { + "additionalProp1": "string" + }, + "creationTimestamp": "2022-11-20T13:06:38.505Z", + }, + "spec": { + "title": "string", + "slug": "string", + "releaseSnapshot": "string", + "headSnapshot": "string", + "baseSnapshot": "string", + "owner": "string", + "template": "string", + "cover": "string", + "deleted": false, + "publish": false, + "publishTime": "2022-11-20T13:06:38.505Z", + "pinned": false, + "allowComment": true, + "visible": "PUBLIC", + "priority": 0, + "excerpt": { + "autoGenerate": true, + "raw": "string" + }, + "categories": [ + "string" + ], + "tags": [ + "string" + ], + "htmlMetas": [ + { + "additionalProp1": "string" + } + ] + }, + "status": { + "permalink": "string", + "excerpt": "string", + "inProgress": true, + "commentsCount": 0, + "contributors": [ + "string" + ] + }, + "categories": "List<#CategoryVo>", + "tags": "List<#TagVo>", + "contributors": [ + { + "name": "string", + "displayName": "string", + "avatar": "string", + "bio": "string" + } + ], + "owner": { + "name": "string", + "displayName": "string", + "avatar": "string", + "bio": "string" + }, + "stats": { + "visit": 0, + "upvote": 0, + "comment": 0 + } +} +``` + +- [#CategoryVo](#categoryvo) +- [#TagVo](#tagvo) + +### UrlContextListResult + +```json title="UrlContextListResult" +{ + "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) diff --git a/docs/developer-guide/theme/template-variables/index_.md b/docs/developer-guide/theme/template-variables/index_.md new file mode 100644 index 0000000..9d3f451 --- /dev/null +++ b/docs/developer-guide/theme/template-variables/index_.md @@ -0,0 +1,218 @@ +--- +title: 首页 +description: index.html - / +--- + +## 路由信息 + +- 模板路径:`/templates/index.html` +- 访问路径:`/` + +## 变量 + +### posts + +#### 变量类型 + +[#UrlContextListResult](#urlcontextlistresultlistedpostvo) + +#### 示例 + +```html title="/templates/index.html" +
    +
      +
    • + +
    • +
    + +
    +``` + +### _templateId + +#### 变量值 + +`index` + +## 类型定义 + +### CategoryVo + +```json title="CategoryVo" +{ + "metadata": { + "name": "string", + "labels": { + "additionalProp1": "string" + }, + "annotations": { + "additionalProp1": "string" + }, + "creationTimestamp": "2022-11-20T13:06:38.512Z", + }, + "spec": { + "displayName": "string", + "slug": "string", + "description": "string", + "cover": "string", + "template": "string", + "priority": 0, + "children": [ + "string" + ] + }, + "status": { + "permalink": "string", + "postCount": 0, + "visiblePostCount": 0 + }, + "postCount": 0 +} +``` + +### TagVo + +```json title="TagVo" +{ + "metadata": { + "name": "string", + "labels": { + "additionalProp1": "string" + }, + "annotations": { + "additionalProp1": "string" + }, + "creationTimestamp": "2022-11-20T13:06:38.512Z", + }, + "spec": { + "displayName": "string", + "slug": "string", + "color": "#F9fEB1", + "cover": "string" + }, + "status": { + "permalink": "string", + "visiblePostCount": 0, + "postCount": 0 + }, + "postCount": 0 +} +``` + +### ListedPostVo + +```json title="ListedPostVo" +{ + "metadata": { + "name": "string", + "labels": { + "additionalProp1": "string" + }, + "annotations": { + "additionalProp1": "string" + }, + "creationTimestamp": "2022-11-20T13:06:38.505Z", + }, + "spec": { + "title": "string", + "slug": "string", + "releaseSnapshot": "string", + "headSnapshot": "string", + "baseSnapshot": "string", + "owner": "string", + "template": "string", + "cover": "string", + "deleted": false, + "publish": false, + "publishTime": "2022-11-20T13:06:38.505Z", + "pinned": false, + "allowComment": true, + "visible": "PUBLIC", + "priority": 0, + "excerpt": { + "autoGenerate": true, + "raw": "string" + }, + "categories": [ + "string" + ], + "tags": [ + "string" + ], + "htmlMetas": [ + { + "additionalProp1": "string" + } + ] + }, + "status": { + "permalink": "string", + "excerpt": "string", + "inProgress": true, + "commentsCount": 0, + "contributors": [ + "string" + ] + }, + "categories": "List<#CategoryVo>", + "tags": "List<#TagVo>", + "contributors": [ + { + "name": "string", + "displayName": "string", + "avatar": "string", + "bio": "string" + } + ], + "owner": { + "name": "string", + "displayName": "string", + "avatar": "string", + "bio": "string" + }, + "stats": { + "visit": 0, + "upvote": 0, + "comment": 0 + } +} +``` + +- [#CategoryVo](#categoryvo) +- [#TagVo](#tagvo) + +### UrlContextListResult + +```json title="UrlContextListResult" +{ + "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) diff --git a/docs/developer-guide/theme/template-variables/page.md b/docs/developer-guide/theme/template-variables/page.md new file mode 100644 index 0000000..4aa1bb6 --- /dev/null +++ b/docs/developer-guide/theme/template-variables/page.md @@ -0,0 +1,109 @@ +--- +title: 独立页面 +description: page.html - /:slug +--- + +## 路由信息 + +- 模板路径:`/templates/page.html` +- 访问路径:`/:slug` + +## 变量 + +### singlePage + +#### 变量类型 + +[#SinglePageVo](#singlepagevo) + +#### 示例 + +```html title="/templates/page.html" +
    +

    +
    +
    +``` + +### _templateId + +#### 变量值 + +`page` + +## 类型定义 + +### SinglePageVo + +```json title="SinglePageVo" +{ + "metadata": { + "name": "string", + "labels": { + "additionalProp1": "string" + }, + "annotations": { + "additionalProp1": "string" + }, + "creationTimestamp": "2022-11-20T14:29:44.601Z", + }, + "spec": { + "title": "string", + "slug": "string", + "releaseSnapshot": "string", + "headSnapshot": "string", + "baseSnapshot": "string", + "owner": "string", + "template": "string", + "cover": "string", + "deleted": false, + "publish": false, + "publishTime": "2022-11-20T14:29:44.601Z", + "pinned": false, + "allowComment": true, + "visible": "PUBLIC", + "priority": 0, + "excerpt": { + "autoGenerate": true, + "raw": "string" + }, + "htmlMetas": [ + { + "additionalProp1": "string" + } + ] + }, + "status": { + "permalink": "string", + "excerpt": "string", + "inProgress": true, + "commentsCount": 0, + "contributors": [ + "string" + ] + }, + "stats": { + "visit": 0, + "upvote": 0, + "comment": 0 + }, + "contributors": [ + { + "name": "string", + "displayName": "string", + "avatar": "string", + "bio": "string" + } + ], + "owner": { + "name": "string", + "displayName": "string", + "avatar": "string", + "bio": "string" + }, + "content": { + "raw": "string", + "content": "string" + } +} +``` diff --git a/docs/developer-guide/theme/template-variables/post.md b/docs/developer-guide/theme/template-variables/post.md new file mode 100644 index 0000000..c554038 --- /dev/null +++ b/docs/developer-guide/theme/template-variables/post.md @@ -0,0 +1,182 @@ +--- +title: 文章 +description: post.html - /archives/:slug +--- + +## 路由信息 + +- 模板路径:`/templates/post.html` +- 访问路径:`/archives/:slug` + +## 变量 + +### post + +#### 变量类型 + +[#PostVo](#postvo) + +#### 示例 + +```html title="/templates/post.html" +
    +

    +
    +
    +``` + +### _templateId + +#### 变量值 + +`post` + +## 类型定义 + +### CategoryVo + +```json title="CategoryVo" +{ + "metadata": { + "name": "string", + "labels": { + "additionalProp1": "string" + }, + "annotations": { + "additionalProp1": "string" + }, + "creationTimestamp": "2022-11-20T13:06:38.512Z", + }, + "spec": { + "displayName": "string", + "slug": "string", + "description": "string", + "cover": "string", + "template": "string", + "priority": 0, + "children": [ + "string" + ] + }, + "status": { + "permalink": "string", + "postCount": 0, + "visiblePostCount": 0 + }, + "postCount": 0 +} +``` + +### TagVo + +```json title="TagVo" +{ + "metadata": { + "name": "string", + "labels": { + "additionalProp1": "string" + }, + "annotations": { + "additionalProp1": "string" + }, + "creationTimestamp": "2022-11-20T13:06:38.512Z", + }, + "spec": { + "displayName": "string", + "slug": "string", + "color": "#F9fEB1", + "cover": "string" + }, + "status": { + "permalink": "string", + "visiblePostCount": 0, + "postCount": 0 + }, + "postCount": 0 +} +``` + +### PostVo + +```json title="PostVo" +{ + "metadata": { + "name": "string", + "labels": { + "additionalProp1": "string" + }, + "annotations": { + "additionalProp1": "string" + }, + "creationTimestamp": "2022-11-20T12:45:43.888Z", + }, + "spec": { + "title": "string", + "slug": "string", + "releaseSnapshot": "string", + "headSnapshot": "string", + "baseSnapshot": "string", + "owner": "string", + "template": "string", + "cover": "string", + "deleted": false, + "publish": false, + "publishTime": "2022-11-20T12:45:43.888Z", + "pinned": false, + "allowComment": true, + "visible": "PUBLIC", + "priority": 0, + "excerpt": { + "autoGenerate": true, + "raw": "string" + }, + "categories": [ + "string" + ], + "tags": [ + "string" + ], + "htmlMetas": [ + { + "additionalProp1": "string" + } + ] + }, + "status": { + "permalink": "string", + "excerpt": "string", + "commentsCount": 0, + "contributors": [ + "string" + ] + }, + "categories": "List<#CategoryVo>", + "tags": "List<#TagVo>", + "contributors": [ + { + "name": "string", + "displayName": "string", + "avatar": "string", + "bio": "string" + } + ], + "owner": { + "name": "string", + "displayName": "string", + "avatar": "string", + "bio": "string" + }, + "stats": { + "visit": 0, + "upvote": 0, + "comment": 0 + }, + "content": { + "raw": "string", + "content": "string" + } +} +``` + +- [#CategoryVo](#categoryvo) +- [#TagVo](#tagvo) diff --git a/docs/developer-guide/theme/template-variables/tag.md b/docs/developer-guide/theme/template-variables/tag.md new file mode 100644 index 0000000..8a968e0 --- /dev/null +++ b/docs/developer-guide/theme/template-variables/tag.md @@ -0,0 +1,225 @@ +--- +title: 标签归档 +description: tag.html - /tags/:slug +--- + +## 路由信息 + +- 模板路径:`/templates/tag.html` +- 访问路径:`/tags/:slug` + +## 变量 + +### tag + +#### 变量类型 + +[#TagVo](#tagvo) + +### posts + +#### 变量类型 + +[#UrlContextListResult](#urlcontextlistresultlistedpostvo) + +#### 示例 + +```html title="/templates/tag.html" +
    +

    +
      +
    • + +
    • +
    + +
    +``` + +### _templateId + +#### 变量值 + +`tag` + +## 类型定义 + +### CategoryVo + +```json title="CategoryVo" +{ + "metadata": { + "name": "string", + "labels": { + "additionalProp1": "string" + }, + "annotations": { + "additionalProp1": "string" + }, + "creationTimestamp": "2022-11-20T13:06:38.512Z", + }, + "spec": { + "displayName": "string", + "slug": "string", + "description": "string", + "cover": "string", + "template": "string", + "priority": 0, + "children": [ + "string" + ] + }, + "status": { + "permalink": "string", + "postCount": 0, + "visiblePostCount": 0 + }, + "postCount": 0 +} +``` + +### TagVo + +```json title="TagVo" +{ + "metadata": { + "name": "string", + "labels": { + "additionalProp1": "string" + }, + "annotations": { + "additionalProp1": "string" + }, + "creationTimestamp": "2022-11-20T13:06:38.512Z", + }, + "spec": { + "displayName": "string", + "slug": "string", + "color": "#F9fEB1", + "cover": "string" + }, + "status": { + "permalink": "string", + "visiblePostCount": 0, + "postCount": 0 + }, + "postCount": 0 +} +``` + +### ListedPostVo + +```json title="ListedPostVo" +{ + "metadata": { + "name": "string", + "labels": { + "additionalProp1": "string" + }, + "annotations": { + "additionalProp1": "string" + }, + "creationTimestamp": "2022-11-20T13:06:38.505Z", + }, + "spec": { + "title": "string", + "slug": "string", + "releaseSnapshot": "string", + "headSnapshot": "string", + "baseSnapshot": "string", + "owner": "string", + "template": "string", + "cover": "string", + "deleted": false, + "publish": false, + "publishTime": "2022-11-20T13:06:38.505Z", + "pinned": false, + "allowComment": true, + "visible": "PUBLIC", + "priority": 0, + "excerpt": { + "autoGenerate": true, + "raw": "string" + }, + "categories": [ + "string" + ], + "tags": [ + "string" + ], + "htmlMetas": [ + { + "additionalProp1": "string" + } + ] + }, + "status": { + "permalink": "string", + "excerpt": "string", + "inProgress": true, + "commentsCount": 0, + "contributors": [ + "string" + ] + }, + "categories": "List<#CategoryVo>", + "tags": "List<#TagVo>", + "contributors": [ + { + "name": "string", + "displayName": "string", + "avatar": "string", + "bio": "string" + } + ], + "owner": { + "name": "string", + "displayName": "string", + "avatar": "string", + "bio": "string" + }, + "stats": { + "visit": 0, + "upvote": 0, + "comment": 0 + } +} +``` + +- [#CategoryVo](#categoryvo) +- [#TagVo](#tagvo) + +### UrlContextListResult + +```json title="UrlContextListResult" +{ + "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) diff --git a/docs/developer-guide/theme/template-variables/tags.md b/docs/developer-guide/theme/template-variables/tags.md new file mode 100644 index 0000000..a1b0920 --- /dev/null +++ b/docs/developer-guide/theme/template-variables/tags.md @@ -0,0 +1,61 @@ +--- +title: 文章标签集合 +description: tags.html - /tags +--- +## 路由信息 + +- 模板路径:`/templates/tags.html` +- 访问路径:`/tags` + +## 变量 + +### tags + +#### 变量类型 + +List<[#TagVo](#tagvo)> + +#### 示例 + +```html title="/templates/tags.html" +
      +
    • +
    +``` + +### _templateId + +#### 变量值 + +`tags` + +## 类型定义 + +### TagVo + +```json title="TagVo" +{ + "metadata": { + "name": "string", + "labels": { + "additionalProp1": "string" + }, + "annotations": { + "additionalProp1": "string" + }, + "creationTimestamp": "2022-11-20T13:06:38.512Z", + }, + "spec": { + "displayName": "string", + "slug": "string", + "color": "#F9fEB1", + "cover": "string" + }, + "status": { + "permalink": "string", + "visiblePostCount": 0, + "postCount": 0 + }, + "postCount": 0 +} +``` diff --git a/docusaurus.config.js b/docusaurus.config.js index 17b91e3..4542b35 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -63,6 +63,11 @@ const config = { themeConfig: /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ ({ + docs: { + sidebar: { + autoCollapseCategories: true, + }, + }, navbar: { title: "Halo Documents", logo: { diff --git a/sidebars.js b/sidebars.js index c60e186..c040e94 100644 --- a/sidebars.js +++ b/sidebars.js @@ -19,12 +19,18 @@ module.exports = { { type: "category", label: "入门", + link: { + type: "generated-index", + }, collapsed: false, items: [ "getting-started/prepare", { type: "category", label: "安装指南", + link: { + type: "generated-index", + }, items: [ // "getting-started/install/linux", "getting-started/install/docker", @@ -48,10 +54,16 @@ module.exports = { { type: "category", label: "开发者指南", + link: { + type: "generated-index", + }, items: [ { type: "category", label: "系统开发", + link: { + type: "generated-index", + }, items: [ // "developer-guide/core/structure", "developer-guide/core/prepare", @@ -62,21 +74,66 @@ module.exports = { { type: "category", label: "插件开发", + link: { + type: "generated-index", + }, items: ["developer-guide/plugin/prepare"], }, { type: "category", label: "主题开发", + link: { + type: "generated-index", + }, items: [ "developer-guide/theme/prepare", + "developer-guide/theme/config", "developer-guide/theme/structure", - // "developer-guide/theme/config-files", - // "developer-guide/theme/global-variable", - // "developer-guide/theme/public-template-tag", - // "developer-guide/theme/page-variable", - // "developer-guide/theme/template-tag", + "developer-guide/theme/template-route-mapping", + "developer-guide/theme/static-resources", + "developer-guide/theme/settings", + { + type: "category", + label: "模板变量", + link: { + type: "doc", + id: "developer-guide/theme/template-variables", + }, + items: [ + "developer-guide/theme/template-variables/index_", + "developer-guide/theme/template-variables/post", + "developer-guide/theme/template-variables/page", + "developer-guide/theme/template-variables/archives", + "developer-guide/theme/template-variables/tags", + "developer-guide/theme/template-variables/tag", + "developer-guide/theme/template-variables/categories", + "developer-guide/theme/template-variables/category", + ], + }, + { + type: "category", + label: "Finder API", + link: { + type: "doc", + id: "developer-guide/theme/finder-apis", + }, + items: [ + "developer-guide/theme/finder-apis/category", + "developer-guide/theme/finder-apis/tag", + "developer-guide/theme/finder-apis/post", + "developer-guide/theme/finder-apis/single-page", + "developer-guide/theme/finder-apis/comment", + "developer-guide/theme/finder-apis/contributor", + "developer-guide/theme/finder-apis/menu", + "developer-guide/theme/finder-apis/site-stats", + "developer-guide/theme/finder-apis/theme", + "developer-guide/theme/finder-apis/plugin", + ], + }, + "developer-guide/theme/code-snippets", ], }, + "developer-guide/form-schema", // { // type: "link", // label: "REST API", @@ -87,6 +144,9 @@ module.exports = { { type: "category", label: "参与贡献", + link: { + type: "generated-index", + }, items: ["contribution/issue", "contribution/pr"], }, "about", diff --git a/static/img/formkit/formkit-repeater.png b/static/img/formkit/formkit-repeater.png new file mode 100644 index 0000000000000000000000000000000000000000..73dc53253fc7393a4630ef11054acf68ba3914c6 GIT binary patch literal 37687 zcmeFZby!s2_dkk&v@oM04dbAobVxT0VFJ=ENJ@8u^e6%XBGO0*($YOjgM@U0ba&_6 zGx!PS{e6Ev_x^RC=l<@O=NadC=InjWUVH7eUhB2iK5rDB$zCI%B*4JHxb{#E_8bEP z8;XH}`2r6IIHMO{M~s0%kZmRlA{`DaD{xU3{o>6Lk@6=j3f<{oQjIn^Hu)1Zgv+CF8Yr?t8fBTBu9?C7o!IURCDJK=quv0X{b zFWSw&$Ud~2u@Mnocv*LMtB#1TRk!ZLi-*E|jat+4T|`ki9pa)bMXwT?3pDpD_-$kozy(Lr$}|aXPhMo z5bsu?Zt%l?Pp(O5>9hSAnt4-@NJRo6Zu0r@FUL$=H;ilIslTWex!?J=76|9yto#Um zA=v)<>^FxCMZ2F`-LMUDH4U2&^Y0rr^d9)N_>+DoQ_-8)1j%yuq(3mU-ISKCNcC0V z%*6hHS3(;V#3ZAV{7R8ElemvYS?m?j9)5_lQul-6`y=2rKVsRUZszWl?yHN>cWoW$ zD``A&MPAd&deS)0+YNr|XA~yy?CY*hsh1OJvsuxtW3OYb^IG8@faUAVf3t4s z9+FpMASm?S1;olTN>!*1G;CO}dbg_4d|7s)u*el+w)8uJkY#QV+vdpM~BHZXIonnhl=Coh`db zARRXS=*?$Cg;J%gd(rph@6)AcJ~w{;- z`&^_-Hwj5sNH>_hbdyV8e|uf^V$^r!Y8jb_f@bDyR)b2LQkGI>#;!)pm({QFIn_!P z50BmuXg10jy=h~L@?%m{Zo1XR?x5YG`>CwJYf4H*ycB6tRyS=z zwT7Vv?0MJoi&v>v$oWgGw?4r?9{CaYRRyX`Rq7W`U8%RCA8Kj3*PYqE_>t4X!n~bN z^u^dqU(RoQ;7`h*c4H%+QhbT|!t({o_^t7|aa>1iyDn>w#?2zrg_Ka~$#%A1EuK#e%z@pS0BVhf;0)Y;0Y${2gExFqMWNxv-X$mh(($#g!B* znQYijYTQd3PB3S)0mDZ(w(%l|djkx-eEa8IXGR)7^Vroi5(`gH36@B6NHbM!R7V%&+w)3>4h05*d|O<9vj$BlTRk- zwRtDZMk*)n-WIr5!R5+ymn)r(G>J0WO%=i@cdIz#Ylcz!9$rX5McsK6G4nwVHQCXd z&~&M#pFMHhv*AsdN*0iF3E}rk@fshGY*iYvdNm3)d~-VcWwOh1X*>7Z&Rws$U2}gr zs@YKzH$a^8GDoPIR+A1LniMma`WkZsFn={&LS?5?_6p=+U|T^n7lR$Yc@K%}`Q zb=K0H)P7+2IKgbsoIRRf={z$jZ8 z`}kJ5{T|!SdWXBuf!lh}*lfke0{4d72QkP1(9&q=cmehW{n$x zh{3(l{Uo|g@LXVdyMD5<=!pD4J+0a!Pj}%cL7uyYZ`8SSF$gGZBr!3pnXV+mu*BDJUY9$RCM9y3 z?u&gV@33Dt2+qNi&fO($%T@@+ zv7D-Zd4@Ct)ekX?(jX+;hIglU%5m9|o=5@KJ%dH%NC3UQHBx_Q{PZaX6L5@&fqjJ% z<0^1;1^9?wq59Xc%$0i>SQqCpF))J6FtGn|%`@N|_4gk5pxXTT#)=5Uzy<4CK|I*IP#=*?m z3WDla-@w|@K?Dj#-RNI`f1cCG#q8gAva7}3k+Em5f$WF@I61b;>=)dRd zANRfd<3Ab-v!jN7NfdwNd~p^Kv?zfv`@bell%UpY0}jk1wHZua8TbaY4E1+~1Ni6O zpKsK$tj$;2I!O!+ag2vB31ycntK-hq)G9y3en-B?mc;zbjP2`S_7-~B!vbOYNR|Uv zR7jIkbHi7`{QNlb7@C472flhmQ4!War~aP!xzu&oD~Pz z6otTlc{~W*hUD_oqS|>fK;VnquNa~K9>hg=*x>cQv=&e9!o~)B%B;cu@@TIibs~Rh zjkg=~8j{kLnE?N%Wd8AtAXqu>Uq;{~dIJR8r6{=p{)>2g@ecmd8uH5o(-$t9>ID6Z z+yUXz{-rhc|I5PVNcMwM)V20q=lUt8zP2hwRDM+V_{QN>;ar-o z8-+e=KC0L_79iU;3bMfLyqepb0>*W<`ae7P*1Anb*A9pxGoC!&uUcjlGyR$Dxjt4g zRirm^zj}9jH*JDdwD2MSKK#th;&Ba)aqW@u;mYL3*8`=e!q`2QHBMQ>&J)$H4~O61mU`ut&2rc*wm~9aOY=(kBj1i|iHa)0N=}IzchFEY z+Faqh|Ni6Oqi`|!=bfjM)jnL*xome^PgJL27f+f+k(Nyw<_*L+M$fn%ko;6av_LR!%+*1I4_#0;> zfV7D%`gFgQRqW<~ZqfeMYhyonTeDajqvcLemqm#*|LSXz2u)@`I7zkjc+U(&oTI=f zeV8b1-g)EGhZ^+U(CK5a-mrfD_DlK=jfL^49HlV)`k-qo&#jNsx!^;V8C&`_2RRAn zeQT3d8x>;l_EYJtqQ)?X$8)lmACbt#=L#uoOx?}m_otp2ED=jtn&X7cH({Qc#$~`0 zMb>(ImCr}-;GXkDm3)OT!-v)%BdMGwk5;Av$QHU<$hnQf4bR|M#6qG1_VN-9TGlnB z!~)w5o6VyePxix#ZnK?CQRv5{N4HzJ8i8!5zXkbs@J>MNC)?<(PIh#rzT@wV-i2p= z*owKVGJf(XvDu#a@~f|#nX7@eg7wZA#*PNP2SmnmG>Ynf0$~Zx{;)Y)0~Vds0W4h(dB#HCb(Sc3dR#3p+fKK+Sup>S1latZ!bxzn|z4OzIdas}-EC`xA12sE*WrRXrXOEZ6xR9ziVB7hIswUQ? z3hOx%rHnJ7C#iF{p82_X#8zGr_eKwZsaR)6}TNf4FS*@<1hSk>%mLEW~_WW?6* z`mjJ6XQ6^>d5`qDw3o!hkJ-MdSL7T^O+XCl_a>><-8aLxD=EI6mk-nrmY#T!kJM)i z&D2Ioz)mOH7~rPT9h{d}9yvcImMHww>264)>b{6s-*Iw3KcrS)p^^+R#P#3wvyYM&f?Wd1#3{aBN}UadVj%R z>aq27i`!F^jyI%#G2FcPL_>f!LbqyebQ0NK`B~0dJM?L@2rz*%VVeA_?wjAHCc8w{ zqz)UDBaT8w_~0~HKSP9=fbL|GK|i5n0IxaGMKyYls#9#IykLD~gKcp9 z3CLF6tf`)a?eL+C{Xhr&@RFI@5}evQ;tLQa}e; zM8rO&xZwQgbHd}t>&=Q{hoZ!au;i`2f^&lRC004@xz$GJzJ%)y`6XjvXj zDbOfr+x_^eU!pRhGQsXue#Y)ictR)mO`CV=lV4pB-`R`ulSJG)tH~GzHnH(P8hFzlD?Vn8&%ZEU z5|fcn5~UYDsC)cS>J~+fjmu#9+{j2~DK>cN2^>rx7-ahs!TnC#^hYUgVR+j#Jw?({ zUFaOowP{Q^qH^iu_Gd;qXZ2_byg^+rFF4^{RjXWV@YW%@u(H_t*5QrFdK50v_9VLB zDZ1v~n#xqQ>MHk1*hurJEJ@Ev*JVMd^2?Jh#HNAztJXa3YgvA9atO6Y1O7u1TahHCEc6a3Ipo-!9~e@0D0!?@KJ(GM&W}Wla(`^2r9jPWs_l@eYdN%J%X z(LATrZkwrVDAYmd+pcj43M`zBtW7RM3|8$nSXkR!z3kef()oOa1zaCVNx+^|lpeLQ zO^J2Yyr*_QQy~ku8?Bx*YM&vNXx9`lm1S-%hbY}W7>LWm<9uI$7F38Y5HxU1cQ-E} zbnK#{K3b2)f-C&s4!kfA>Y0QM^+LU?$m0X|Iwg5|LcO8m$cI9_Bd0ra`wbAN6+7be zk-9@W)RB(S6c~J$bAJh6pi*Z!#4Bfbdh~wnc4ODT2NP0dTrj7C9NFAyhojj2Ty|S; z;CXY9@9f!#xAx*~M?DwvgYvBpDrW8Fksh|ER{G1_UHipaWrz&V?zLxLcP@E(3?DDD z!-FFBJ7H$U_NuUT$aJ`D5))wUoXldqkez{1N$bMcg-hncL)nB>JTGDjAza!U6K*THn_H}+H}j4fL*^79#Ori!v)?$DJ{;xO zjXCEhNNRs7vQun#2!X4w-H;cdsXgxSjNoP|+KrfsO-dYnsWt8D?OJc0t=YdbC}2PT zq3#o5B+X3abw2H#`b3gKT=W%EB`(Np@S`VZzh6z~8|zvt4Q%Y-_siBw+?AY)M@g(t zh8Lp_08!$em?SwrypGdnZcjwNKje5)(2C5_+gPyrIi2IrFLc|=HX#VfTC+AyhpZNB z7z|Lg(vX0SnIvI_5<8!-CAF^OdN~hnR&;r#Z2v0t^x7BeVhr6xp_<1 z#n9z!kB=#iVgfNCsNbdIjAx$Lt)9_DHC2!*Pi#BYQm(?*u5nf_TLUqekFyR$0j$|v zvZcDnZ;5nzE}BOjbX)FKrS2Qv_f`uBKRe-<5{{db6%~e2XZyIO^l`zxjt1M>8)^1t9O-B51+EE4FYBRRnfG} z9JIq;$>Lho64ohnH{NLg8Bn$12^r?AHGa^kN-kjcdDgw^{Aj7mi=tEbU^=Zb-*e7h zKTM}~A}_073e5%7ut4CZnB^1i;0Xu9{9^~->7VvJuQIP3G9aR(b=lOLA}Kq&8>7G3 z^Rw67DbyU=Q0Cw<{gUp{#Ha9e5Zx$$NwHO(9@5rYvu|!Aa0S6FzPC?NwJKB>B^Dz#I>jus8b!3WJb%*qGO0 zBy(gl)AT}lUc;6w9r-OCLxK@nM6{`^(Pwy)w z30gxOAA!i)o|FxzmuarnOe(h0Q_9j7$`{HXYjJx!kDvUq>GC8oqAfHr)9>6GwTe8d zqC;bcl6L^g>FI9kdC)T=z|B41mTQn+h#zVQ580rk3p!leuqH{Gc(qd?ZFalq+s~S! zEx(mj0h7)#o54vWg;n(t2`UU_Vl}Anbj=?wG6~W-5M^m-W)wy2G1mX;$x83n(!K%3 z0WZyvpqwcG=_jQ~1*qt#_J~af-MYifhE5Fhf86{l!fXjHpT1x#M!Y2N6$B0w(DUd!rcr z^a~CGJSfh~{3>HK4Tb!Y2RJ-0UuRA9(;VbivB7@d26ed6#0Xks1T^3X^GY`oq7Pl%nv)EBN%wBXM-@rhz0o=2!XL5QzCj*q-*^sh6wel?H?U*G5I zEj+?da$1>C?R)RYMRQKDVox0vu))20F<@PHPvgi3PK@Z9fGxvXtw3J1xL7JdbO@Pl zjMl~Ho{W!r9YV{)g@U6T7DEM~8?=83t~`9qkX~ND^$e-1_=Y;13dTAPFDX5R$#DkjcM*DfJj9tA_*Ff+31Ug;H-phuh#gus$u| zhx|<`!SC8K)=kmW=3dTg2-JHpBd5>mAw(; znDeN5o-#WyxHAS>F%P+VS+wMafYRE$*Kx|%vAUQafvWkC81DKh6jDO|oUH8YTeR@k z)3%i$7kM?s)~xHe7BcmLn{DyA>c&sX>|AO#(T&;JjG1TW66NcGlMG4DPRPUG$4gpwwl9kdXr0{`c^yEtJ z9LG)vU;Q56IU7qw!*pw1bVj19zRTv6Y~)N{)pi|_(|GLgb!Oe27O%?FL^8TCu{K`N zOmYawxf~S7O?Pk1(EsTQh8g1|Ot_?bq4{b~`(K;=5Aw^N z)g*nm9DBbI$4{H7zv8HP=E8Yl1s)8!I!~U6A5Ps>`-tVUTTp(h;hXF#Ss*J1OZ7&L zd60hnEjjPy?E_T)OlM#4PD8-GnOi3E+FPmI=CnT-?S75wRT4r`iJ+;)8F_X(r@8}T z;?%5xcsXxC?df~MrUuhxC2#G$?J5i+xuj%7+9(N}sQyy|JIc#ChUy8sp>U&s%Gsp zT(LXX!<=`G^Mzd5gKGF>GeB37a~im8_W~nI5{?M3<#HILK~UCim9T#aay+6MEIM3` zp6{xKJ8IjuYUp?!1YEXKSSlzb>01H*{wT>0+F-1sHRlQT1_^Huk@<-#Z~C0FbF8quYO0JSHnilYjwnW3CK@P^hh@-wOijC{Z~so@wUOY8w4_Fv~H*>tJ?G&bde06 zZhvV@sZ5%wi?YpqTi&P^aK0O4a_4sA5t+`dyqfd*w9`$$gGU3t2o-m>`wW#OXwn>9#)Se;e0@*%mVo0fECwNx|F}n1syWZ@=?$5WIg4qI8^eC@EJB5!jZ5qC1+?{HnV5CtIoOF+f zou2-vc77T-yW@{_K*U+_Nx`jgapT;^{kfC+*H!LxE;QtV;|I7HYNEjrvcXTz@s={M zI5)G-_=+jtV0Mz|)7|At9yWl)Xw|&Fl6SUViBP-Mi2_^+C#uhPcUHV;YW3WNWGi(% zmjvd@j2rkIgm+tBPDLY*E%{9+tuw0p-6R_H)=y>2=fa%t=rGQgaH=7_AJ05H?VDaK;3fAX7SwJp^6qZcix}0aXc!bzkwB+#YP-wRV=|( zW$@#w>>Wgcw+7^C}Z6kOBxks%u@c_cPizv*R4dC1ecK% zkY2?GudOLdzyeKBASNs4Jhz7^fH5id`g#>|%H}}iAP6z$=v*i&H76RCBv#xS2!PmS zL?`z+d*?HNrLA2WWb-}{b1*o1N~Pm<&-3W}$-uQOl?2_W#9F@1a@B}69R_C$1B0p(esaC`K6?$aNlT2JxX&|3_3_sC>K$w3leR$(^F5p2&Ild1xaLy`eXZzjGg+eQ zj%gDEl_$hvufqCHbHnfHFXrAi14*bBFeQ+vCWZ}F8G$KUt1 zbp%*ayTj$slRKYfwP)>)zWoeDOnU0sr7(Lr7!LttSmdBj69w6Zo^`^u!N*bSgQJ4c zPQ7e==(H9$v7pEE!a(Vq*}jK2BGlaGtt-%=?5`sfzT45#e0S>)z7rQ~R&(+?&qjJO zH^!RhQ1YH9?bX*Dh$tr}6bzOjz@kMeFY_DX1$H`SZQt(p0qHw!XM_B+FeY4*leHQp zv$LS8wuOrIHp2Q3W=P@_8ZOS$)rg{ce}Lmc+3Dvh9tjJ$6Uf9$i3U_c9>_tZ86y6f zwC?FWxg5CqTmewCsr3N?luNi5g-$g+J(LZAT-2C?rh9H6=H)&U{JO+)l#7b4!6@gX#C4 z5!qkKfN~)wx-R~s^7=Z4bPXC_G_7#B&VdQwGW8>f?%G0vs|UHD zq1-qm<=8enxKn_$K~|<`hIuj;T>q|I$Nj8h%3rDLsi+zb*+U<1v}GrLwP$Mzqo!IE zk#m6q?Hn|$uYx0!5-CxM6;)j`?Vd_EZfIx4Z^o&JpYsSy`dX10-y(*n{5$3{4Sgnw z*HB4LSH!MjsGSeGTwVi6e%~nz0f*MS9u~$XfKL(>^1PV`${q+bGp18s8d`|DGB%}S#&gW|2N7^}Z%6-w_ znKtrEA+)xS2V>+85a&&k0awYW!J^xrL+Up5>w65mFJm2!7Z)~4^Pe`VEmQRVx<3SJ zIhp59r%6oLQ+RmrkBnYTVT%dc3^D-_pFfkWl8l}ABJiP3Py*7TjdW#W21S9d;zZ1y z_!r?f+D_(Uz~QQP1Fq5j00YSjq-S-Em+sbgv5SdmY!?0`sl6Qfy#N8U%E;*OB@gkO zdQx&LJI?B%=*D(Oe&DqMZ+YjcXzw@?cc-DVsvyQmGAeF{_y=3>x2^X?<6YMqr#{Ql z%6kc}XJXZTk|z5)yk_k@Nyb*=;RX8k9h2|_?VviJNAd2VG!ebUCCmdNZ$~LT2UYRT z$VJFvYGR!kd>M0K3qAw*rwKMN%+Te_|C#X@Ga7~FC_)87v`p>`mxl^=+z{WR`K zyir%}+Fx~uUUJldM?`U5hKTMKs>|{iI~0XxqGL$`3YJrUweT^YyZZdOFZ@aDM=ub}upk{}u#1lw8RQ88W+&0ZbFkc~ zKJ)o=zTyuf!q@i_N<+of6>Jw3w700=y+a zuldB?OTg!R)z`9_*MkIOgRUhHqXns~cn#vytv%j&UTYtnxD_$rKz?5k zcmUV?QvS)q&cglJY`oiC)o65HvJNKayK>}@=7?og4YQ*$a~Z%GJC#@$3`SlUV-jZt zK`N+d=(mwVt^1f3Mfc)Kz=Ygp5srA!bMx*wC6tkNJM7M=11bjn zhCwXyW+W+=t3y+$EDwu3-G$0o)p3CTrF2Wz0`G3EfP2t$&|4RpdXpJv9Dxo@^CmswDpNb#!Xpaes{vnaLt-KG3HPv0grvsB7exV~&+8JTlG z3L-xGWj13W0Sg)@`Rhxn#!G>j}K5xjzowNH?lDSC zP&ad$pte0mH~Q7ZrF~#+;^F_@bp+5X6R8I~axxx|WyYpQA5acwb@dMxL9xLUHNafG zR=mu@14!kj0NfO)QX#s%UIUb=$;RT}WTNX{T+smg!rvjW{=+Sp=%P`LjPSyp9;WKU z_Hy7deqp=9uIIp`?*ez?KuZ_^q;?86xZg*TUSD#LGfJnYZ7YOY$u(3s378|dznddP zU%0UeqXU^_noh4=!LK%!cg5$z-v8-pupc~ez})nXzg$&D21klr0$hd;_u)$Cjv6TZ zGvnWk8PMpI7!Y(VWxWpG21&`lxRaYtgX;e6e|Q*SAMvVC9(ri=)A4nx+}?;->IcM^ z)1%*$ubUnm7L;u=4Z=;GjD3N;Q9ef~}PLs0@|JwCjQ)-~kDy+X3S|aA3X}Ia#Qae?xv4FK6|MzPhx~-Zw^WLs(n_PO2 z1zwytq3I3HMN}bxZfX8*CAUbx?&KvlzAtPRH)#JP9a(0WW)$T;qj{q@0})aE^yTsH zk#(P(w{`RC+u}S`B(Oea|8^X>;BWzR8L5%0oa&ilS$;unlbSJ#imo12qJ&VcJAi8b zk0k}{6o)O7%d{hfSdT_m#W-_N6Vgs`vtuCI*&7&O^4|=QL%|pRp=ww>r56*vD~JvL z3hM`ol1SnCmjk+cpT3I&B`HU}ju0S>^=wbsq3d0=I)ghP&rg}*Ex!O0^*^jJjvYwx zJzM7J6A75kN6_Dl5Quk7oIU{4sQ14={vR&{A0KMi6EC+{F0Z!x(g5ffa|=~vTcM^( z&ByyrOW)$iJ;M{Q7Q67jQ_)|(aB|VH{e`N!*)lA6VkJDLE@P;)9ZH=Qg zB6=JWTM_vBfREf@SSHzvhM8vTs;7jxW|Ox8w7wR*6rtPw-Zh=|tfy_#iRT`QG<)hf zU$Hs;xVIkI#zA$9LL@8du^rK6kcao+{^s(#!Me!EB>cxLq73hJoR%2E)+^lwMOQUP zswP9lmR4hLyhFC}K4q7T;Ypo-kF}rq6_HP{9a=QXvo4?S{QW98AHm5jyx;I)_PG76 zT-7jItu9dB2q5P~AP@PGgfL9|-eBvoAUrb>zdKZes7BEwI}35Vo zg%7w51j0-WKcNM^;<`TMl0dz!rV9Y?N4NlAz(_+kZ>dG!tk(6ybfJ>CM#GtY@Z~6p{F+4JP;eN9h}%@b^CdCt zvX?8_kQBjtrp-^_%PaYb6y0qzwEd2}Cpw_;+aFQ6XC~lDWt2Z2T?R#0FxN;;n=3(4 ztN@6QGOF3C5QH!0I#M$YCM|{(q%y``5Q=Ws^NvOGD7G%*v-C78V52~5wn(R3xa)Es`8RU<-l~z_d?K+^IHZWQ{!GV-Y)^G83sk2IPgV&-rfq%5l^*C zT3=ocG4H&Hw)otn6E2%~$S+PzPcTebl&xFN1!TK?6))YOX%`)A&sCFIqZ91{*p|8bd2^0!G_QbR609^hr+@m#58JB`711M9N z%~C778OVAOlx~*kC_U%_&|9}8BftM;$z#a@N!0y;yRHvX1%{nMPI`-o0j$@yf%zk)tTahNqUum?L# z9d5T94lT10bmPCCYSlaUBW}E9~Ffs ze-8JYK8j5U0Ed|kG60dh-6(D!G4bN8!f(0GPq5T++7Ou6gr z;`&yw>Qq*JXl>#(@F*+1WwGWz*9ff!&+pH_yaAXNZr(q9NU9~8aRc3p7uUl9gEQO) zB!!G+P{E#48U!lE_MeM=P6{icR`}BvEay1iE;ow+*%9-zH^t}c=YQmtkT;3`;G39d zU>K};S%c`lA<>=D=PokCkB%<~HCHgPo+kr{{?V8N4ZPR_ zm7f{g{&`ogN=pnnju1mZ-`$IZ-T#Nn$=KB(2N)Q*9H@5zVB`HSUo8XsKO(h}&^Oj9 zFMmz`X=3aHvwtXVR9x9~`8WMBf|oxhh1R4ld`-eY>InMg1WVFIAgBohwjdnkaU88- zq+|q-0fbV74)3U8eWKG^FUq@({TJ`1*I@~uRx1;LAiFS2$_E7-Xr6#08T*(UUcR<3 zRG;*y2vS&To8HX9RJLyXxIsMShOsOjl*v73+Ww+YBC|&p&H3I-21-{}SH9@EHJ0#% z={Nkoorl{!E(lsW2Jd0Mb2$YZ%hJl@CA-=Si&1HFj9zJr^)y+`7d|5|SQTh{s#a>| z25!rtAPu4}kbQdQsREQCzJKt>q>t3X%0JhHA z-fB#b2Hu?$UzFL|{(LGZg!LAdiTM432lPJafUgRp+7*?EeywADzNCqfCwtgNt^PKt-+I-SLP}n#7 zen>lf)3nKZ4;8f!N!Qan8qLE==Zl4;MCsa~1xPrdsO6B$oJ9nk7Kn9?GL|p8 zUuj;{$h5G62d%7ne0sSM3V3}9+pkf8)J-MZqfKq8Tm+^^c%l8(8xITo{4kRIWXj z8s=YQ60q#BN5$6x=o7g3lh3%}*)5pi*z$i--cbNKlTKGqY7`f4f3lrv3Iw*n^uFXi zL4hmn3xg)&^o<4`qqO`3T$$cchc_!*&8U)V9hJ=68`*%pS67BXTk*c|Oa+t2sOfji zQbm8c%+>2aMYm`7?1YC!3g3!V6T=37)i^iwTnu=n4iagMEX)C3@$?YoJJ-ft4Ce;St1RE(Tz@&+#uSl!E z$72gxxeinB^rs)Y{XR~-RL>}TX+0)3W_>wvY58b>py=*vI)OGm$ zQ)PXr=LoyiZkH?_6;Ph{+3+NoouApFup7s7mvvGXyY)@P#2__ZHl&E=#26G1l|55*j?=TF)iHT=b9qV~t^JWrZzKIk<#^(oFmX1{K+lbWW*tJHa zPlX~AV^)hs{B8BKSs9`a4hq}20w^wd6`TrjH*fwHidd&(*NvhO_@a~=i)L~8ZfoaU z>&KItjrzHKV{PPUaAe`3S-XlV1?};+X;Yr{*zM{E-V)_2>+ad1r}Z0NBGpza9rcAq z`TpKx&fc!$(egsf!lDab4Uhc0JRVq7wx$_+jlWs(SJU(4sm|_8Jk&Dp=&#TB-l{hg zJqN=++;6`$V2&1`x~iiF18~lXsIlHt)pJ#v9G}{surJ%3ixlm>3m>wrx<9pnEEkL4 z@e8iRCMnrVqlZp2^380yog{diBK#8`Z6pI5(-@3IE^%ajI>0iZUM21jL|&-H?q^0y zpd#|+^&(o&YGJpOvU+Ws^`+KS(p)?!vFl`29b85A>#MrkxCo(lv^-f^?Uj67 zQz%VjJKgDoXZ>>a)5Tsme0{{eKYalp7xi+hRN5|E@n4E4GX@kjVkAEKvx--%*L780 z_bbGRqqUFYtjynTc)Dyg9?iX;UgfU6`DMMpvT)68@>I8cd0C8MN9?3qBk8KYe(VE+)X3gP3R-soy*7e+RVE6W)r{pKSrFIBpd7 z?za;1W)rYp(g*QmtgAgI^~Ay@R|L}kv7Rl*uYwUr<8<{{BqG<>4_DgXi&k!}^&-ff z*lM0|ofrB7?;1Mou&IAqc4yQuM8UKP4i`H;PT8iXY5j3}8Gd|+02>$|loeeX$GR`v zA+FY=w#W_!^Eha*bUv3aJh`TZWO+xeDDvud9tf6J`QQTVrQR^%Txtw)J8P0dIkmzljixp-qXBq@ScGFfn;z}F@R7q|-#0yiurAlE zYEQwP&a#@j9=lRO1kmRorAvkn&k)$@#`S{o|7!2c!=Y^dzDvr^OeG?FQrWX)r%V*L zq$IoBPC~LXV{RdnEES?;iHfrCyU7x_Y}t1jvSb_kU>MIidf&&){nYQ@_jup;{%?-s zy5_ph>wBK(cl~@nrx36%pWYEAJ~dSZ$=YbaMrykf948frIfcXRHVFM@47NyY!*Doi zgMc(XH71}mjx7-7U>V7o@0swa@|r2GalhDn^@V8Fw~QRS_T>A-ZRRh889ZoNW#bQ{{<%_E)>0Gvq~~@C5pMl=Yc^cWcM&*7-+6IGq6ux>%4qWh zJs`>7V%z-0x7VCK1^9c73FDv+AQ}|Fv+2`MxP@#;LB%Z6(JA8)r^WjHVY$0BnTb@m^Jg5_+ln` zC$u9#Q(WUgxbbEA0xftL3augQcvqx;iwjn~XK|Z@;-{@Sv>uJ#Q3VZGRH15jhKh7+S)~x3yv9w}DJe~hNU5+a_xn)S(h#q=Sz`qET^eB0HWrt8(Fe+1;=dfmYcSLG$^-o@saVMr(*___l8L;961j`P$&%bkSWM z=Qsn>kk-G3j@}KT<9tAeZp6tS6sL-U2}oI@k7BJmYPkeVh^A=Lwy%DL*R_bj&p5@5 z!pRvTj~@fE`^&{hkW}}mB#9FjWp3rlJhQq=v)}6DW{vtV-}z^uB|6$E{1)8pj46D; z>t%;N^On^o4@}~@#co`xYetU_^6R6jRpDGPZLB|v@9Y?!uZ&J?jBu-{|A9*R709#d z#GjJ=pUGb=6F$FG!l|^f3Bz`>{i#&4#a6)wlNoIG!=E<#jPC}plD||M zs8<1N8~brd%hNDNfv)h-*yS?eNYCqCPQ72OK^W)c^XAu0wLts3yE+7*^eP+PEbko4 zq?Y$_{o{lc3+5oAyxC8{{l01(_;HU}z#VSKTFH zln#?Akr)C2%V<9H`g1^g-zyJMdIik&Mn)*DwjF*6IQJnh`PZQ@M~j`z=C5k*ia zL(Skns!%*%XTUAffocJ$v!l~zLn|slp#ky-F|PSlp#c}1dDqDrqSNNfY2mvJhKKI~ zc?Hwpy}ABAe-KYDEh9SmN8HIH2Tp4TgK@9!sjlCR+er4pn^cvg^-4yBy|y+sE=?f| z3PrRx_q{hzmF%DcIGlr-WIek9FrwM+{?W5yr>1BQ)8>IGuw1GO;pE7xSZ(SoC3YXL zlVWiW_#65YK=L3rRXfgbF!19UA&B7P^`?!D`JQ?hncCO^ws&Q)#r2BOBf38}@E>^~2~)`4n@ZRfWm01c0l6!H4!f&G2LD>dLEZcZ=U6xQ&D@bltlLtveo|ylrZKAO*S0e`h zwme>#!rf73xJCeFn}qj2ZPK+~Ir0}&d{Uu=LNh&%tJ0o4^w?!wuVq_94c=Q<;Vt@z zxs%zf>nhH%ej31g)KdSm9+H8yw6L{5E#e0i8?^Td5#{3^7Mhkegf>{&d$*mQb{aWv z&%n(;xdTwtM{7~codVD8E0GrZ;`RY+7u0Wb8JUgDgPD2w`IO61|0P(+Wm+y54 zFsD1j&iy?D`9C}NM~I{dj(`+_m@Ftj{F+2ywhZKu(*d8s@7EUS|1Sdl|9h#Exv`)p zCxS~sN7rrC_@Nz%xH`v$+5)m2uddv>f6KDx1De)m|V9>BV1D(D`f zDjs&@>3BaXe$gaOM4GX={p6BtTj11GYr4<{QfJ^Gh1Lmu$C{Fykvge3JqKX%3TUak zul!ukrd9!*e(sOTCQky1RXakP#D*hbM1Z@a55OkO~JkKoZzk7=ZCTgljs`RXFon#J9R?&91-9zILT&+9>)(t23gO?C@b7l`S0_;X zf2bHik5Qd6Cq1YpiCND!o>ad{j&00@Tz!b*QLTx}ly13=0LCi)sC*1aw_AtIyd;`d zxBq1$gB>+k2paEp4{npDmQ|A0D6(G=E^T1nAz3+<;QDfLOB+m2B+mJ<11=qPb z7@!e3Kk8?Lb@s=krjd5hKQXYZatXMJFNwZ>!(Kg+ar zkg;d|+JT4ZPDoix!tQAU5fwYGoAH1lfRurf={2Spf_khn(mc-{{)gr2v<}5%rGfrX zQfH!)rtW}6^~0dXaaNZ>aZ9zJFwI@aP-r4|^2w0wZXT3ZOY&V2q9b)%?^Sqcnvev! zGuOgwSMSSn-Y9B>OYg-?XJhWY$lHCO!Bednfq5r}j_MLdcpEI+x5=gF$daW-bZMii zZpgsPB6QoBdQ&e%g@09 zt2+kwTXG6lpB?n9WhRK?vBV#06{}AGYY`D zah$nd7IOvyZ_lQmwVVxS;vMr&<>R|RPcB-p{Z3M}c!|QSy90Q^At0wlPv+XSkLA2w zeGn$|_B5mL5?R^^kW6v@d-uOGAp?v3A6h4Wex-yK1LoP1?=g3R zlv_#^6)xbHa_;r#b$yCb8KeP6QAh$KU73Sl{eU@y9Tc4_7%y_{RbwEF@cAgfCfjIU zGGkydUQJhnK}Q-&+deY!vSL!j`Ak>o>CbLdSer-YLEw$l(e>s-Mf@eDyz5cDa* zIpKuJ?xw6``-w18xu~R20F}W?(>`r&j`m;{yO3<+t5#03m8R2G8UTzw9^yQVTdb1n z3s*6x%)-wHjhxhp9qG71;#J%|EGLZ;V@obd7xoPKO9vX?5!o8>-vj&f?Kn_r7Qy!T zU@Fd6QZl~X2A&0ZcB+(d2uEb~QBX&M0wfC3y&L=75Z`ij@ah4-) zu*-OpyfZ}bYwo4G689oOr))b}e^bgy#m&*md*)K4nsVS401hPVB;Tg=0kpS(5R9MX zsw0u_dc3t=lEMIl{6Z%uNYVfWk#S30IJoqciZf7Nhbh1 z@63Yd^8Q$80S(hxH=OGKdEvVEvY`v+csgAmFz!-K^J~=1rNB+J030 zshC{ys|TQ_MpOHUiH=D@Mvw@c@v-)g0rFf>(Avio?crGtc^%wdSmyHB>-}0nKol#f zco9gL=Rvh0Bp^Lx`;`AtX_W`;IQZ5v^+4QCr$8-VK4-dNDZytpF#3*Z>Bo$OAa$;| zW2T6;?q(C|#0s4fL_KrkR>XEwBZdXNWinZG#fS%W)-b|elw|5Z0P51{=_z~9i_h^d zC<1-VK<`(w#gN?K+R90H#UZEi>kR11IjY7G>n2m0M5TqU#YoL8A!ixfWBFQOf^|)^ zJZwaUD74^_`0QdSi$%re04dq4OZmu;mYpMJBv_* zW3uRw6_^)`BdXy)ie{W&`@lD*gO#6V4Bjm-4i=bH=$`jLoXZeE#jbo6CLaBzsOLMn z!xS9vA)nOXxaIJUI~o0!gTxi`w*mgwE8ziEMBycv*~s{flR~TfC8*%+ifi+;N{W)@ z_UU6RLP{Q8JeXVWJg->o5b!;tfm+~fi;C5bhdBhB&V1?FqHO@fQJllIDt?*mG4O3! ztdAdgDzrPFg>>^am>m18S=*i{BWo}k0}h3mYp5-fpEt@va@(HAdvOn9`u2ATH6#tV zkviG>0fW7dAPB_ilxN($;mI40Kc1ZW;moamCF26zOFz%eejv(Vxa{)N_vEdcnjIbP2$@wWgeI0CZoWPO_1t#1 z*{D8@P8(RAHIo$q03u@uOgC4b%sQw7o%pD4X7qzJYP!h|%8;{FiP9LB%5%+@?d? z0z?p-6N?(>hEQlIpWd~aZ~&ZX%GyTAET;&(m_KVN*g(zL8aZyC2RDo2TCWb zw7c@o#Eq>gkdmgdmqdXYYZ|`Jrw_A4WfKTuKLMkm-EVvtEnhCS6GHl|05zhAY3|hi zI3VG~Vy1f%2|z8hI09C+aoZz%v~1ftY{TnwdbS=~XgRXMkH5JBUu88opzMp4RW$Zl z@_atFaXn@kdN>>jft@}@AgwwknE^Gv^C^Bx=P@YBDm2_X(fF33hXGl( zU~3NWCD)?06?csLoMJrr0WYbzTE+zhKm|RM)AK4_;4@Zvt`qZ0c_^wGIq@WsCUJYi zym33!2uqKzn5vB~Uzb7l@!*58@YyJcY*-u`b-PT{4Eo$pOF>3*{#vY-BVYa%$O3DMb%ev&1%YT7Xz zL&VW9P&^%Ra2z=y?pCtit4uq((c|4%&gL=Xr%^KZ9Q$Z#Tu+PfsA^DVF;d+Nr{w!c z8##s&^IWJ{Eb`>gar#tttd|y9x^!nkjMwk>_#D=RwF*8E;;9mKF(YTXd;t zGoAdxRssk|T|1MPC3gm{z&9uAwBAQIjmIc3%6;RweK1H};){X}i5tlM^(KHcX?ow$ z9f^TLF@LQgJO#`c-o3&LGA*+8%#>vqqrCR@vL? zLI+tHG1|j%i~KskjSDw#n(&S$220`PQ}i#D<5Jrb9%Y<1p5~X z`w)dzC2UGyJbOoCn;Vp4cIIB^^m2ASZ$Iwfd=_=KmV8LcKrd_^l;uor-_r};YUYx0 zR>AY8EKVhp0@n(6m&P?~ZM7AIUz#BbmoU}n(We!HW6I83nti+9B5ogsc8)x_vFH30 znQa$4gQI3DBo#b%TX_6}#)0-aK7#B^m5B3Mil|~H5cUvueFJ+*<`Q0gW7A?DDiJ#Zpl0dDrdwV&~Si5fwcCqRzNX?2i%AZc~B?RR(_6Slub}OIFt~%?Dv4*!#s2Nqso;v}1D@Ts- z%mg~2uxes}9if~2j62ywvQEWM+_Ep{&AS*i0>8KHL;zC@l4EkIY)_k4 zO;-+I3g8P!|GH@1kB<9@xEq!wXj&ePey~){G-tMD zQGYIXYf+m^PTF{_d^U1(L%@lpI07B#R3%(7o0Jl{x!Ts3n^HT6Cqz})j)kLJ>3yqK za;$E%!RoYqS`*P?!wN6Mxh_ig`~7c!tI#Rw#QECy{&xe$Npna^P@viP?#iy)|Af9g zyOmJ!;}ND++3e-qD?esFk+*zoV{kae3ch5uCBMQh5X8r%EVkKf3s;A2S9;^(0j_>? z$BHnm{rZ%jcfS0(C2r8PDvoT|FQx+ubaf8bn>VpYQ+#L6 z4tvqHNm0UQ=b8p(ca4qhTrP%Sj;(~8g4OO&qOU+B`kX-nc|IUS5`_}&sBoBcz+4&f zzzx4YgUtir=?COn%NQ855|b>&a(2T}A|@!p_iflr6JDUh(MT%ZeN+pK>UO$Pp` z?aj_#XTyx3cX+&q#vjXS2WJ-f8VNBbZq`9DngWOmFSEB@N08eJr;r^Nzg)40xb#N@ zN5^lq?t%B~YwGu@c|Yot;mVSDfYep*we)`gm?T37`qMTbHh#LM1b9>a7%f~K6_F48 zA%%TvM+7Btc&Daov9es>>9DOOIw)f-B6)uJ`Fz_A^B>P1ui}c!2~Z{6W;TD~${$T? z;isT4q82)HE`+VY(W}?K+WVe5Wnl+Fre{kly}rFQQ(tlubb~D&j%JB64fXDA z_Kloa)utCZla;SmvRL8vox#$kc65bK_nsYrxI4Saj^z=V(Q{CV6(vs2(xUA8nd_Y(+^G?0wY^`-y& zCOzL6wyy0q67wcI40N&+owiBpkg6Wb@-bjvLOSmlk~C-iOo~{l#LO_9ri2_CFgfIN z|5Dvw%knZLv9c`i3}qJ$OC3PR&MGSi(j}ZKB@E_%>_C64zmEqQkLfvryT`t-CPzHM ztFdx+NKOy@$TgX*+xJl1h;i$9c^{2OGWc=Kw_A1}Ysz{GSefefw${8_@J5cFH`n*0 zzrB70`s7xRkMYDD&+96;Gy5+*5(my~%2_K}nWqMVQFlv;+>egL&H_UpkT)3b)((DL z!Csmt$v}7!uN7nCy@|DiUt}i&wkhs!eFoX6@n-nsM)o5*k*(WP zt5Fg>i@Ox+oF0G5tgJ?Qk;O+(iT4h9G?-vA@tmfAUvIagP^2-&4bs(KmvODo(EXaD_!hIj zaEJp=3Lq}Fu9w%z*fuGt_p^$ma{5b!3Ybv2G>Ldxk{tq`Xy*xLyd3v*om5Vsyuk?i zXxwht$}y8>Zy?-X7rqCHf>RU|6f)U*5L?lB#2gf-mUXSo$Ui|PDU9-ju<21wujzI%_(o37=kE-09eG2S z>c(9YXxiH1S$gylaAMI<%RWlupKtbOiCyW+$MjE`QWI5iPh5%3Ew(86{W^9%D|364 zk`4{Im|5@pyMg-i-|yM8xzxf=wMR)$F!EXmcIZG|((2!?^)8{*Zk4I=lTY!Ve^Odd zi{oL9#xAogGB|Lv%vNpkxH_K$yOCb$70c znC3}Vh+*G-cM0tq*sER(WDa&cx+si7WooFaNfrj3|Ho8PZkZ(Y^YL^Xv%3p{3Wi}r zWAQbb=7fhq=0BR6o@@KQr;;EIz=`UAKsRaiaVw_w(IfE`^&pz@2I={DNTKcEDtGew zfqz!u4pQtTF~$D*5JM5*c*^D;B3riL>HpsS|GrEXkw=QW-(38I!3T7gAGmhqIxOq* HpAY{Fjq_9_ literal 0 HcmV?d00001