> For AI agents: the complete documentation index is available at https://docs.halo.run/llms.txt, the full documentation bundle is available at https://docs.halo.run/llms-full.txt.

# 文章

除另行注明外，本页 API 自 Halo 2.0.0 起可用。

## getByName(postName)

```js
postFinder.getByName(postName);
```

### 描述

根据 `metadata.name` 获取文章。

### 参数

1. `postName:string` - 文章的唯一标识 `metadata.name`。

### 返回值

[#PostVo](#postvo)

### 示例

```html
<div th:with="post = ${postFinder.getByName('post-foo')}">
  <a th:href="@{${post.status.permalink}}" th:text="${post.spec.title}"></a>
</div>
```

## content(postName)

```js
postFinder.content(postName);
```

### 描述

根据文章的 `metadata.name` 单独获取文章内容。

### 参数

1. `postName:string` - 文章的唯一标识 `metadata.name`。

### 返回值

[#ContentVo](#contentvo)

### 示例

```html
<div th:with="content = ${postFinder.content('post-foo')}">
  <div th:utext="${content.content}"></div>
</div>
```

## cursor(postName)

```js
postFinder.cursor(postName);
```

### 描述

根据文章的 `metadata.name` 获取相邻的文章（上一篇 / 下一篇）。

:::info 上一篇与下一篇的定义
上一篇文章是指发布时间较当前文章更早的文章，下一篇文章是指发布时间较当前文章更新的文章。
:::

### 参数

1. `postName:string` - 文章的唯一标识 `metadata.name`。

### 返回值

[#NavigationPostVo](#navigationpostvo)

### 示例

```html title="/templates/post.html"
<div th:with="postCursor = ${postFinder.cursor(post.metadata.name)}">
  <a
    th:if="${postCursor.hasPrevious()}"
    th:href="@{${postCursor.previous.status.permalink}}"
  >
    <span th:text="${postCursor.previous.spec.title}"></span>
  </a>
  <a
    th:if="${postCursor.hasNext()}"
    th:href="@{${postCursor.next.status.permalink}}"
  >
    <span th:text="${postCursor.next.spec.title}"></span>
  </a>
</div>
```

## cursorByCategory(postName)

```js
postFinder.cursorByCategory(postName);
```

### 描述

根据文章的 `metadata.name` 获取同一主分类下相邻的文章（上一篇 / 下一篇）。

**引入版本**: 2.25.0

主分类为文章 `spec.categories` 中的第一个分类。此方法只匹配同一分类下的文章，不会包含子分类中的文章。如果当前文章没有分类、未发布或者不存在，将返回空的导航结果。

:::info 上一篇与下一篇的定义
上一篇文章是指发布时间较当前文章更早的文章，下一篇文章是指发布时间较当前文章更新的文章。
:::

对应的 Public API 为：

```http
GET /apis/api.content.halo.run/v1alpha1/posts/{name}/navigation?scope=category
```

### 参数

1. `postName:string` - 文章的唯一标识 `metadata.name`。

### 返回值

[#NavigationPostVo](#navigationpostvo)

### 示例

```html title="/templates/post.html"
<div th:with="postCursor = ${postFinder.cursorByCategory(post.metadata.name)}">
  <a
    th:if="${postCursor.hasPrevious()}"
    th:href="@{${postCursor.previous.status.permalink}}"
  >
    <span th:text="${postCursor.previous.spec.title}"></span>
  </a>
  <a
    th:if="${postCursor.hasNext()}"
    th:href="@{${postCursor.next.status.permalink}}"
  >
    <span th:text="${postCursor.next.spec.title}"></span>
  </a>
</div>
```

## listAll()

```js
postFinder.listAll();
```

### 描述

获取所有文章。

### 参数

无

### 返回值

List\<[#ListedPostVo](#listedpostvo)>

### 示例

```html
<ul th:with="posts = ${postFinder.listAll()}">
  <li th:each="post : ${posts}">
    <a th:href="@{${post.status.permalink}}" th:text="${post.spec.title}"></a>
  </li>
</ul>
```

## random(maxSize)

```js
postFinder.random(maxSize);
```

### 描述

随机获取文章列表。

**引入版本**: 2.24.1

### 参数

1. `maxSize:int` - 获取文章的最大数量，取值范围为 1 到 100。

### 返回值

List\<[#ListedPostVo](#listedpostvo)>

### 示例

```html
<ul th:with="posts = ${postFinder.random(10)}">
  <li th:each="post : ${posts}">
    <a th:href="@{${post.status.permalink}}" th:text="${post.spec.title}"></a>
  </li>
</ul>
```

## `list({...})`

```js
postFinder.list({
  page: 1,
  size: 10,
  tagName: 'fake-tag',
  categoryName: 'fake-category',
  owner: 'fake-owner',
  pinned: true,
  sort: {'spec.publishTime,desc', 'metadata.creationTimestamp,asc'}
});
```

### 描述

统一参数的文章列表查询方法，支持分页、标签、分类、创建者、置顶状态、排序等参数，且均为可选参数。

**引入版本**：2.19.0

可以使用此方法来代替 `list(page, size)`、`listByCategory(page, size, categoryName)`、`listByTag(page, size, tag)`、`listByOwner(page, size, owner)` 方法。

:::info 排序与分类筛选的默认行为

- 默认排序为 `spec.pinned,desc → spec.priority,desc → spec.publishTime,desc → metadata.name,asc`。传递自定义 `sort` 时，自定义排序会拼接到默认排序之前，默认排序仍然生效。
- 使用 `categoryName` 筛选时，会同时包含该分类的**所有子分类**下的文章。这与 [cursorByCategory(postName)](#cursorbycategorypostname) 只匹配同一分类、不包含子分类的行为相反。
- 未传递任何筛选参数时，不会返回「不在列表中显示」（`status.hideFromList`）的文章。

:::

### 参数

1. `page:int` - 分页页码，从 1 开始
2. `size:int` - 分页条数
3. `tagName:string` - 标签唯一标识 `metadata.name`
4. `categoryName:string` - 分类唯一标识 `metadata.name`，包含子分类下的文章
5. `owner:string` - 创建者用户名 `name`
6. `pinned:boolean` - 置顶状态，`true` 仅返回置顶文章，`false` 仅返回非置顶文章；不传时不按置顶状态筛选（引入版本：2.26.0）
7. `sort:string[]` - 排序字段，格式为 `字段名,排序方式`，排序方式可选值为 `asc` 或 `desc`，如 `spec.publishTime,desc`，传递时需要使用 `{}` 形式并用逗号分隔表示数组。

### 返回值

[#ListResult\<ListedPostVo>](#listresultlistedpostvo)

### 示例

```html
<ul
  th:with="posts = ${postFinder.list({
  page: 1,
  size: 10,
  tagName: 'fake-tag',
  categoryName: 'fake-category',
  owner: 'fake-owner',
  pinned: true,
  sort: {'spec.publishTime,desc', 'metadata.creationTimestamp,asc'}
})}"
>
  <li th:each="post : ${posts.items}">
    <a th:href="@{${post.status.permalink}}" th:text="${post.spec.title}"></a>
  </li>
</ul>
```

## list(page,size)

```js
postFinder.list(page, size);
```

### 描述

根据分页参数获取文章列表。

**已过时**: 请使用 `list({...})` 方法代替。

### 参数

1. `page:int` - 分页页码，从 1 开始
2. `size:int` - 分页条数

### 返回值

[#ListResult\<ListedPostVo>](#listresultlistedpostvo)

### 示例

```html
<ul th:with="posts = ${postFinder.list(1,10)}">
  <li th:each="post : ${posts.items}">
    <a th:href="@{${post.status.permalink}}" th:text="${post.spec.title}"></a>
  </li>
</ul>
```

## listByCategory(page,size,categoryName)

```js
postFinder.listByCategory(page, size, categoryName);
```

### 描述

根据分类标识和分页参数获取文章列表。

**已过时**: 请使用 `list({...})` 方法代替。

### 参数

1. `page:int` - 分页页码，从 1 开始
2. `size:int` - 分页条数
3. `categoryName:string` - 文章分类唯一标识 `metadata.name`

### 返回值

[#ListResult\<ListedPostVo>](#listresultlistedpostvo)

### 示例

```html
<ul th:with="posts = ${postFinder.listByCategory(1,10,'category-foo')}">
  <li th:each="post : ${posts.items}">
    <a th:href="@{${post.status.permalink}}" th:text="${post.spec.title}"></a>
  </li>
</ul>
```

## listByTag(page,size,tag)

```js
postFinder.listByTag(page, size, tag);
```

### 描述

根据标签标识和分页参数获取文章列表。

**已过时**: 请使用 `list({...})` 方法代替。

### 参数

1. `page:int` - 分页页码，从 1 开始
2. `size:int` - 分页条数
3. `tag:string` - 文章标签唯一标识 `metadata.name`

### 返回值

[#ListResult\<ListedPostVo>](#listresultlistedpostvo)

### 示例

```html
<ul th:with="posts = ${postFinder.listByTag(1,10,'tag-foo')}">
  <li th:each="post : ${posts.items}">
    <a th:href="@{${post.status.permalink}}" th:text="${post.spec.title}"></a>
  </li>
</ul>
```

## listByOwner(page,size,owner)

```js
postFinder.listByOwner(page, size, owner);
```

### 描述

根据创建者用户名和分页参数获取文章列表。

**引入版本**：2.1.0

**已过时**: 请使用 `list({...})` 方法代替。

### 参数

1. `page:int` - 分页页码，从 1 开始
2. `size:int` - 分页条数
3. `owner:string` - 创建者用户名 `name`

### 返回值

[#ListResult\<ListedPostVo>](#listresultlistedpostvo)

### 示例

```html
<ul th:with="posts = ${postFinder.listByOwner(1,10,'admin')}">
  <li th:each="post : ${posts.items}">
    <a th:href="@{${post.status.permalink}}" th:text="${post.spec.title}"></a>
  </li>
</ul>
```

## archives(page,size)

```js
postFinder.archives(page, size);
```

### 描述

根据分页参数获取文章归档列表。

### 参数

1. `page:int` - 分页页码，从 1 开始
2. `size:int` - 分页条数

### 返回值

[#ListResult\<PostArchiveVo>](#listresultpostarchivevo)

### 示例

```html
<th:block th:with="archives = ${postFinder.archives(1,10)}">
  <th:block th:each="archive : ${archives.items}">
    <h1 th:text="${archive.year}"></h1>
    <ul>
      <th:block th:each="month : ${archive.months}">
        <li th:each="post : ${month.posts}">
          <a th:href="@{${post.status.permalink}}" th:text="${post.spec.title}">
          </a>
        </li>
      </th:block>
    </ul>
  </th:block>
</th:block>
```

## archives(page,size,year)

```js
postFinder.archives(page, size, year);
```

### 描述

根据年份和分页参数获取文章归档列表。

### 参数

1. `page:int` - 分页页码，从 1 开始
2. `size:int` - 分页条数
3. `year:string` - 年份

### 返回值

[#ListResult\<PostArchiveVo>](#listresultpostarchivevo)

### 示例

```html
<th:block th:with="archives = ${postFinder.archives(1,10,'2022')}">
  <th:block th:each="archive : ${archives.items}">
    <h1 th:text="${archive.year}"></h1>
    <ul>
      <th:block th:each="month : ${archive.months}">
        <li th:each="post : ${month.posts}">
          <a th:href="@{${post.status.permalink}}" th:text="${post.spec.title}">
          </a>
        </li>
      </th:block>
    </ul>
  </th:block>
</th:block>
```

## archives(page,size,year,month)

```js
postFinder.archives(page, size, year, month);
```

### 描述

根据年月和分页参数获取文章归档列表。

### 参数

1. `page:int` - 分页页码，从 1 开始
2. `size:int` - 分页条数
3. `year:string` - 年份
4. `month:string` - 月份

### 返回值

[#ListResult\<PostArchiveVo>](#listresultpostarchivevo)

### 示例

```html
<th:block th:with="archives = ${postFinder.archives(1,10,'2022','11')}">
  <th:block th:each="archive : ${archives.items}">
    <h1 th:text="${archive.year}"></h1>
    <ul>
      <th:block th:each="month : ${archive.months}">
        <li th:each="post : ${month.posts}">
          <a th:href="@{${post.status.permalink}}" th:text="${post.spec.title}">
          </a>
        </li>
      </th:block>
    </ul>
  </th:block>
</th:block>
```

## 类型定义

### CategoryVo

```jsonc title="CategoryVo"
{
  "metadata": {
    "name": "string", // 唯一标识
    "labels": {
      "additionalProp1": "string",
    },
    "annotations": {
      "additionalProp1": "string",
    },
    "creationTimestamp": "2022-11-20T13:06:38.512Z", // 创建时间
  },
  "spec": {
    "displayName": "string", // 显示名称
    "slug": "string", // 别名，通常用于生成 status.permalink
    "description": "string", // 描述
    "cover": "string", // 封面图
    "template": "string", // 自定义渲染模板名称
    "priority": 0, // 排序字段
    "parent": "string", // 父分类的 metadata.name，根分类为空
    "children": [
      // 已弃用，仅用于兼容旧数据，不再表示当前分类层级
      "string",
    ],
  },
  "status": {
    "permalink": "string", // 固定链接
  },
  "postCount": 0, // 文章数量
}
```

### TagVo

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

### ContributorVo

```jsonc title="ContributorVo"
{
  "name": "string", // 用户名
  "displayName": "string", // 显示名称
  "avatar": "string", // 头像
  "bio": "string", // 描述
  "permalink": "string", // 作者的文章归档页面链接
  "metadata": {
    "name": "string", // 唯一标识
    "labels": {
      "additionalProp1": "string",
    },
    "annotations": {
      "additionalProp1": "string",
    },
    "creationTimestamp": "2022-11-20T13:06:38.512Z", // 创建时间
  },
}
```

### PostVo

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

- [#CategoryVo](#categoryvo)
- [#TagVo](#tagvo)
- [#ContributorVo](#contributorvo)
- [#ContentVo](#contentvo)

### ContentVo

```jsonc title="ContentVo"
{
  "raw": "string", // 原始文本，一般用于给编辑器使用
  "content": "string", // 最终渲染的文本
}
```

### NavigationPostVo

```jsonc title="NavigationPostVo"
{
  "previous": "#ListedPostVo", // 上一篇文章，发布时间较当前文章更早的文章
  "next": "#ListedPostVo", // 下一篇文章，发布时间较当前文章更新的文章
}
```

- [#PostVo](#postvo)

### ListedPostVo

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

- [#CategoryVo](#categoryvo)
- [#TagVo](#tagvo)
- [#ContributorVo](#contributorvo)

### ListResult\<ListedPostVo>

```jsonc title="ListResult<ListedPostVo>"
{
  "page": 0, // 当前页码
  "size": 0, // 每页条数
  "total": 0, // 总条数
  "items": "List<#ListedPostVo>", // 文章列表数据
  "first": true, // 是否为第一页
  "last": true, // 是否为最后一页
  "hasNext": true, // 是否有下一页
  "hasPrevious": true, // 是否有上一页
  "totalPages": 0, // 总页数
}
```

- [#ListedPostVo](#listedpostvo)

### PostArchiveVo

```json title="PostArchiveVo"
{
  "year": "string",
  "months": [
    {
      "month": "string",
      "posts": "#ListedPostVo"
    }
  ]
}
```

- [#ListedPostVo](#listedpostvo)

### ListResult\<PostArchiveVo>

```jsonc title="ListResult<PostArchiveVo>"
{
  "page": 0, // 当前页码
  "size": 0, // 每页条数
  "total": 0, // 总条数
  "items": "List<#PostArchiveVo>", // 文章归档数据
  "first": true, // 是否为第一页
  "last": true, // 是否为最后一页
  "hasNext": true, // 是否有下一页
  "hasPrevious": true, // 是否有上一页
  "totalPages": 0, // 总页数
}
```

- [#PostArchiveVo](#postarchivevo)
