docs: refactor documentation of plugin development (#291)
重构和完善插件开发文档。 /kind documentation Fixes https://github.com/halo-dev/docs/issues/253 ```release-note None ``` --------- Signed-off-by: Ryan Wang <i@ryanc.cc> Co-authored-by: guqing <i@guqing.email>wan92hen-patch-1
@ -0,0 +1,55 @@
|
||||
---
|
||||
title: 附件存储
|
||||
description: 为附件存储方式提供的扩展点,可用于自定义附件存储方式。
|
||||
---
|
||||
附件存储策略扩展点支持扩展附件的上传和存储方式,如将附件存储到第三方云存储服务中。
|
||||
|
||||
扩展点接口如下:
|
||||
|
||||
```java
|
||||
public interface AttachmentHandler extends ExtensionPoint {
|
||||
|
||||
Mono<Attachment> upload(UploadContext context);
|
||||
|
||||
Mono<Attachment> delete(DeleteContext context);
|
||||
|
||||
default Mono<URI> getSharedURL(Attachment attachment,
|
||||
Policy policy,
|
||||
ConfigMap configMap,
|
||||
Duration ttl) {
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
default Mono<URI> getPermalink(Attachment attachment,
|
||||
Policy policy,
|
||||
ConfigMap configMap) {
|
||||
return Mono.empty();
|
||||
}
|
||||
```
|
||||
|
||||
- `upload` 方法用于上传附件,返回值为 `Mono<Attachment>`,其中 `Attachment` 为上传成功后的附件对象。
|
||||
- `delete` 方法用于删除附件,返回值为 `Mono<Attachment>`,其中 `Attachment` 为删除后的附件对象。
|
||||
- `getSharedURL` 方法用于获取附件的共享链接,返回值为 `Mono<URI>`,其中 `URI` 为附件的共享链接。
|
||||
- `getPermalink` 方法用于获取附件的永久链接,返回值为 `Mono<URI>`,其中 `URI` 为附件的永久链接。
|
||||
|
||||
`AttachmentHandler` 对应的 `ExtensionPointDefinition` 如下:
|
||||
|
||||
```yaml
|
||||
apiVersion: plugin.halo.run/v1alpha1
|
||||
kind: ExtensionPointDefinition
|
||||
metadata:
|
||||
name: attachment-handler
|
||||
spec:
|
||||
className: run.halo.app.core.extension.attachment.endpoint.AttachmentHandler
|
||||
displayName: AttachmentHandler
|
||||
type: MULTI_INSTANCE
|
||||
description: "Provide extension points for attachment storage strategies"
|
||||
```
|
||||
|
||||
即声明 `ExtensionDefinition` 自定义模型对象时对应的 `extensionPointName` 为 `attachment-handler`。
|
||||
|
||||
可以参考以下项目示例:
|
||||
|
||||
- [S3 对象存储协议的存储插件](https://github.com/halo-dev/plugin-s3)
|
||||
- [阿里云 OSS 的存储策略插件](https://github.com/halo-sigs/plugin-alioss)
|
||||
- [又拍云 OSS 的存储策略](https://github.com/AirboZH/plugin-uposs)
|
@ -0,0 +1,83 @@
|
||||
---
|
||||
title: 评论主体展示
|
||||
description: 用于在管理端评论列表中展示评论的主体内容。
|
||||
---
|
||||
|
||||
评论主体扩展点用于在管理端评论列表中展示评论的主体内容,评论自定义模型是 Halo 主应用提供的,如果你需要使用评论自定义模型,那么评论会统一
|
||||
展示在管理后台的评论列表中,这时就需要通过评论主体扩展点来展示评论的主体内容便于跳转到对应的页面,如果没有实现该扩展点,那么评论列表中对应的评论的主体会显示为“未知”。
|
||||
|
||||
```java
|
||||
public interface CommentSubject<T extends Extension> extends ExtensionPoint {
|
||||
|
||||
Mono<T> get(String name);
|
||||
|
||||
default Mono<SubjectDisplay> getSubjectDisplay(String name) {
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
boolean supports(Ref ref);
|
||||
|
||||
record SubjectDisplay(String title, String url, String kindName) {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- `get` 方法用于获取评论主体,参数 `name` 是评论主体的自定义模型对象的名称,返回值为 `Mono<T>`,其中 `T` 为评论主体对象,它是使用评论的那个自定义模型。
|
||||
- `getSubjectDisplay` 方法用于获取评论主体的展示信息,返回值为 `Mono<SubjectDisplay>`,其中 `SubjectDisplay` 为评论主体的展示信息,包含标题、链接和类型名称,用于在主题端展示评论主体的信息。
|
||||
- `supports` 方法用于判断是否支持该评论主体,返回值为 `boolean`,如果支持则返回 `true`,否则返回 `false`。
|
||||
|
||||
实现该扩展点后,评论列表会通过 `get` 方法将主体的自定义模型对象带到评论列表中,可以配置前端的扩展点来决定如何展示评论主体的信息,参考:[UI 评论来源显示](../../ui/extension-points//comment-subject-ref-create.md)
|
||||
|
||||
例如对于文章是支持评论的,所以文章的评论主体扩展点实现如下:
|
||||
|
||||
```java
|
||||
public class PostCommentSubject implements CommentSubject<Post> {
|
||||
|
||||
private final ReactiveExtensionClient client;
|
||||
private final ExternalLinkProcessor externalLinkProcessor;
|
||||
|
||||
@Override
|
||||
public Mono<Post> get(String name) {
|
||||
return client.fetch(Post.class, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<SubjectDisplay> getSubjectDisplay(String name) {
|
||||
return get(name)
|
||||
.map(post -> {
|
||||
var url = externalLinkProcessor
|
||||
.processLink(post.getStatusOrDefault().getPermalink());
|
||||
return new SubjectDisplay(post.getSpec().getTitle(), url, "文章");
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(Ref ref) {
|
||||
Assert.notNull(ref, "Subject ref must not be null.");
|
||||
GroupVersionKind groupVersionKind =
|
||||
new GroupVersionKind(ref.getGroup(), ref.getVersion(), ref.getKind());
|
||||
return GroupVersionKind.fromExtension(Post.class).equals(groupVersionKind);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`CommentSubject` 对应的 `ExtensionPointDefinition` 如下:
|
||||
|
||||
```yaml
|
||||
apiVersion: plugin.halo.run/v1alpha1
|
||||
kind: ExtensionPointDefinition
|
||||
metadata:
|
||||
name: comment-subject
|
||||
spec:
|
||||
className: run.halo.app.content.comment.CommentSubject
|
||||
displayName: CommentSubject
|
||||
type: MULTI_INSTANCE
|
||||
description: "Provide extension points for comment subject display"
|
||||
```
|
||||
|
||||
即声明 `ExtensionDefinition` 自定义模型对象时对应的 `extensionPointName` 为 `comment-subject`。
|
||||
|
||||
可以参考其他使用该扩展点的项目示例:
|
||||
|
||||
- [Halo 自定义页面评论主体](https://github.com/halo-dev/halo/blob/main/application/src/main/java/run/halo/app/content/comment/SinglePageCommentSubject.java)
|
||||
- [瞬间的评论主体](https://github.com/halo-sigs/plugin-moments/blob/096b1b3e4a2ca44b6f858ba1181b62eeff64a139/src/main/java/run/halo/moments/MomentCommentSubject.java#L25)
|
@ -0,0 +1,65 @@
|
||||
---
|
||||
title: 扩展点
|
||||
description: Halo 服务端为插件提供的扩展点接口
|
||||
---
|
||||
|
||||
术语:
|
||||
|
||||
- 扩展点
|
||||
- 由 Halo 定义的用于添加特定功能的接口。
|
||||
- 扩展点应该在服务的核心功能和它所认为的集成之间的交叉点上。
|
||||
- 扩展点是对服务的扩充,但不是影响服务的核心功能:区别在于,如果没有其核心功能,服务就无法运行,而扩展点对于特定的配置可能至关重要该服务最终是可选的。
|
||||
- 扩展点应该小且可组合,并且在相互配合使用时,可为 Halo 提供比其各部分总和更大的价值。
|
||||
- 扩展
|
||||
- 扩展点的一种具体实现。
|
||||
|
||||
使用 Halo 扩展点的必要步骤是:
|
||||
|
||||
1. 实现扩展点接口,然后标记上 `@Component` 注解。
|
||||
2. 对该扩展点的实现类进行 `ExtensionDefinition` 自定义模型对象的声明。
|
||||
|
||||
例如: 实现一个通知器的扩展,首先 `implements` ReactiveNotifier 扩展点接口:
|
||||
|
||||
```java
|
||||
@Component
|
||||
public class EmailNotifier implements ReactiveNotifier {
|
||||
@Override
|
||||
public Mono<Void> notify(NotificationContext context) {
|
||||
// Send notification
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
然后声明一个 `ExtensionDefinition` 自定义模型对象:
|
||||
|
||||
```yaml
|
||||
apiVersion: plugin.halo.run/v1alpha1
|
||||
kind: ExtensionDefinition
|
||||
metadata:
|
||||
name: halo-email-notifier # 指定一个扩展定义的名称
|
||||
spec:
|
||||
# 扩展的全限定类名
|
||||
className: run.halo.app.notification.EmailNotifier
|
||||
# 所实现的扩展点定义的自定义模型对象名称
|
||||
extensionPointName: reactive-notifier
|
||||
# 扩展名称用于展示给用户
|
||||
displayName: "EmailNotifier"
|
||||
# 扩展的简要描述,用于让用户了解该扩展的作用
|
||||
description: "Support sending notifications to users via email"
|
||||
```
|
||||
|
||||
:::tip Note
|
||||
每一个扩展点都会声明一个对应的 `ExtensionPointDefinition` 自定义模型对象被称之为扩展点定义,用于描述该扩展点的信息,例如:扩展点的名称、描述、扩展点的类型等。
|
||||
|
||||
而每一个扩展也必须声明一个对应的 `ExtensionDefinition` 自定义模型对象被称之为扩展定义,用于描述该扩展的信息,例如:扩展的名称、描述、对应扩展点的对象名称等。
|
||||
:::
|
||||
|
||||
关于如何在插件中声明自定义模型对象请参考:[自定义模型](../../server/extension.md#declare-extension-object)
|
||||
|
||||
以下是目前已支持的扩展点列表:
|
||||
|
||||
```mdx-code-block
|
||||
import DocCardList from '@theme/DocCardList';
|
||||
|
||||
<DocCardList />
|
||||
```
|
@ -0,0 +1,43 @@
|
||||
---
|
||||
title: 主题端文章内容处理
|
||||
description: 提供扩展主题端文章内容处理的方法,干预文章内容的渲染。
|
||||
---
|
||||
|
||||
主题端文章内容处理扩展点用于干预文章内容的渲染,例如:在文章内容中添加广告、添加版权信息等。
|
||||
|
||||
```java
|
||||
public interface ReactivePostContentHandler extends ExtensionPoint {
|
||||
|
||||
Mono<PostContentContext> handle(@NonNull PostContentContext postContent);
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
class PostContentContext {
|
||||
private Post post;
|
||||
private String content;
|
||||
private String raw;
|
||||
private String rawType;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`handle` 方法用于处理文章内容,参数 `postContent` 为文章内容上下文,包含文章自定义模型对象、文章 html 内容、原始内容、原始内容类型等信息。
|
||||
|
||||
`ReactivePostContentHandler` 对应的 `ExtensionPointDefinition` 如下:
|
||||
|
||||
```yaml
|
||||
apiVersion: plugin.halo.run/v1alpha1
|
||||
kind: ExtensionPointDefinition
|
||||
metadata:
|
||||
name: reactive-post-content-handler
|
||||
spec:
|
||||
className: run.halo.app.theme.ReactivePostContentHandler
|
||||
displayName: ReactivePostContentHandler
|
||||
type: MULTI_INSTANCE
|
||||
description: "Provides a way to extend the post content to be displayed on the theme-side."
|
||||
```
|
||||
|
||||
即声明 `ExtensionDefinition` 自定义模型对象时对应的 `extensionPointName` 为 `reactive-post-content-handler`。
|
||||
|
||||
使用案例可以参考:[WebP Cloud 插件](https://github.com/webp-sh/halo-plugin-webp-cloud/blob/a6069dfa78931de0d5b5dfe98fdd18a0da75b09f/src/main/java/se/webp/plugin/WebpCloudPostContentHandler.java#L17)
|
||||
它的作用是处理主题端文章内容中的所有图片的地址,将其替换为一个 WebP Cloud 的代理地址,从而实现文章内容中的图片都使用 WebP 格式。
|
@ -0,0 +1,38 @@
|
||||
---
|
||||
title: 主题端自定义页面内容处理
|
||||
description: 提供扩展主题端自定义页面内容处理的方法,干预自定义页面内容的渲染。
|
||||
---
|
||||
|
||||
主题端自定义页面内容处理扩展点,作用同 [主题端文章内容处理](./post-content.md) 扩展点,只是作用于自定义页面。
|
||||
|
||||
```java
|
||||
public interface ReactiveSinglePageContentHandler extends ExtensionPoint {
|
||||
|
||||
Mono<SinglePageContentContext> handle(@NonNull SinglePageContentContext singlePageContent);
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
class SinglePageContentContext {
|
||||
private SinglePage singlePage;
|
||||
private String content;
|
||||
private String raw;
|
||||
private String rawType;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`ReactiveSinglePageContentHandler` 对应的 `ExtensionPointDefinition` 如下:
|
||||
|
||||
```yaml
|
||||
apiVersion: plugin.halo.run/v1alpha1
|
||||
kind: ExtensionPointDefinition
|
||||
metadata:
|
||||
name: reactive-singlepage-content-handler
|
||||
spec:
|
||||
className: run.halo.app.theme.ReactiveSinglePageContentHandler
|
||||
displayName: ReactiveSinglePageContentHandler
|
||||
type: MULTI_INSTANCE
|
||||
description: "Provides a way to extend the single page content to be displayed on the theme-side."
|
||||
```
|
||||
|
||||
即声明 `ExtensionDefinition` 自定义模型对象时对应的 `extensionPointName` 为 `reactive-singlepage-content-handler`。
|
@ -0,0 +1,30 @@
|
||||
---
|
||||
title: 用户名密码认证管理器
|
||||
description: 提供扩展用户名密码的身份验证的方法
|
||||
---
|
||||
|
||||
用户名密码认证管理器扩展点用于替换 Halo 默认的用户名密码认证管理器实现,例如:使用第三方的身份验证服务,一个例子是 LDAP。
|
||||
|
||||
```java
|
||||
public interface UsernamePasswordAuthenticationManager extends ExtensionPoint {
|
||||
Mono<Authentication> authenticate(Authentication authentication);
|
||||
}
|
||||
```
|
||||
|
||||
`UsernamePasswordAuthenticationManager` 对应的 `ExtensionPointDefinition` 如下:
|
||||
|
||||
```yaml
|
||||
apiVersion: plugin.halo.run/v1alpha1
|
||||
kind: ExtensionPointDefinition
|
||||
metadata:
|
||||
name: username-password-authentication-manager
|
||||
spec:
|
||||
className: run.halo.app.security.authentication.login.UsernamePasswordAuthenticationManager
|
||||
displayName: Username password authentication manager
|
||||
type: SINGLETON
|
||||
description: "Provides a way to extend the username password authentication."
|
||||
```
|
||||
|
||||
即声明 `ExtensionDefinition` 自定义模型对象时对应的 `extensionPointName` 为 `username-password-authentication-manager`。
|
||||
|
||||
可以参考的实现示例 [TOTP 认证](https://github.com/halo-dev/halo/blob/86e688a15d05c084021b6ba5e75d56a8813c3813/application/src/main/java/run/halo/app/security/authentication/twofactor/totp/TotpAuthenticationFilter.java#L84)
|
@ -0,0 +1,60 @@
|
||||
---
|
||||
title: 为主题提供数据
|
||||
description: 了解如何为主题提供更多获取和使用数据的方法。
|
||||
---
|
||||
|
||||
当你在插件中创建了自己的自定义模型时,你可能需要在主题模板中使用这些数据。或者,你提供一些额外的数据,以便主题可以使用它们,你可以通过创建一个自定义的 `finder` 来实现这一点。
|
||||
|
||||
## 创建一个 Finder
|
||||
|
||||
首先,你需要创建一个 `interface`,并在其中定义你需要提供给主题获取数据的方法,方法的返回值可以是 `Mono` 或 `Flux` 类型,例如:
|
||||
|
||||
```java
|
||||
public interface LinkFinder {
|
||||
Mono<LinkVo> get(String linkName);
|
||||
|
||||
Flux<LinkVo> listAll();
|
||||
}
|
||||
```
|
||||
|
||||
然后写一个实现类,实现这个 `interface`,并在类上添加 `@Finder` 注解,例如:
|
||||
|
||||
```java
|
||||
import run.halo.app.theme.finders.Finder;
|
||||
|
||||
@Finder("myPluginLinkFinder")
|
||||
public class LinkFinderImpl implements LinkFinder {
|
||||
@Override
|
||||
public Mono<LinkVo> get(String linkName) {
|
||||
// ...
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<LinkVo> listAll() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`@Finder` 注解的值是你在主题中使用的名称,例如,你可以在主题中使用 `myPluginLinkFinder.get('a-link-name')` 来获取数据,`myPluginLinkFinder` 就是你在 `@Finder` 注解中定义的名称。
|
||||
|
||||
## Finder 命名
|
||||
|
||||
为了避免与其他插件的 `finder` 名称冲突,建议在 `@Finder` 注解中添加一个你插件名称的前缀作为 `finder` 名称且名称需要是驼峰式的,不能包含除了 `_` 之外的其他特殊字符。
|
||||
|
||||
例如,你的插件名称是 `my_plugin`,你需要为主题提供一个获取链接的 `finder`,那么你可以这样定义 `@Finder` 注解:
|
||||
|
||||
```java
|
||||
@Finder("myPluginLinkFinder")
|
||||
```
|
||||
|
||||
## 使用 Finder
|
||||
|
||||
在主题中,你可以通过 `finder` 名称和方法名及对应的参数来获取数据,例如:
|
||||
|
||||
```html
|
||||
<div th:text="${myPluginLinkFinder.listAll()}">
|
||||
</div>
|
||||
```
|
||||
|
||||
模板语法参考:[Thymeleaf](https://www.thymeleaf.org/doc/tutorials/3.1/usingthymeleaf.html#standard-expression-syntax)。
|
@ -0,0 +1,55 @@
|
||||
---
|
||||
title: AnnotationsForm
|
||||
description: 元数据表单组件
|
||||
---
|
||||
|
||||
此组件用于提供统一的 [Annotations 表单](../../../../annotations-form.md),可以根据 `group` 和 `kind` 属性自动渲染对应的表单项。
|
||||
|
||||
## 使用示例
|
||||
|
||||
```html
|
||||
<script lang="ts" setup>
|
||||
import { ref } from "vue"
|
||||
|
||||
const annotationsFormRef = ref()
|
||||
const currentAnnotations = ref()
|
||||
|
||||
function handleSubmit () {
|
||||
annotationsFormRef.value?.handleSubmit();
|
||||
await nextTick();
|
||||
|
||||
const { customAnnotations, annotations, customFormInvalid, specFormInvalid } =
|
||||
annotationsFormRef.value || {};
|
||||
|
||||
// 表单验证不通过
|
||||
if (customFormInvalid || specFormInvalid) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 合并自定义数据和表单提供的数据
|
||||
const newAnnotations = {
|
||||
...annotations,
|
||||
...customAnnotations,
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AnnotationsForm
|
||||
ref="annotationsFormRef"
|
||||
:value="currentAnnotations"
|
||||
kind="Post"
|
||||
group="content.halo.run"
|
||||
/>
|
||||
|
||||
<VButton @click="handleSubmit">提交</VButton>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| 属性名 | 类型 | 默认值 | 描述 |
|
||||
|---------|------------------------------------|---------|-----------------------------------------|
|
||||
| `group` | string | 无,必填 | 定义组件所属的分组。 |
|
||||
| `kind` | string | 无,必填 | 定义组件的种类。 |
|
||||
| `value` | { [key: string]: string; } \| null | null | 可选,包含键值对的对象或空值,用于存储数据。 |
|
@ -0,0 +1,25 @@
|
||||
---
|
||||
title: AttachmentFileTypeIcon
|
||||
description: 附件文件类型图标组件
|
||||
---
|
||||
|
||||
此组件用于根据文件名显示文件类型图标。
|
||||
|
||||
## 使用示例
|
||||
|
||||
```html
|
||||
<script lang="ts" setup></script>
|
||||
|
||||
<template>
|
||||
<AttachmentFileTypeIcon fileName="example.png" />
|
||||
</div>
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| 属性名 | 类型 | 默认值 | 描述 |
|
||||
|--------------|---------------------|-----------|------------------------------------|
|
||||
| `fileName` | string \| undefined | undefined | 文件名,可以是字符串或未定义。 |
|
||||
| `displayExt` | boolean | true | 可选,是否显示文件扩展名,默认为 true。 |
|
||||
| `width` | number | 10 | 可选,组件宽度,默认为 10。 |
|
||||
| `height` | number | 10 | 可选,组件高度,默认为 10。 |
|
@ -0,0 +1,50 @@
|
||||
---
|
||||
title: AttachmentSelectorModal
|
||||
description: 附件选择组件
|
||||
---
|
||||
|
||||
此组件用于调出附件选择器,以供用户选择附件。
|
||||
|
||||
:::info 注意
|
||||
此组件当前仅在 Console 中可用。
|
||||
:::
|
||||
|
||||
## 使用示例
|
||||
|
||||
```html
|
||||
<script lang="ts" setup>
|
||||
import { ref } from "vue"
|
||||
|
||||
const visible = ref(false)
|
||||
|
||||
function onAttachmentSelect (attachments: AttachmentLike[]) {
|
||||
console.log(attachments)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VButton @click="visible = true">选择附件</VButton>
|
||||
|
||||
<AttachmentSelectorModal
|
||||
v-model:visible="visible"
|
||||
@select="onAttachmentSelect"
|
||||
/>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| 属性名 | 类型 | 默认值 | 描述 |
|
||||
|-----------|----------|---------------|--------------------------|
|
||||
| `visible` | boolean | false | 控制组件是否可见。 |
|
||||
| `accepts` | string[] | () => ["*/*"] | 可选,定义可接受的文件类型。 |
|
||||
| `min` | number | undefined | 可选,定义最小选择数量。 |
|
||||
| `max` | number | undefined | 可选,定义最大选择数量。 |
|
||||
|
||||
## Emits
|
||||
|
||||
| 事件名称 | 参数 | 描述 |
|
||||
|----------------|---------------------------------------------------|---------------------|
|
||||
| update:visible | `visible`: boolean 类型,表示可见状态。 | 当可见状态更新时触发。 |
|
||||
| close | 无 | 当弹框关闭时触发。 |
|
||||
| select | `attachments`: AttachmentLike[] 类型,表示附件数组。 | 当选择确定按钮时触发。 |
|
@ -0,0 +1,18 @@
|
||||
---
|
||||
title: FilterCleanButton
|
||||
description: 过滤器清除按钮组件
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
```html
|
||||
<script lang="ts" setup>
|
||||
function onClear () {
|
||||
console.log("clear")
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FilterCleanButton @click="onClear" />
|
||||
</template>
|
||||
```
|
@ -0,0 +1,48 @@
|
||||
---
|
||||
title: FilterDropdown
|
||||
description: 过滤器下拉组件
|
||||
---
|
||||
|
||||
此组件为通用的下拉筛选组件,可以接收一个对象数组作为选项,并使用 `v-model` 绑定选择的值。
|
||||
|
||||
## 使用示例
|
||||
|
||||
```html
|
||||
<script lang="ts" setup>
|
||||
import { ref } from "vue"
|
||||
|
||||
const value = ref("")
|
||||
const items = [
|
||||
{
|
||||
label: "最近创建",
|
||||
value: "creationTimestamp,desc"
|
||||
},
|
||||
{
|
||||
label: "较晚创建",
|
||||
value: "creationTimestamp,asc"
|
||||
}
|
||||
]
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<FilterDropdown
|
||||
v-model="value"
|
||||
label="排序"
|
||||
:items="items"
|
||||
/>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| 属性名 | 类型 | 默认值 | 描述 |
|
||||
|--------------|-----------------------------------------------------------|-----------|--------------------------------------------------|
|
||||
| `items` | { label: string; value?: string \| boolean \| number; }[] | 无,必填 | 包含 `label` 和可选 `value` 的对象数组。 |
|
||||
| `label` | string | 无,必填 | 组件的标签文本。 |
|
||||
| `modelValue` | string \| boolean \| number | undefined | 可选,用于绑定到组件的值,可以是字符串、布尔值或数字。 |
|
||||
|
||||
## Emits
|
||||
|
||||
| 事件名称 | 参数 | 描述 |
|
||||
|-------------------|--------------------------------------------------------|-------------------|
|
||||
| update:modelValue | `modelValue`: string \| boolean \| number \| undefined | 当模型值更新时触发。 |
|
@ -0,0 +1,26 @@
|
||||
---
|
||||
title: HasPermission
|
||||
description: 权限判断组件
|
||||
---
|
||||
|
||||
此组件用于根据权限控制元素的显示与隐藏。
|
||||
|
||||
## 使用方式
|
||||
|
||||
```html
|
||||
<script lang="ts" setup>
|
||||
import { VButton } from "@halo-dev/components"
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<HasPermission :permissions="['system:posts:manage']">
|
||||
<VButton type="danger">删除</VButton>
|
||||
</HasPermission>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| 属性名 | 类型 | 默认值 | 描述 |
|
||||
|---------------|----------|------|-----------------------|
|
||||
| `permissions` | string[] | 无,必填 | 定义组件所需的权限列表。 |
|
@ -0,0 +1,42 @@
|
||||
---
|
||||
title: 组件
|
||||
description: 在 Halo UI 中可使用的组件。
|
||||
---
|
||||
|
||||
此文档将介绍所有在插件中可用的组件,以及它们的使用方法和区别。
|
||||
|
||||
## 基础组件库
|
||||
|
||||
我们为 Halo 的前端封装了一个基础组件的库,你可以在插件中使用这些组件。
|
||||
|
||||
安装方式:
|
||||
|
||||
```bash
|
||||
pnpm install @halo-dev/components
|
||||
```
|
||||
|
||||
在 Vue 组件中:
|
||||
|
||||
```html
|
||||
<script lang="ts" setup>
|
||||
import { VButton } from "@halo-dev/components";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VButton>Hello</VButton>
|
||||
</template>
|
||||
```
|
||||
|
||||
所有可用的基础组件以及文档可查阅:<https://halo-ui-components.pages.dev>
|
||||
|
||||
## 业务组件和指令
|
||||
|
||||
除了基础组件库,我们还为 Halo 的前端封装了一些业务组件和指令,这些组件已经在全局注册,你可以直接在插件中使用这些组件和指令。
|
||||
|
||||
以下是所有可用的业务组件和指令:
|
||||
|
||||
```mdx-code-block
|
||||
import DocCardList from '@theme/DocCardList';
|
||||
|
||||
<DocCardList />
|
||||
```
|
@ -0,0 +1,33 @@
|
||||
---
|
||||
title: SearchInput
|
||||
description: 搜索输入框组件
|
||||
---
|
||||
|
||||
此组件适用于关键词搜索场景,输入数据的过程中不会触发搜索,只有在输入完成后,点击回车才会触发搜索。
|
||||
|
||||
## 使用方式
|
||||
|
||||
```html
|
||||
<script lang="ts" setup>
|
||||
import { ref } from "vue"
|
||||
|
||||
const keyword = ref("")
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SearchInput v-model="keyword" placeholder="请输入关键字" />
|
||||
</template>
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| 属性名 | 类型 | 默认值 | 描述 |
|
||||
|---------------|--------|-----------|----------------------------------|
|
||||
| `placeholder` | string | undefined | 可选,用于指定输入字段的占位符文本。 |
|
||||
| `modelValue` | string | 无,必填 | 用于绑定输入字段的值。 |
|
||||
|
||||
## Emits
|
||||
|
||||
| 事件名称 | 参数 | 描述 |
|
||||
|-------------------|-------------------------------------|-------------------|
|
||||
| update:modelValue | `modelValue`: string 类型,表示模型值。 | 当模型值更新时触发。 |
|
@ -0,0 +1,36 @@
|
||||
---
|
||||
title: VCodemirror
|
||||
description: 代码编辑器组件
|
||||
---
|
||||
|
||||
此组件封装了 Codemirror 代码编辑器,适用于一些需要编辑代码的场景。
|
||||
|
||||
## 使用方式
|
||||
|
||||
```html
|
||||
<script lang="ts" setup>
|
||||
import { ref } from "vue"
|
||||
|
||||
const value = ref("")
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VCodemirror v-model="value" height="300px" language="html" />
|
||||
</template>
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| 属性名 | 类型 | 默认值 | 描述 |
|
||||
|--------------|-------------------------------------------------|----------|-------------------------------------------|
|
||||
| `modelValue` | string | "" | 可选,绑定到组件的字符串值,默认为空字符串。 |
|
||||
| `height` | string | auto | 可选,组件的高度,默认为 `"auto"`。 |
|
||||
| `language` | keyof typeof presetLanguages \| LanguageSupport | yaml | 代码编辑器的语言支持,默认为 `"yaml"`。 |
|
||||
| `extensions` | EditorStateConfig["extensions"] | () => [] | 可选,编辑器状态配置的扩展,默认为一个空数组。 |
|
||||
|
||||
## Emits
|
||||
|
||||
| 事件名称 | 参数 | 描述 |
|
||||
|-------------------|----------------------------------|-------------------|
|
||||
| update:modelValue | `value`: string 类型,表示模型值。 | 当模型值更新时触发。 |
|
||||
| change | `value`: string 类型,表示变更的值。 | 当值发生变化时触发。 |
|
@ -0,0 +1,18 @@
|
||||
---
|
||||
title: v-permission
|
||||
description: 权限指令
|
||||
---
|
||||
|
||||
与 [HasPermission](./has-permission.md) 组件相同,此指令也是用于根据权限控制元素的显示与隐藏。
|
||||
|
||||
## 使用方式
|
||||
|
||||
```html
|
||||
<script lang="ts" setup>
|
||||
import { VButton } from "@halo-dev/components"
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VButton type="danger" v-permission="['system:posts:manage']">删除</VButton>
|
||||
</template>
|
||||
```
|
@ -0,0 +1,18 @@
|
||||
---
|
||||
title: v-tooltip
|
||||
description: Tooltip 指令
|
||||
---
|
||||
|
||||
此指令用于在任何元素上添加一个提示框。
|
||||
|
||||
## 使用方式
|
||||
|
||||
```html
|
||||
<script lang="ts" setup>
|
||||
import { IconDeleteBin } from "@halo-dev/components"
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<IconDeleteBin v-tooltip="'删除此文档'" />
|
||||
</template>
|
||||
```
|
@ -0,0 +1,98 @@
|
||||
---
|
||||
title: 附件数据列表操作菜单
|
||||
description: 扩展附件数据列表操作菜单 - attachment:list-item:operation:create
|
||||
---
|
||||
|
||||
此扩展点用于扩展附件数据列表的操作菜单项。
|
||||
|
||||
![附件数据列表操作菜单](/img/developer-guide/plugin/api-reference/ui/extension-points/attachment-list-item-operation-create.png)
|
||||
|
||||
## 定义方式
|
||||
|
||||
```ts
|
||||
export default definePlugin({
|
||||
extensionPoints: {
|
||||
"attachment:list-item:operation:create": (
|
||||
attachment: Ref<Attachment>
|
||||
): OperationItem<Attachment>[] | Promise<OperationItem<Attachment>[]> => {
|
||||
return [
|
||||
{
|
||||
priority: 10,
|
||||
component: markRaw(VDropdownItem),
|
||||
props: {},
|
||||
action: (item?: Attachment) => {
|
||||
// do something
|
||||
},
|
||||
label: "foo",
|
||||
hidden: false,
|
||||
permissions: [],
|
||||
children: [],
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
```mdx-code-block
|
||||
import OperationItem from "./interface/OperationItem.md";
|
||||
|
||||
<OperationItem />
|
||||
```
|
||||
|
||||
## 示例
|
||||
|
||||
此示例将实现一个下载附件到本地的操作菜单项。
|
||||
|
||||
```ts
|
||||
import { definePlugin, type OperationItem } from "@halo-dev/console-shared";
|
||||
import { Toast, VDropdownItem } from "@halo-dev/components";
|
||||
import { markRaw, type Ref } from "vue";
|
||||
import type { Attachment } from "@halo-dev/api-client";
|
||||
|
||||
export default definePlugin({
|
||||
extensionPoints: {
|
||||
"attachment:list-item:operation:create": (
|
||||
attachment: Ref<Attachment>
|
||||
): OperationItem<Attachment>[] | Promise<OperationItem<Attachment>[]> => {
|
||||
return [
|
||||
{
|
||||
priority: 10,
|
||||
component: markRaw(VDropdownItem),
|
||||
props: {},
|
||||
action: (item?: Attachment) => {
|
||||
if (!item?.status?.permalink) {
|
||||
Toast.error("该附件没有下载地址");
|
||||
return;
|
||||
}
|
||||
|
||||
const a = document.createElement("a");
|
||||
a.href = item.status.permalink;
|
||||
a.download = item?.spec.displayName || item.metadata.name;
|
||||
a.click();
|
||||
},
|
||||
label: "下载",
|
||||
hidden: false,
|
||||
permissions: [],
|
||||
children: [],
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
## 实现案例
|
||||
|
||||
- <https://github.com/halo-dev/plugin-s3>
|
||||
|
||||
## 类型定义
|
||||
|
||||
### Attachment
|
||||
|
||||
```mdx-code-block
|
||||
import Attachment from "./interface/Attachment.md";
|
||||
|
||||
<Attachment />
|
||||
```
|
@ -0,0 +1,146 @@
|
||||
---
|
||||
title: 附件选择选项卡
|
||||
description: 扩展附件选择组件的选项卡 - attachment:selector:create
|
||||
---
|
||||
|
||||
此扩展点用于扩展附件选择组件的选项卡,目前 Halo 仅包含内置的附件库,你可以通过此扩展点添加自定义的选项卡。
|
||||
|
||||
![附件选择选项卡](/img/developer-guide/plugin/api-reference/ui/extension-points/attachment-selector-create.png)
|
||||
|
||||
## 定义方式
|
||||
|
||||
```ts
|
||||
export default definePlugin({
|
||||
extensionPoints: {
|
||||
"attachment:selector:create": (): AttachmentSelectProvider[]| Promise<AttachmentSelectProvider[]> => {
|
||||
return [
|
||||
{
|
||||
id: "foo",
|
||||
label: "foo",
|
||||
component: markRaw(FooComponent),
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
```ts title="AttachmentSelectProvider"
|
||||
export interface AttachmentSelectProvider {
|
||||
id: string; // 选项卡 ID
|
||||
label: string; // 选项卡名称
|
||||
component: Component | string; // 选项卡组件
|
||||
}
|
||||
```
|
||||
|
||||
其中,`component` 可以是组件对象或组件名称,且此组件有以下实现要求:
|
||||
|
||||
1. 组件必须包含名称为 `selected` 的 `prop`,用于接收当前选中的附件。
|
||||
|
||||
```ts
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
selected: AttachmentLike[];
|
||||
}>(),
|
||||
{
|
||||
selected: () => [],
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
2. 组件必须包含名称为 `update:selected` 的 emit 事件,用于更新选中的附件。
|
||||
|
||||
```ts
|
||||
const emit = defineEmits<{
|
||||
(event: "update:selected", attachments: AttachmentLike[]): void;
|
||||
}>();
|
||||
```
|
||||
|
||||
```ts title="AttachmentLike"
|
||||
export type AttachmentLike =
|
||||
| Attachment
|
||||
| string
|
||||
| {
|
||||
url: string;
|
||||
type: string;
|
||||
};
|
||||
```
|
||||
|
||||
## 示例
|
||||
|
||||
为附件选择组件添加 Stickers 选项卡,用于从给定的贴纸列表选择附件。
|
||||
|
||||
```ts title="index.ts"
|
||||
import {
|
||||
definePlugin,
|
||||
type AttachmentSelectProvider,
|
||||
} from "@halo-dev/console-shared";
|
||||
import { markRaw } from "vue";
|
||||
import StickerSelectorProvider from "./components/StickerSelectorProvider.vue";
|
||||
|
||||
export default definePlugin({
|
||||
components: {},
|
||||
routes: [],
|
||||
extensionPoints: {
|
||||
"attachment:selector:create": (): AttachmentSelectProvider[] => {
|
||||
return [
|
||||
{
|
||||
id: "stickers",
|
||||
label: "Stickers",
|
||||
component: markRaw(StickerSelectorProvider),
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
```html title="StickerSelectorProvider.vue"
|
||||
<script lang="ts" setup>
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
selected: AttachmentLike[];
|
||||
}>(),
|
||||
{
|
||||
selected: () => [],
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: "update:selected", attachments: AttachmentLike[]): void;
|
||||
}>();
|
||||
|
||||
const stickers = [
|
||||
{
|
||||
url: "https://picsum.photos/200?random=1",
|
||||
},
|
||||
{
|
||||
url: "https://picsum.photos/200?random=2",
|
||||
},
|
||||
{
|
||||
url: "https://picsum.photos/200?random=3",
|
||||
},
|
||||
];
|
||||
|
||||
const selectedStickers = ref<Set<String>>(new Set());
|
||||
|
||||
const handleSelect = async (url: string) => {
|
||||
if (selectedStickers.value.has(url)) {
|
||||
selectedStickers.value.delete(url);
|
||||
return;
|
||||
}
|
||||
selectedStickers.value.add(url);
|
||||
emit('update:selected', Array.from(selectedStickers.value));
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<img v-for="sticker in stickers" :src="sticker.url" @click="handleSelect(sticker.url)" />
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
## 实现案例
|
||||
|
||||
- <https://github.com/halo-sigs/plugin-unsplash>
|
@ -0,0 +1,41 @@
|
||||
---
|
||||
title: 备份数据列表操作菜单
|
||||
description: 扩展备份数据列表操作菜单 - backup:list-item:operation:create
|
||||
---
|
||||
|
||||
此扩展点用于扩展备份数据列表的操作菜单项。
|
||||
|
||||
![备份数据列表操作菜单](/img/developer-guide/plugin/api-reference/ui/extension-points/backup-list-item-operation-create.png)
|
||||
|
||||
## 定义方式
|
||||
|
||||
```ts
|
||||
export default definePlugin({
|
||||
extensionPoints: {
|
||||
"backup:list-item:operation:create": (
|
||||
backup: Ref<Backup>
|
||||
): OperationItem<Backup>[] | Promise<OperationItem<Backup>[]> => {
|
||||
return [
|
||||
{
|
||||
priority: 10,
|
||||
component: markRaw(VDropdownItem),
|
||||
props: {},
|
||||
action: (item?: Backup) => {
|
||||
// do something
|
||||
},
|
||||
label: "foo",
|
||||
hidden: false,
|
||||
permissions: [],
|
||||
children: [],
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
```mdx-code-block
|
||||
import OperationItem from "./interface/OperationItem.md";
|
||||
|
||||
<OperationItem />
|
||||
```
|
@ -0,0 +1,36 @@
|
||||
---
|
||||
title: 备份页面选项卡
|
||||
description: 扩展备份页面选项卡 - backup:tabs:create
|
||||
---
|
||||
|
||||
此扩展点可以针对备份页面扩展更多关于 UI 的功能,比如定时备份设置、备份到第三方云存储等。
|
||||
|
||||
![备份页面选项卡](/img/developer-guide/plugin/api-reference/ui/extension-points/backup-tabs-create.png)
|
||||
|
||||
## 定义方式
|
||||
|
||||
```ts
|
||||
export default definePlugin({
|
||||
extensionPoints: {
|
||||
"backup:tabs:create": (): BackupTab[] | Promise<BackupTab[]> => {
|
||||
return [
|
||||
{
|
||||
id: "foo",
|
||||
label: "foo",
|
||||
component: markRaw(FooComponent),
|
||||
permissions: [],
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
```ts title="BackupTab"
|
||||
export interface BackupTab {
|
||||
id: string; // 选项卡 ID
|
||||
label: string; // 选项卡标题
|
||||
component: Raw<Component>; // 选项卡面板组件
|
||||
permissions?: string[]; // 选项卡权限
|
||||
}
|
||||
```
|
@ -0,0 +1,114 @@
|
||||
---
|
||||
title: 评论来源显示
|
||||
description: 扩展评论来源显示 - comment:subject-ref:create
|
||||
---
|
||||
|
||||
Console 的评论管理列表的评论来源默认仅支持显示来自文章和页面的评论,如果其他插件中的业务模块也使用了评论,那么就可以通过该拓展点来扩展评论来源的显示。
|
||||
|
||||
:::info 提示
|
||||
此扩展点需要后端配合使用,请参考 [评论主体展示](../../server/extension-points/comment-subject.md)。
|
||||
:::
|
||||
|
||||
![评论来源显示](/img/developer-guide/plugin/api-reference/ui/extension-points/comment-subject-ref-create.png)
|
||||
|
||||
## 定义方式
|
||||
|
||||
```ts
|
||||
export default definePlugin({
|
||||
extensionPoints: {
|
||||
"comment:subject-ref:create": (): CommentSubjectRefProvider[] => {
|
||||
return [
|
||||
{
|
||||
kind: "Example",
|
||||
group: "example.halo.run",
|
||||
resolve: (subject: Extension): CommentSubjectRefResult => {
|
||||
return {
|
||||
label: "foo",
|
||||
title: subject.title,
|
||||
externalUrl: `/example/${subject.metadata.name}`,
|
||||
route: {
|
||||
name: "Example",
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
```ts title="CommentSubjectRefProvider"
|
||||
export type CommentSubjectRefProvider = {
|
||||
kind: string;
|
||||
group: string;
|
||||
resolve: (subject: Extension) => CommentSubjectRefResult;
|
||||
};
|
||||
```
|
||||
|
||||
```ts title="CommentSubjectRefResult"
|
||||
export interface CommentSubjectRefResult {
|
||||
label: string;
|
||||
title: string;
|
||||
route?: RouteLocationRaw;
|
||||
externalUrl?: string;
|
||||
}
|
||||
```
|
||||
|
||||
## 示例
|
||||
|
||||
此示例以[瞬间插件](https://github.com/halo-sigs/plugin-moments)为例,目前瞬间插件中的评论是通过 Halo 的内置的评论功能实现的,所以需要扩展评论来源显示。
|
||||
|
||||
```ts
|
||||
import {
|
||||
definePlugin,
|
||||
type CommentSubjectRefResult,
|
||||
} from "@halo-dev/console-shared";
|
||||
import { markRaw } from "vue";
|
||||
import type { Moment } from "@/types";
|
||||
import { formatDatetime } from "./utils/date";
|
||||
import type { Extension } from "@halo-dev/api-client/index";
|
||||
|
||||
export default definePlugin({
|
||||
extensionPoints: {
|
||||
"comment:subject-ref:create": () => {
|
||||
return [
|
||||
{
|
||||
kind: "Moment",
|
||||
group: "moment.halo.run",
|
||||
resolve: (subject: Extension): CommentSubjectRefResult => {
|
||||
const moment = subject as Moment;
|
||||
return {
|
||||
label: "瞬间",
|
||||
title: determineMomentTitle(moment),
|
||||
externalUrl: `/moments/${moment.metadata.name}`,
|
||||
route: {
|
||||
name: "Moments",
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const determineMomentTitle = (moment: Moment) => {
|
||||
const pureContent = stripHtmlTags(moment.spec.content.raw);
|
||||
const title = !pureContent?.trim()
|
||||
? formatDatetime(new Date(moment.spec.releaseTime || ""))
|
||||
: pureContent;
|
||||
return title?.substring(0, 100);
|
||||
};
|
||||
|
||||
const stripHtmlTags = (str: string) => {
|
||||
// strip html tags
|
||||
const stripped = str?.replace(/<\/?[^>]+(>|$)/gi, "") || "";
|
||||
// strip newlines and collapse spaces
|
||||
return stripped.replace(/\n/g, " ").replace(/\s+/g, " ");
|
||||
};
|
||||
```
|
||||
|
||||
## 实现案例
|
||||
|
||||
- <https://github.com/halo-sigs/plugin-moments>
|
@ -0,0 +1,14 @@
|
||||
---
|
||||
title: 扩展点
|
||||
description: Halo UI 为插件提供的扩展点接口
|
||||
---
|
||||
|
||||
UI 扩展点是用于扩展 Console 和 UC 的界面的接口,通过实现扩展点接口,插件可以在 Console 和 UC 中扩展功能。
|
||||
|
||||
以下是目前已支持的扩展点列表:
|
||||
|
||||
```mdx-code-block
|
||||
import DocCardList from '@theme/DocCardList';
|
||||
|
||||
<DocCardList />
|
||||
```
|
@ -0,0 +1,25 @@
|
||||
```ts
|
||||
export interface Attachment {
|
||||
apiVersion: "storage.halo.run/v1alpha1"
|
||||
kind: "Attachment"
|
||||
metadata: {
|
||||
annotations: {}
|
||||
creationTimestamp: string
|
||||
labels: {}
|
||||
name: string // 附件的唯一标识
|
||||
version: number
|
||||
}
|
||||
spec: {
|
||||
displayName: string // 附件名称
|
||||
groupName: string // 附件分组
|
||||
mediaType: string // 附件类型
|
||||
ownerName: string // 附件上传者
|
||||
policyName: string // 附件存储策略
|
||||
size: number // 附件大小
|
||||
tags: Array<string>
|
||||
}
|
||||
status: {
|
||||
permalink: string // 附件固定访问地址
|
||||
}
|
||||
}
|
||||
```
|
@ -0,0 +1,119 @@
|
||||
```ts
|
||||
export interface ListedPost {
|
||||
categories: Array<{ // 文章的分类集合
|
||||
apiVersion: "content.halo.run/v1alpha1";
|
||||
kind: "Category";
|
||||
metadata: {
|
||||
annotations: {};
|
||||
creationTimestamp: string;
|
||||
labels: {};
|
||||
name: string; // 分类的唯一标识
|
||||
version: number;
|
||||
};
|
||||
spec: {
|
||||
children: Array<string>; // 子分类
|
||||
cover: string; // 分类封面图
|
||||
description: string; // 分类描述
|
||||
displayName: string; // 分类名称
|
||||
priority: number; // 分类优先级
|
||||
slug: string; // 分类别名
|
||||
template: string; // 分类渲染模板
|
||||
};
|
||||
status: {
|
||||
permalink: string; // 分类的永久链接
|
||||
postCount: number; // 分类下的文章总数
|
||||
visiblePostCount: number; // 分类下可见的文章总数
|
||||
};
|
||||
}>;
|
||||
contributors: Array<{ // 文章的贡献者集合
|
||||
avatar: string; // 贡献者头像
|
||||
displayName: string; // 贡献者名称
|
||||
name: string; // 贡献者唯一标识
|
||||
}>;
|
||||
owner: { // 文章的作者信息
|
||||
avatar: string; // 作者头像
|
||||
displayName: string; // 作者名称
|
||||
name: string; // 作者唯一标识
|
||||
};
|
||||
post: { // 文章信息
|
||||
apiVersion: "content.halo.run/v1alpha1";
|
||||
kind: "Post";
|
||||
metadata: {
|
||||
annotations: {};
|
||||
creationTimestamp: string;
|
||||
labels: {};
|
||||
name: string; // 文章的唯一标识
|
||||
version: number;
|
||||
};
|
||||
spec: {
|
||||
allowComment: boolean; // 是否允许评论
|
||||
baseSnapshot: string; // 内容基础快照
|
||||
categories: Array<string>; // 文章所属分类
|
||||
cover: string; // 文章封面图
|
||||
deleted: boolean; // 是否已删除
|
||||
excerpt: { // 文章摘要
|
||||
autoGenerate: boolean; // 是否自动生成
|
||||
raw: string; // 摘要内容
|
||||
};
|
||||
headSnapshot: string; // 内容最新快照
|
||||
htmlMetas: Array<{}>;
|
||||
owner: string; // 文章作者的唯一标识
|
||||
pinned: boolean; // 是否置顶
|
||||
priority: number; // 文章优先级
|
||||
publish: boolean; // 是否发布
|
||||
publishTime: string; // 发布时间
|
||||
releaseSnapshot: string; // 已发布的内容快照
|
||||
slug: string; // 文章别名
|
||||
tags: Array<string>; // 文章所属标签
|
||||
template: string; // 文章渲染模板
|
||||
title: string; // 文章标题
|
||||
visible: string; // 文章可见性
|
||||
};
|
||||
status: {
|
||||
commentsCount: number; // 文章评论总数
|
||||
conditions: Array<{
|
||||
lastTransitionTime: string;
|
||||
message: string;
|
||||
reason: string;
|
||||
status: string;
|
||||
type: string;
|
||||
}>;
|
||||
contributors: Array<string>;
|
||||
excerpt: string; // 最终的文章摘要,根据是否自动生成计算
|
||||
inProgress: boolean; // 是否有未发布的内容
|
||||
lastModifyTime: string; // 文章最后修改时间
|
||||
permalink: string; // 文章的永久链接
|
||||
phase: string;
|
||||
};
|
||||
};
|
||||
stats: {
|
||||
approvedComment: number; // 已审核的评论数
|
||||
totalComment: number; // 评论总数
|
||||
upvote: number; // 点赞数
|
||||
visit: number; // 访问数
|
||||
};
|
||||
tags: Array<{ // 文章的标签集合
|
||||
apiVersion: "content.halo.run/v1alpha1";
|
||||
kind: "Tag";
|
||||
metadata: {
|
||||
annotations: {};
|
||||
creationTimestamp: string;
|
||||
labels: {};
|
||||
name: string; // 标签的唯一标识
|
||||
version: number;
|
||||
};
|
||||
spec: {
|
||||
color: string; // 标签颜色
|
||||
cover: string; // 标签封面图
|
||||
displayName: string; // 标签名称
|
||||
slug: string; // 标签别名
|
||||
};
|
||||
status: {
|
||||
permalink: string; // 标签的永久链接
|
||||
postCount: number; // 标签下的文章总数
|
||||
visiblePostCount: number; // 标签下可见的文章总数
|
||||
};
|
||||
}>;
|
||||
}
|
||||
|
||||
```
|
@ -0,0 +1,12 @@
|
||||
```ts title="OperationItem"
|
||||
export interface OperationItem<T> {
|
||||
priority: number; // 排序优先级
|
||||
component: Raw<Component>; // 菜单项组件
|
||||
props?: Record<string, unknown>; // 菜单项组件属性
|
||||
action?: (item?: T) => void; // 菜单项点击事件
|
||||
label?: string; // 菜单项标题
|
||||
hidden?: boolean; // 菜单项是否隐藏
|
||||
permissions?: string[]; // 菜单项 UI 权限
|
||||
children?: OperationItem<T>[]; // 子菜单项
|
||||
}
|
||||
```
|
@ -0,0 +1,50 @@
|
||||
```ts
|
||||
export interface Plugin {
|
||||
apiVersion: "plugin.halo.run/v1alpha1"
|
||||
kind: "Plugin"
|
||||
metadata: {
|
||||
annotations: {}
|
||||
creationTimestamp: string // 创建时间
|
||||
labels: {}
|
||||
name: string // 唯一标识
|
||||
version: number
|
||||
}
|
||||
spec: {
|
||||
author: { // 作者信息
|
||||
name: string
|
||||
website: string
|
||||
}
|
||||
configMapName: string // 关联的 ConfigMap 模型,用于存储配置
|
||||
description: string // 插件描述
|
||||
displayName: string // 插件名称
|
||||
enabled: boolean
|
||||
homepage: string // 插件主页
|
||||
license: Array<{ // 插件协议
|
||||
name: string
|
||||
url: string
|
||||
}>
|
||||
logo: string
|
||||
pluginDependencies: {}
|
||||
repo: string // 插件仓库地址
|
||||
requires: string // 所依赖的 Halo 版本
|
||||
settingName: string // 关联的 Setting 模型,用于渲染配置表单
|
||||
version: string // 插件版本
|
||||
}
|
||||
status: {
|
||||
conditions: Array<{
|
||||
lastTransitionTime: string
|
||||
message: string
|
||||
reason: string
|
||||
status: string
|
||||
type: string
|
||||
}>
|
||||
entry: string
|
||||
lastProbeState: string
|
||||
lastStartTime: string
|
||||
loadLocation: string
|
||||
logo: string // 插件 Logo 地址
|
||||
phase: string
|
||||
stylesheet: string
|
||||
}
|
||||
}
|
||||
```
|
@ -0,0 +1,63 @@
|
||||
```ts
|
||||
export interface Theme {
|
||||
apiVersion: "theme.halo.run/v1alpha1"
|
||||
kind: "Theme"
|
||||
metadata: {
|
||||
annotations: {}
|
||||
creationTimestamp: string
|
||||
labels: {}
|
||||
name: string // 主题的唯一标识
|
||||
version: number
|
||||
}
|
||||
spec: {
|
||||
author: { // 主题作者信息
|
||||
name: string
|
||||
website: string
|
||||
}
|
||||
configMapName: string // 关联的 ConfigMap 模型,用于存储配置
|
||||
customTemplates: { // 自定义模板信息
|
||||
category: Array<{
|
||||
description: string
|
||||
file: string
|
||||
name: string
|
||||
screenshot: string
|
||||
}>
|
||||
page: Array<{
|
||||
description: string
|
||||
file: string
|
||||
name: string
|
||||
screenshot: string
|
||||
}>
|
||||
post: Array<{
|
||||
description: string
|
||||
file: string
|
||||
name: string
|
||||
screenshot: string
|
||||
}>
|
||||
}
|
||||
description: string // 主题描述
|
||||
displayName: string // 主题名称
|
||||
homepage: string // 主题主页
|
||||
license: Array<{ // 主题许可证信息
|
||||
name: string
|
||||
url: string
|
||||
}>
|
||||
logo: string // 主题 Logo
|
||||
repo: string // 主题仓库地址
|
||||
requires: string // 所依赖的 Halo 版本
|
||||
settingName: string // 关联的 Setting 模型,用于渲染配置表单
|
||||
version: string // 主题版本
|
||||
}
|
||||
status: {
|
||||
conditions: Array<{
|
||||
lastTransitionTime: string
|
||||
message: string
|
||||
reason: string
|
||||
status: string
|
||||
type: string
|
||||
}>
|
||||
location: string
|
||||
phase: string
|
||||
}
|
||||
}
|
||||
```
|
@ -0,0 +1,44 @@
|
||||
---
|
||||
title: 插件安装界面选项卡
|
||||
description: 扩展插件安装界面选项卡 - plugin:installation:tabs:create
|
||||
---
|
||||
|
||||
目前 Halo 原生支持本地上传和远程下载的方式安装插件,此扩展点用于扩展插件安装界面的选项卡,以支持更多的安装方式。
|
||||
|
||||
![插件安装界面选项卡](/img/developer-guide/plugin/api-reference/ui/extension-points/plugin-installation-tabs-create.png)
|
||||
|
||||
## 定义方式
|
||||
|
||||
```ts
|
||||
export default definePlugin({
|
||||
extensionPoints: {
|
||||
"plugin:installation:tabs:create": (): PluginInstallationTab[] | Promise<PluginInstallationTab[]> => {
|
||||
return [
|
||||
{
|
||||
id: "foo",
|
||||
label: "foo",
|
||||
component: markRaw(FooComponent),
|
||||
props: {},
|
||||
permissions: [],
|
||||
priority: 0,
|
||||
}
|
||||
];
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
```ts title="PluginInstallationTab"
|
||||
export interface PluginInstallationTab {
|
||||
id: string; // 选项卡 ID
|
||||
label: string; // 选项卡标题
|
||||
component: Raw<Component>; // 选项卡面板组件
|
||||
props?: Record<string, unknown>; // 选项卡面板组件属性
|
||||
permissions?: string[]; // 选项卡 UI 权限
|
||||
priority: number; // 选项卡排序优先级
|
||||
}
|
||||
```
|
||||
|
||||
## 实现案例
|
||||
|
||||
- <https://github.com/halo-dev/plugin-app-store>
|
@ -0,0 +1,55 @@
|
||||
---
|
||||
title: 插件数据列表操作菜单
|
||||
description: 扩展插件数据列表操作菜单 - plugin:list-item:operation:create
|
||||
---
|
||||
|
||||
此扩展点用于扩展插件数据列表的操作菜单项。
|
||||
|
||||
![插件数据列表操作菜单](/img/developer-guide/plugin/api-reference/ui/extension-points/plugin-list-item-operation-create.png)
|
||||
|
||||
## 定义方式
|
||||
|
||||
```ts
|
||||
export default definePlugin({
|
||||
extensionPoints: {
|
||||
"plugin:list-item:operation:create": (
|
||||
plugin: Ref<Plugin>
|
||||
): OperationItem<Plugin>[] | Promise<OperationItem<Plugin>[]> => {
|
||||
return [
|
||||
{
|
||||
priority: 10,
|
||||
component: markRaw(VDropdownItem),
|
||||
props: {},
|
||||
action: (item?: Plugin) => {
|
||||
// do something
|
||||
},
|
||||
label: "foo",
|
||||
hidden: false,
|
||||
permissions: [],
|
||||
children: [],
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
```mdx-code-block
|
||||
import OperationItem from "./interface/OperationItem.md";
|
||||
|
||||
<OperationItem />
|
||||
```
|
||||
|
||||
## 实现案例
|
||||
|
||||
- <https://github.com/halo-dev/plugin-app-store>
|
||||
|
||||
## 类型定义
|
||||
|
||||
### Plugin
|
||||
|
||||
```mdx-code-block
|
||||
import Plugin from "./interface/Plugin.md";
|
||||
|
||||
<Plugin />
|
||||
```
|
@ -0,0 +1,92 @@
|
||||
---
|
||||
title: 文章数据列表操作菜单
|
||||
description: 扩展文章数据列表操作菜单 - post:list-item:operation:create
|
||||
---
|
||||
|
||||
此扩展点用于扩展文章数据列表的操作菜单项。
|
||||
|
||||
![文章数据列表操作菜单](/img/developer-guide/plugin/api-reference/ui/extension-points/post-list-item-operation-create.png)
|
||||
|
||||
## 定义方式
|
||||
|
||||
```ts
|
||||
export default definePlugin({
|
||||
extensionPoints: {
|
||||
"post:list-item:operation:create": (
|
||||
post: Ref<ListedPost>
|
||||
): OperationItem<ListedPost>[] | Promise<OperationItem<ListedPost>[]> => {
|
||||
return [
|
||||
{
|
||||
priority: 10,
|
||||
component: markRaw(VDropdownItem),
|
||||
props: {},
|
||||
action: (item?: ListedPost) => {
|
||||
// do something
|
||||
},
|
||||
label: "foo",
|
||||
hidden: false,
|
||||
permissions: [],
|
||||
children: [],
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
```mdx-code-block
|
||||
import OperationItem from "./interface/OperationItem.md";
|
||||
|
||||
<OperationItem />
|
||||
```
|
||||
|
||||
## 示例
|
||||
|
||||
此示例将实现一个操作菜单项,点击后会将文章内容作为文件下载到本地。
|
||||
|
||||
```ts
|
||||
import type { ListedPost } from "@halo-dev/api-client";
|
||||
import { VDropdownItem } from "@halo-dev/components";
|
||||
import { definePlugin } from "@halo-dev/console-shared";
|
||||
import axios from "axios";
|
||||
import { markRaw } from "vue";
|
||||
|
||||
export default definePlugin({
|
||||
extensionPoints: {
|
||||
"post:list-item:operation:create": () => {
|
||||
return [
|
||||
{
|
||||
priority: 21,
|
||||
component: markRaw(VDropdownItem),
|
||||
label: "下载到本地",
|
||||
visible: true,
|
||||
permissions: [],
|
||||
action: async (post: ListedPost) => {
|
||||
const { data } = await axios.get(
|
||||
`/apis/api.console.halo.run/v1alpha1/posts/${post.post.metadata.name}/head-content`
|
||||
);
|
||||
const blob = new Blob([data.raw], {
|
||||
type: "text/plain;charset=utf-8",
|
||||
});
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.download = `${post.post.spec.title}.${data.rawType}`;
|
||||
link.click();
|
||||
},
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## 类型定义
|
||||
|
||||
### ListedPost
|
||||
|
||||
```mdx-code-block
|
||||
import ListedPost from "./interface/ListedPost.md";
|
||||
|
||||
<ListedPost />
|
||||
```
|
@ -0,0 +1,91 @@
|
||||
---
|
||||
title: 主题数据列表操作菜单
|
||||
description: 扩展主题数据列表操作菜单 - theme:list-item:operation:create
|
||||
---
|
||||
|
||||
此扩展点用于扩展主题数据列表的操作菜单项。
|
||||
|
||||
![主题数据列表操作菜单](/img/developer-guide/plugin/api-reference/ui/extension-points/theme-list-item-operation-create.png)
|
||||
|
||||
## 定义方式
|
||||
|
||||
```ts
|
||||
export default definePlugin({
|
||||
extensionPoints: {
|
||||
"theme:list-item:operation:create": (
|
||||
theme: Ref<Theme>
|
||||
): OperationItem<Theme>[] | Promise<OperationItem<Theme>[]> => {
|
||||
return [
|
||||
{
|
||||
priority: 10,
|
||||
component: markRaw(VDropdownItem),
|
||||
props: {},
|
||||
action: (item?: Theme) => {
|
||||
// do something
|
||||
},
|
||||
label: "foo",
|
||||
hidden: false,
|
||||
permissions: [],
|
||||
children: [],
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
```mdx-code-block
|
||||
import OperationItem from "./interface/OperationItem.md";
|
||||
|
||||
<OperationItem />
|
||||
```
|
||||
|
||||
## 示例
|
||||
|
||||
此示例将实现一个跳转到前台预览主题的操作菜单项。
|
||||
|
||||
```ts
|
||||
import { definePlugin, type OperationItem } from "@halo-dev/console-shared";
|
||||
import { VButton } from "@halo-dev/components";
|
||||
import { markRaw, type Ref } from "vue";
|
||||
import type { Theme } from "@halo-dev/api-client";
|
||||
|
||||
export default definePlugin({
|
||||
extensionPoints: {
|
||||
"theme:list-item:operation:create": (
|
||||
theme: Ref<Theme>
|
||||
): OperationItem<Theme>[] | Promise<OperationItem<Theme>[]> => {
|
||||
return [
|
||||
{
|
||||
priority: 10,
|
||||
component: markRaw(VButton),
|
||||
props: {
|
||||
size: "sm",
|
||||
},
|
||||
action: (item?: Theme) => {
|
||||
window.open(`/?preview-theme=${item?.metadata.name}`);
|
||||
},
|
||||
label: "前台预览",
|
||||
hidden: false,
|
||||
permissions: [],
|
||||
children: [],
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## 实现案例
|
||||
|
||||
- <https://github.com/halo-dev/plugin-app-store>
|
||||
|
||||
## 类型定义
|
||||
|
||||
### Theme
|
||||
|
||||
```mdx-code-block
|
||||
import Theme from "./interface/Theme.md";
|
||||
|
||||
<Theme />
|
||||
```
|
@ -0,0 +1,44 @@
|
||||
---
|
||||
title: 主题管理界面选项卡
|
||||
description: 扩展主题管理界面选项卡 - theme:list:tabs:create
|
||||
---
|
||||
|
||||
目前在 Halo 的主题管理中原生支持本地上传和远程下载的方式安装主题,此扩展点用于扩展主题管理界面的选项卡,以支持更多的安装方式。
|
||||
|
||||
![主题管理界面选项卡](/img/developer-guide/plugin/api-reference/ui/extension-points/theme-list-tabs-create.png)
|
||||
|
||||
## 定义方式
|
||||
|
||||
```ts
|
||||
export default definePlugin({
|
||||
extensionPoints: {
|
||||
"theme:list:tabs:create": (): ThemeListTab[] | Promise<ThemeListTab[]> => {
|
||||
return [
|
||||
{
|
||||
id: "foo",
|
||||
label: "foo",
|
||||
component: markRaw(FooComponent),
|
||||
props: {},
|
||||
permissions: [],
|
||||
priority: 0,
|
||||
}
|
||||
];
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
```ts title="ThemeListTab"
|
||||
export interface ThemeListTab {
|
||||
id: string; // 选项卡 ID
|
||||
label: string; // 选项卡标题
|
||||
component: Raw<Component>; // 选项卡面板组件
|
||||
props?: Record<string, unknown>; // 选项卡面板组件属性
|
||||
permissions?: string[]; // 选项卡 UI 权限
|
||||
priority: number; // 选项卡排序优先级
|
||||
}
|
||||
```
|
||||
|
||||
## 实现案例
|
||||
|
||||
- <https://github.com/halo-dev/plugin-app-store>
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
title: 附录
|
||||
description: 附录
|
||||
---
|
@ -0,0 +1,12 @@
|
||||
---
|
||||
title: 介绍
|
||||
description: 介绍插件 UI 部分的基础知识。
|
||||
---
|
||||
|
||||
Halo 插件体系的 UI 部分可以让开发者在 Console 控制台和 UC 个人中心添加新的页面或者扩展已有的功能。
|
||||
|
||||
在开始之前,建议先熟悉或安装以下库和工具:
|
||||
|
||||
1. [Node.js 18+](https://nodejs.org)
|
||||
2. [pnpm 8+](https://pnpm.io)
|
||||
3. [Vue.js 3](https://vuejs.org)
|
After Width: | Height: | Size: 89 KiB |
After Width: | Height: | Size: 83 KiB |
After Width: | Height: | Size: 68 KiB |
After Width: | Height: | Size: 69 KiB |
After Width: | Height: | Size: 70 KiB |
After Width: | Height: | Size: 66 KiB |
After Width: | Height: | Size: 103 KiB |
After Width: | Height: | Size: 86 KiB |
After Width: | Height: | Size: 64 KiB |
After Width: | Height: | Size: 81 KiB |
After Width: | Height: | Size: 78 KiB |
After Width: | Height: | Size: 216 KiB |
After Width: | Height: | Size: 126 KiB |
After Width: | Height: | Size: 140 KiB |
After Width: | Height: | Size: 74 KiB |
After Width: | Height: | Size: 73 KiB |
After Width: | Height: | Size: 78 KiB |
After Width: | Height: | Size: 236 KiB |
After Width: | Height: | Size: 91 KiB |
After Width: | Height: | Size: 232 KiB |