Compare commits
	
		
			1 Commits 
		
	
	
		
			main
			...
			JohnNiang-
		
	
	| Author | SHA1 | Date | 
|---|---|---|
|  | 83233f49ce | 2 years ago | 
| @ -1,72 +0,0 @@ | ||||
| --- | ||||
| title: 获取扩展 | ||||
| description: 了解如何在插件中使用 `ExtensionGetter` 获取扩展 | ||||
| --- | ||||
| 
 | ||||
| `ExtensionGetter` 用于获取和管理 Halo 或其他插件提供的扩展。它提供了多种方法来根据扩展点获取扩展,确保插件能够灵活地集成和使用各种扩展功能。 | ||||
| 
 | ||||
| `ExtensionGetter` 接口的定义如下: | ||||
| 
 | ||||
| ```java | ||||
| public interface ExtensionGetter { | ||||
| 
 | ||||
|     /** | ||||
|      * Get only one enabled extension from system configuration. | ||||
|      * | ||||
|      * @param extensionPoint is extension point class. | ||||
|      * @return implementation of the corresponding extension point. If no configuration is found, | ||||
|      * we will use the default implementation from application context instead. | ||||
|      */ | ||||
|     <T extends ExtensionPoint> Mono<T> getEnabledExtension(Class<T> extensionPoint); | ||||
| 
 | ||||
|     /** | ||||
|      * Get the extension(s) according to the {@link ExtensionPointDefinition} queried | ||||
|      * by incoming extension point class. | ||||
|      * | ||||
|      * @param extensionPoint extension point class | ||||
|      * @return implementations of the corresponding extension point. | ||||
|      * @throws IllegalArgumentException if the incoming extension point class does not have | ||||
|      *                                  the {@link ExtensionPointDefinition}. | ||||
|      */ | ||||
|     <T extends ExtensionPoint> Flux<T> getEnabledExtensions(Class<T> extensionPoint); | ||||
| 
 | ||||
|     /** | ||||
|      * Get all extensions according to extension point class. | ||||
|      * | ||||
|      * @param extensionPointClass extension point class | ||||
|      * @param <T> type of extension point | ||||
|      * @return a bunch of extension points. | ||||
|      */ | ||||
|     <T extends ExtensionPoint> Flux<T> getExtensions(Class<T> extensionPointClass); | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| 包含以下方法: | ||||
| 
 | ||||
| 1. `getEnabledExtension(Class<T> extensionPoint)`: 获取一个在扩展设置中已启用的扩展。如果没有找到对应配置,将使用 Halo 中的默认扩展,如果 Halo 没有提供默认实现则找到一个由**已启用插件**提供的可用扩展。 | ||||
| 2. `getEnabledExtensions(Class<T> extensionPoint)`: 根据传入的扩展点类获取所有已启用扩展。如果没有在扩展设置页面配置过则会返回所有可用的扩展。 | ||||
| 3. `getExtensions(Class<T> extensionPointClass)`: 获取所有与扩展点类相关的扩展,无论是否在扩展设置中启用它。 | ||||
| 
 | ||||
| :::tip Note | ||||
| 使用 `getEnabledExtension` 方法或者 `getEnabledExtensions` 方法取决于扩展点声明的 `type` 是 `SINGLETON` 还是 `MULTI_INSTANCE`。 | ||||
| 
 | ||||
| 通过使用 `ExtensionGetter`,开发者可以轻松地在插件中访问和管理各种扩展点,提升插件的功能和灵活性。 | ||||
| 
 | ||||
| 如果想了解 Halo 提供的扩展点请参考:[扩展点](./extension-points/index.md)。 | ||||
| ::: | ||||
| 
 | ||||
| ### 示例 | ||||
| 
 | ||||
| 如果你想在插件中获取已启用的搜索引擎扩展,可以使用 `ExtensionGetter` 来获取: | ||||
| 
 | ||||
| ```java | ||||
| @Service | ||||
| @RequiredArgsConstructor | ||||
| public class SearchService { | ||||
|     private final ExtensionGetter extensionGetter; | ||||
| 
 | ||||
|     Mono<SearchEngine> getSearchEngine() { | ||||
|         return extensionGetter.getEnabledExtension(SearchEngine.class) | ||||
|     } | ||||
| } | ||||
| ``` | ||||
| @ -1,55 +0,0 @@ | ||||
| --- | ||||
| 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) | ||||
| @ -1,83 +0,0 @@ | ||||
| --- | ||||
| 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) | ||||
| @ -1,65 +0,0 @@ | ||||
| --- | ||||
| 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 /> | ||||
| ``` | ||||
| @ -1,43 +0,0 @@ | ||||
| --- | ||||
| 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 格式。 | ||||
| @ -1,38 +0,0 @@ | ||||
| --- | ||||
| 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`。 | ||||
| @ -1,77 +0,0 @@ | ||||
| --- | ||||
| title: 主题端 Halo Footer 标签处理 | ||||
| description: 提供扩展主题端 HTML 页面中的 <halo:footer/> 标签内容处理的方法。 | ||||
| --- | ||||
| 
 | ||||
| Halo 为主题端模板提供了自定义标签 `<halo:footer/>` 的处理扩展点,以便可以添加额外的页脚内容如版权信息、备案号等。 | ||||
| 
 | ||||
| ## 使用场景 | ||||
| 
 | ||||
| - 添加备案号 | ||||
| - 添加版权信息 | ||||
| - 添加统计代码 | ||||
| - 添加自定义脚本 | ||||
| - 添加自定义链接 | ||||
| 
 | ||||
| ## 扩展点定义 | ||||
| 
 | ||||
| 扩展 `<halo:footer/>` 标签的扩展点定义为 `TemplateFooterProcessor`,对应的 `ExtensionPoint` 类型为 `MULTI_INSTANCE`,即可以有多个实现类。 | ||||
| 
 | ||||
| ```java | ||||
| public interface TemplateFooterProcessor extends ExtensionPoint { | ||||
| 
 | ||||
|     Mono<Void> process(ITemplateContext context, IProcessableElementTag tag, | ||||
|         IElementTagStructureHandler structureHandler, IModel model); | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| `TemplateFooterProcessor` 对应的 `ExtensionPointDefinition` 资源描述如下: | ||||
| 
 | ||||
| ```yaml | ||||
| apiVersion: plugin.halo.run/v1alpha1 | ||||
| kind: ExtensionPointDefinition | ||||
| metadata: | ||||
|   name: template-footer-processor | ||||
| spec: | ||||
|   className: run.halo.app.theme.dialect.TemplateFooterProcessor | ||||
|   displayName: 页脚标签内容处理器 | ||||
|   type: MULTI_INSTANCE | ||||
|   description: "提供用于扩展 <halo:footer/> 标签内容的扩展方式。" | ||||
| ``` | ||||
| 
 | ||||
| 即声明 `ExtensionDefinition` 自定义模型对象时对应的 `extensionPointName` 为 `template-footer-processor`。 | ||||
| 
 | ||||
| ## 示例实现 | ||||
| 
 | ||||
| 以下是一个简单的 TemplateFooterProcessor 插件实现示例: | ||||
| 
 | ||||
| ```java | ||||
| @Component | ||||
| public class FakeFooterCodeInjection implements TemplateFooterProcessor { | ||||
| 
 | ||||
|   @Override | ||||
|   public Mono<Void> process(ITemplateContext context, IProcessableElementTag tag, | ||||
|     IElementTagStructureHandler structureHandler, IModel model) { | ||||
|     var factory = context.getModelFactory(); | ||||
|     // regular footer text | ||||
|     var copyRight = factory.createText("<div>© 2024 Halo</div>"); | ||||
|     model.add(copyRight); | ||||
|     return Mono.empty(); | ||||
|   } | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| 声明 ExtensionDefinition 自定义模型对象时对应的 extensionPointName 为 template-footer-processor。 | ||||
| 
 | ||||
| ```yaml | ||||
| apiVersion: plugin.halo.run/v1alpha1 | ||||
| kind: ExtensionDefinition | ||||
| metadata: | ||||
|   name: custom-footer-extension | ||||
| spec: | ||||
|   extensionPointName: template-footer-processor | ||||
|   className: com.example.FakeFooterCodeInjection | ||||
|   displayName: "Custom Footer Extension" | ||||
|   description: "Adds custom footer content." | ||||
|   icon: 'some-icon' | ||||
| ``` | ||||
| @ -1,30 +0,0 @@ | ||||
| --- | ||||
| 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) | ||||
| @ -1,60 +0,0 @@ | ||||
| --- | ||||
| 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)。 | ||||
| @ -1,36 +0,0 @@ | ||||
| --- | ||||
| title: 静态资源代理 | ||||
| description: 了解如何使用静态资源代理来访问插件中的静态资源 | ||||
| --- | ||||
| 
 | ||||
| 插件中的静态资源如图片等如果想被外部访问到,需要放到 `src/main/resources` 目录下,并通过创建 `ReverseProxy` 自定义模型对象来进行静态资源代理访问。 | ||||
| 
 | ||||
| 例如 `src/main/resources` 下的 `static` 目录下有一张 `halo.jpg`: | ||||
| 
 | ||||
| 1. 首先需要在 `src/main/resources/extensions` 下创建一个 `yaml`,文件名可以任意。 | ||||
| 2. 声明 `ReverseProxy` 对象如下: | ||||
| 
 | ||||
|   ```yaml | ||||
|   apiVersion: plugin.halo.run/v1alpha1 | ||||
|   kind: ReverseProxy | ||||
|   metadata: | ||||
|     # 为了避免与其他插件冲突,推荐带上插件名称前缀 | ||||
|     name: my-plugin-fake-reverse-proxy | ||||
|   rules: | ||||
|     - path: /res/** | ||||
|       file: | ||||
|         directory: static | ||||
|         # 如果想代理 static 下所有静态资源则省略 filename 配置 | ||||
|         filename: halo.jpg | ||||
|   ``` | ||||
| 
 | ||||
| 插件启动后会根据 `/plugins/{plugin-name}/assets/**` 规则生成访问路径, | ||||
| 因此该 `ReverseProxy` 的访问路径为: `/plugins/my-plugin/assets/res/halo.jpg`。 | ||||
| 
 | ||||
| - `rules` 下可以添加多组规则。 | ||||
| - `path` 为路径前缀。 | ||||
| - `file` 表示访问文件系统,目前暂时仅支持这一种。 | ||||
| - `directory` 表示要代理的目标文件目录,它相对于 `src/main/resources/` 目录。 | ||||
| - `filename` 表示要代理的目标文件名。 | ||||
| 
 | ||||
| `directory` 和 `filename` 都是可选的,但必须至少有一个被配置。 | ||||
| @ -1,139 +0,0 @@ | ||||
| --- | ||||
| title: 获取插件配置 | ||||
| description: 了解如何获取插件定义的设置表单对应的配置数据,以及如何在插件中使用配置数据。 | ||||
| --- | ||||
| 
 | ||||
| 插件的 `plugin.yaml` 中允许配置 `settingName` 和 `configMapName` 字段,用于定义插件的个性化设置。 | ||||
| 本文介绍如何获取插件定义的设置表单对应的配置数据,以及如何在插件中使用配置数据。 | ||||
| 
 | ||||
| ## 概述 | ||||
| 
 | ||||
| Halo 提供了两个 Bean 用于获取插件配置数据:`SettingFetcher` 和 `ReactiveSettingFetcher`,分别用于同步和异步获取配置数据。 | ||||
| 
 | ||||
| 以 `ReactiveSettingFetcher` 为例,提供了以下方法: | ||||
| 
 | ||||
| ```java | ||||
| public interface ReactiveSettingFetcher { | ||||
| 
 | ||||
|     <T> Mono<T> fetch(String group, Class<T> clazz); | ||||
| 
 | ||||
|     @NonNull | ||||
|     Mono<JsonNode> get(String group); | ||||
| 
 | ||||
|     @NonNull | ||||
|     Mono<Map<String, JsonNode>> getValues(); | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| - `fetch` 方法用于获取指定分组的配置数据,并将其转换为指定的 Java 类型。 | ||||
| - `get` 方法用于获取指定分组的配置数据,返回 `JsonNode` 类型。 | ||||
| - `getValues` 方法用于获取所有配置数据,返回 `Map<String, JsonNode>` 类型,其中键为分组名称,值为配置对象。 | ||||
| 
 | ||||
| `ReactiveSettingFetcher` 和 `SettingFetcher` 底层都对配置数据进行了缓存,以提高性能,并且在配置变更时会自动刷新缓存,所以直接调用这些方法即可获取最新的配置数据。 | ||||
| 
 | ||||
| ## 监听配置变更 | ||||
| 
 | ||||
| 当用户修改插件配置时,可以通过监听 `PluginConfigUpdatedEvent` 事件,执行相应的操作。`PluginConfigUpdatedEvent` 包含了配置变更前后的数据,使插件能够对变化做出响应。 | ||||
| 
 | ||||
| ```java | ||||
| public class PluginConfigUpdatedEvent extends ApplicationEvent { | ||||
|     private final Map<String, JsonNode> oldConfig; | ||||
|     private final Map<String, JsonNode> newConfig; | ||||
| 
 | ||||
|     // ... | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| ## 使用示例 | ||||
| 
 | ||||
| ### 定义设置表单 | ||||
| 
 | ||||
| 假设插件定义了一个名为 `setting-seo` 的设置表单,其中包含了 `blockSpiders`、`keywords` 和 `description` 三个字段: | ||||
| 
 | ||||
| ```yaml | ||||
| apiVersion: v1alpha1 | ||||
| kind: Setting | ||||
| metadata: | ||||
|   name: setting-seo | ||||
| spec: | ||||
|   forms: | ||||
|     - group: seo   | ||||
|       label: SEO 设置   | ||||
|       formSchema:   | ||||
|         - $formkit: checkbox   | ||||
|           name: blockSpiders   | ||||
|           label: "屏蔽搜索引擎"   | ||||
|           value: false   | ||||
|         - $formkit: textarea   | ||||
|           name: keywords   | ||||
|           label: "站点关键词"   | ||||
|         - $formkit: textarea   | ||||
|           name: description   | ||||
|           label: "站点描述" | ||||
| ``` | ||||
| 
 | ||||
| ### 配置 plugin.yaml | ||||
| 
 | ||||
| 在 `plugin.yaml` 中配置 `settingName` 和 `configMapName` 字段: | ||||
| 
 | ||||
| ```yaml | ||||
| apiVersion: plugin.halo.run/v1alpha1 | ||||
| kind: Plugin | ||||
| metadata: | ||||
|   name: fake-plugin | ||||
| spec: | ||||
|   displayName: "Fake Plugin" | ||||
|   # ... | ||||
|   configMapName: setting-seo-configmap | ||||
|   settingName: setting-seo | ||||
| ``` | ||||
| 
 | ||||
| ### 定义值类 | ||||
| 
 | ||||
| 为了方便使用,定义一个值类存储配置数据: | ||||
| 
 | ||||
| ```java | ||||
| public record SeoSetting(boolean blockSpiders, String keywords, String description) { | ||||
|   public static final String GROUP = "seo"; | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| ### 获取配置数据 | ||||
| 
 | ||||
| 通过依赖注入 `ReactiveSettingFetcher` 并使用 `fetch(group, type)` 方法查询配置: | ||||
| 
 | ||||
| ```java | ||||
| @Service | ||||
| @RequiredArgsConstructor | ||||
| public class SeoService { | ||||
|     private final ReactiveSettingFetcher settingFetcher; | ||||
| 
 | ||||
|     public Mono<Void> checkSeo() { | ||||
|         return settingFetcher.fetch(SeoSetting.GROUP, SeoSetting.class) | ||||
|                 .doOnNext(seoSetting -> { | ||||
|                     if (seoSetting.blockSpiders()) { | ||||
|                         // do something | ||||
|                     } | ||||
|                 }) | ||||
|                 .then(); | ||||
|     } | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| ### 监听配置变更 | ||||
| 
 | ||||
| 通过监听 `PluginConfigUpdatedEvent` 事件来处理配置变更: | ||||
| 
 | ||||
| ```java | ||||
| @Component | ||||
| public class SeoConfigListener { | ||||
|     @EventListener | ||||
|     public void onConfigUpdated(PluginConfigUpdatedEvent event) { | ||||
|         if (event.getNewConfig().containsKey(SeoSetting.GROUP)) { | ||||
|             // do something | ||||
|         } | ||||
|     } | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| 通过以上示例,可以看到如何使用 `ReactiveSettingFetcher` 获取配置数据,并通过监听 `PluginConfigUpdatedEvent` 来处理配置变更事件,确保系统能及时响应配置的变化。 | ||||
| @ -1,55 +0,0 @@ | ||||
| --- | ||||
| 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    | 可选,包含键值对的对象或空值,用于存储数据。 | | ||||
| @ -1,25 +0,0 @@ | ||||
| --- | ||||
| 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。             | | ||||
| @ -1,50 +0,0 @@ | ||||
| --- | ||||
| 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[] 类型,表示附件数组。 | 当选择确定按钮时触发。 | | ||||
| @ -1,18 +0,0 @@ | ||||
| --- | ||||
| title: FilterCleanButton | ||||
| description: 过滤器清除按钮组件 | ||||
| --- | ||||
| 
 | ||||
| ## 使用示例 | ||||
| 
 | ||||
| ```html | ||||
| <script lang="ts" setup> | ||||
| function onClear () { | ||||
|   console.log("clear") | ||||
| } | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <FilterCleanButton @click="onClear" /> | ||||
| </template> | ||||
| ``` | ||||
| @ -1,48 +0,0 @@ | ||||
| --- | ||||
| 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 | 当模型值更新时触发。 | | ||||
| @ -1,26 +0,0 @@ | ||||
| --- | ||||
| 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[] | 无,必填 | 定义组件所需的权限列表。 | | ||||
| @ -1,42 +0,0 @@ | ||||
| --- | ||||
| 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](https://halo-ui-components.pages.dev) | ||||
| 
 | ||||
| ## 业务组件和指令 | ||||
| 
 | ||||
| 除了基础组件库,我们还为 Halo 的前端封装了一些业务组件和指令,这些组件已经在全局注册,你可以直接在插件中使用这些组件和指令。 | ||||
| 
 | ||||
| 以下是所有可用的业务组件和指令: | ||||
| 
 | ||||
| ```mdx-code-block | ||||
| import DocCardList from '@theme/DocCardList'; | ||||
| 
 | ||||
| <DocCardList /> | ||||
| ``` | ||||
| @ -1,30 +0,0 @@ | ||||
| --- | ||||
| title: PluginDetailModal | ||||
| description: 插件详情弹窗组件 | ||||
| --- | ||||
| 
 | ||||
| 此组件可以在 UI 部分的任意组件中打开差价的详情和设置弹窗,可以用于实现在不打断正常操作流程的情况下让用户查看和修改插件的详细信息。 | ||||
| 
 | ||||
| ## 使用方式 | ||||
| 
 | ||||
| ```html | ||||
| <script lang="ts" setup> | ||||
| import { ref } from "vue" | ||||
| 
 | ||||
| const modalVisible = ref(false) | ||||
| 
 | ||||
| function onPluginDetailModalClose() { | ||||
|   // Do something | ||||
| } | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <PluginDetailModal v-if="modalVisible" @close="onPluginDetailModalClose" name="starter" /> | ||||
| </template> | ||||
| ``` | ||||
| 
 | ||||
| ## Props | ||||
| 
 | ||||
| | 属性名 | 类型   | 默认值   | 描述                                          | | ||||
| | ------ | ------ | -------- | --------------------------------------------- | | ||||
| | `name` | string | 无,必填 | 插件名称,即 plugin.yaml 中的 `metadata.name` | | ||||
| @ -1,33 +0,0 @@ | ||||
| --- | ||||
| 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 类型,表示模型值。 | 当模型值更新时触发。 | | ||||
| @ -1,36 +0,0 @@ | ||||
| --- | ||||
| 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 类型,表示变更的值。 | 当值发生变化时触发。 | | ||||
| @ -1,18 +0,0 @@ | ||||
| --- | ||||
| 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> | ||||
| ``` | ||||
| @ -1,18 +0,0 @@ | ||||
| --- | ||||
| title: v-tooltip | ||||
| description: Tooltip 指令 | ||||
| --- | ||||
| 
 | ||||
| 此指令用于在任何元素上添加一个提示框。 | ||||
| 
 | ||||
| ## 使用方式 | ||||
| 
 | ||||
| ```html | ||||
| <script lang="ts" setup> | ||||
| import { IconDeleteBin } from "@halo-dev/components" | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <IconDeleteBin v-tooltip="'删除此文档'" /> | ||||
| </template> | ||||
| ``` | ||||
| @ -1,98 +0,0 @@ | ||||
| --- | ||||
| title: 附件数据列表操作菜单 | ||||
| description: 扩展附件数据列表操作菜单 - attachment:list-item:operation:create | ||||
| --- | ||||
| 
 | ||||
| 此扩展点用于扩展附件数据列表的操作菜单项。 | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
| ## 定义方式 | ||||
| 
 | ||||
| ```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](https://github.com/halo-dev/plugin-s3) | ||||
| 
 | ||||
| ## 类型定义 | ||||
| 
 | ||||
| ### Attachment | ||||
| 
 | ||||
| ```mdx-code-block | ||||
| import Attachment from "./interface/Attachment.md"; | ||||
| 
 | ||||
| <Attachment /> | ||||
| ``` | ||||
| @ -1,146 +0,0 @@ | ||||
| --- | ||||
| title: 附件选择选项卡 | ||||
| description: 扩展附件选择组件的选项卡 - attachment:selector:create | ||||
| --- | ||||
| 
 | ||||
| 此扩展点用于扩展附件选择组件的选项卡,目前 Halo 仅包含内置的附件库,你可以通过此扩展点添加自定义的选项卡。 | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
| ## 定义方式 | ||||
| 
 | ||||
| ```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](https://github.com/halo-sigs/plugin-unsplash) | ||||
| @ -1,41 +0,0 @@ | ||||
| --- | ||||
| title: 备份数据列表操作菜单 | ||||
| description: 扩展备份数据列表操作菜单 - backup:list-item:operation:create | ||||
| --- | ||||
| 
 | ||||
| 此扩展点用于扩展备份数据列表的操作菜单项。 | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
| ## 定义方式 | ||||
| 
 | ||||
| ```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 /> | ||||
| ``` | ||||
| @ -1,36 +0,0 @@ | ||||
| --- | ||||
| title: 备份页面选项卡 | ||||
| description: 扩展备份页面选项卡 - backup:tabs:create | ||||
| --- | ||||
| 
 | ||||
| 此扩展点可以针对备份页面扩展更多关于 UI 的功能,比如定时备份设置、备份到第三方云存储等。 | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
| ## 定义方式 | ||||
| 
 | ||||
| ```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[];     // 选项卡权限 | ||||
| } | ||||
| ``` | ||||
| @ -1,81 +0,0 @@ | ||||
| --- | ||||
| title: 评论数据列表操作菜单 | ||||
| description: 扩展评论数据列表操作菜单 - comment:list-item:operation:create | ||||
| --- | ||||
| 
 | ||||
| 此扩展点用于扩展评论数据列表的操作菜单项。 | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
| ## 定义方式 | ||||
| 
 | ||||
| ```ts | ||||
| export default definePlugin({ | ||||
|   extensionPoints: { | ||||
|     "comment:list-item:operation:create": ( | ||||
|       comment: Ref<ListedComment> | ||||
|     ): OperationItem<ListedComment>[] | Promise<OperationItem<ListedComment>[]> => { | ||||
|       return [ | ||||
|         { | ||||
|           priority: 10, | ||||
|           component: markRaw(VDropdownItem), | ||||
|           props: {}, | ||||
|           action: (item?: ListedComment) => { | ||||
|             // do something | ||||
|           }, | ||||
|           label: "foo", | ||||
|           hidden: false, | ||||
|           permissions: [], | ||||
|           children: [], | ||||
|         }, | ||||
|       ]; | ||||
|     }, | ||||
|   }, | ||||
| }); | ||||
| ``` | ||||
| 
 | ||||
| ```mdx-code-block | ||||
| import OperationItem from "./interface/OperationItem.md"; | ||||
| 
 | ||||
| <OperationItem /> | ||||
| ``` | ||||
| 
 | ||||
| ## 示例 | ||||
| 
 | ||||
| 此示例将实现一个操作菜单项。 | ||||
| 
 | ||||
| ```ts | ||||
| import type { ListedComment } from "@halo-dev/api-client"; | ||||
| import { VDropdownItem } from "@halo-dev/components"; | ||||
| import { definePlugin } from "@halo-dev/console-shared"; | ||||
| import { markRaw } from "vue"; | ||||
| 
 | ||||
| export default definePlugin({ | ||||
|   extensionPoints: { | ||||
|     "comment:list-item:operation:create": () => { | ||||
|       return [ | ||||
|         { | ||||
|           priority: 21, | ||||
|           component: markRaw(VDropdownItem), | ||||
|           label: "测试评论菜单", | ||||
|           visible: true, | ||||
|           permissions: [], | ||||
|           action: async (comment: ListedComment) => { | ||||
|             console.log(comment) | ||||
|           }, | ||||
|         }, | ||||
|       ]; | ||||
|     }, | ||||
|   }, | ||||
| }); | ||||
| ``` | ||||
| 
 | ||||
| ## 类型定义 | ||||
| 
 | ||||
| ### ListedComment | ||||
| 
 | ||||
| ```mdx-code-block | ||||
| import ListedComment from "./interface/ListedComment.md"; | ||||
| 
 | ||||
| <ListedComment /> | ||||
| ``` | ||||
| @ -1,114 +0,0 @@ | ||||
| --- | ||||
| title: 评论来源显示 | ||||
| description: 扩展评论来源显示 - comment:subject-ref:create | ||||
| --- | ||||
| 
 | ||||
| Console 的评论管理列表的评论来源默认仅支持显示来自文章和页面的评论,如果其他插件中的业务模块也使用了评论,那么就可以通过该拓展点来扩展评论来源的显示。 | ||||
| 
 | ||||
| :::info 提示 | ||||
| 此扩展点需要后端配合使用,请参考 [评论主体展示](../../server/extension-points/comment-subject.md)。 | ||||
| ::: | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
| ## 定义方式 | ||||
| 
 | ||||
| ```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](https://github.com/halo-sigs/plugin-moments) | ||||
| @ -1,14 +0,0 @@ | ||||
| --- | ||||
| title: 扩展点 | ||||
| description: Halo UI 为插件提供的扩展点接口 | ||||
| --- | ||||
| 
 | ||||
| UI 扩展点是用于扩展 Console 和 UC 的界面的接口,通过实现扩展点接口,插件可以在 Console 和 UC 中扩展功能。 | ||||
| 
 | ||||
| 以下是目前已支持的扩展点列表: | ||||
| 
 | ||||
| ```mdx-code-block | ||||
| import DocCardList from '@theme/DocCardList'; | ||||
| 
 | ||||
| <DocCardList /> | ||||
| ``` | ||||
| @ -1,25 +0,0 @@ | ||||
| ```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                           // 附件固定访问地址 | ||||
|   } | ||||
| } | ||||
| ``` | ||||
| @ -1,64 +0,0 @@ | ||||
| ```ts | ||||
| export interface ListedComment { | ||||
|   comment:{ | ||||
|     apiVersion: "content.halo.run/v1alpha1" | ||||
|     kind: "Comment" | ||||
|     metadata: { | ||||
|       annotations: {} | ||||
|       creationTimestamp: string                    | ||||
|       labels: {} | ||||
|       name: string                             // 评论的唯一标识 | ||||
|       version: number | ||||
|     } | ||||
|     spec: { | ||||
|       allowNotification: boolean;              // 是否允许通知 | ||||
|       approved: boolean; | ||||
|       approvedTime: string; | ||||
|       content: string;                         // 最终渲染的文本 | ||||
|       creationTime: string;                    // 创建时间 | ||||
|       hidden: boolean; | ||||
|       ipAddress: string;                       // 评论者 IP 地址 | ||||
|       lastReadTime: string; | ||||
|       owner: {                                 // 创建者信息 | ||||
|         annotations: {}; | ||||
|         displayName: string; | ||||
|         kind: string; | ||||
|         name: string; | ||||
|       }; | ||||
|       priority: number;                        // 排序字段 | ||||
|       raw: string;                             // 原始文本,一般用于给编辑器使用 | ||||
|       subjectRef: {                            // 引用关联,比如文章、自定义页面 | ||||
|         group: string; | ||||
|         kind: string; | ||||
|         name: string; | ||||
|         version: string; | ||||
|       }; | ||||
|       top: boolean;                            // 是否置顶 | ||||
|       userAgent: string;                       // 评论者 UserAgent 信息 | ||||
|     } | ||||
|     status: { | ||||
|       hasNewReply: boolean;                    // 是否有新回复 | ||||
|       lastReplyTime: string; | ||||
|       observedVersion: number; | ||||
|       replyCount: number;                      // 回复数量 | ||||
|       unreadReplyCount: number; | ||||
|       visibleReplyCount: number; | ||||
|     } | ||||
|   } | ||||
|   owner: {                                     // 创建者信息 | ||||
|     avatar: string;                            // 头像 | ||||
|     displayName: string;                       // 描述 | ||||
|     email: string;                             // 邮箱 | ||||
|     kind: string; | ||||
|     name: string;                              // 创建者的唯一标识 | ||||
|   }  | ||||
|   stats: { | ||||
|     upvote: number; | ||||
|   } | ||||
|   subject: { | ||||
|     apiVersion: string; | ||||
|     kind: string; | ||||
|     metadata: Metadata; | ||||
|   }  | ||||
| } | ||||
| ``` | ||||
| @ -1,119 +0,0 @@ | ||||
| ```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;                   // 标签下可见的文章总数 | ||||
|     }; | ||||
|   }>; | ||||
| } | ||||
| 
 | ||||
| ``` | ||||
| @ -1,49 +0,0 @@ | ||||
| ```ts | ||||
| export interface ListedReply { | ||||
|   owner: {                                     // 创建者信息 | ||||
|     avatar: string;                            // 头像 | ||||
|     displayName: string;                       // 描述 | ||||
|     email: string;                             // 邮箱 | ||||
|     kind: string; | ||||
|     name: string;                              // 创建者的唯一标识 | ||||
|   }  | ||||
|   reply:{ | ||||
|     apiVersion: "content.halo.run/v1alpha1" | ||||
|     kind: "Reply" | ||||
|     metadata: { | ||||
|       annotations: {} | ||||
|       creationTimestamp: string                    | ||||
|       labels: {} | ||||
|       name: string                             // 评论的唯一标识 | ||||
|       version: number | ||||
|     } | ||||
|     spec: { | ||||
|       allowNotification: boolean;              // 是否允许通知 | ||||
|       approved: boolean; | ||||
|       approvedTime: string; | ||||
|       commentName: string;                     // 被回复的评论名称,即 Comment 的 metadata.name | ||||
|       content: string;                         // 最终渲染的文本 | ||||
|       creationTime: string;                    // 创建时间 | ||||
|       hidden: boolean; | ||||
|       ipAddress: string;                       // 评论者 IP 地址 | ||||
|       owner: {                                 // 创建者信息 | ||||
|         annotations: {}; | ||||
|         displayName: string; | ||||
|         kind: string; | ||||
|         name: string; | ||||
|       }; | ||||
|       priority: number;                        // 排序字段 | ||||
|       quoteReply: string;                      // 被回复的回复名称,即 Reply 的 metadata.name | ||||
|       raw: string;                             // 原始文本,一般用于给编辑器使用 | ||||
|       top: boolean;                            // 是否置顶 | ||||
|       userAgent: string;                       // 评论者 UserAgent 信息 | ||||
|     } | ||||
|     status: { | ||||
|       observedVersion: number; | ||||
|     } | ||||
|   } | ||||
|   stats: { | ||||
|     upvote: number; | ||||
|   } | ||||
| } | ||||
| ``` | ||||
| @ -1,12 +0,0 @@ | ||||
| ```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>[];    // 子菜单项 | ||||
| } | ||||
| ``` | ||||
| @ -1,50 +0,0 @@ | ||||
| ```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 | ||||
|   } | ||||
| } | ||||
| ``` | ||||
| @ -1,63 +0,0 @@ | ||||
| ```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 | ||||
|   } | ||||
| } | ||||
| ``` | ||||
| @ -1,44 +0,0 @@ | ||||
| --- | ||||
| title: 插件安装界面选项卡 | ||||
| description: 扩展插件安装界面选项卡 - plugin:installation:tabs:create | ||||
| --- | ||||
| 
 | ||||
| 目前 Halo 原生支持本地上传和远程下载的方式安装插件,此扩展点用于扩展插件安装界面的选项卡,以支持更多的安装方式。 | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
| ## 定义方式 | ||||
| 
 | ||||
| ```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](https://github.com/halo-dev/plugin-app-store) | ||||
| @ -1,55 +0,0 @@ | ||||
| --- | ||||
| title: 插件数据列表操作菜单 | ||||
| description: 扩展插件数据列表操作菜单 - plugin:list-item:operation:create | ||||
| --- | ||||
| 
 | ||||
| 此扩展点用于扩展插件数据列表的操作菜单项。 | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
| ## 定义方式 | ||||
| 
 | ||||
| ```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](https://github.com/halo-dev/plugin-app-store) | ||||
| 
 | ||||
| ## 类型定义 | ||||
| 
 | ||||
| ### Plugin | ||||
| 
 | ||||
| ```mdx-code-block | ||||
| import Plugin from "./interface/Plugin.md"; | ||||
| 
 | ||||
| <Plugin /> | ||||
| ``` | ||||
| @ -1,92 +0,0 @@ | ||||
| --- | ||||
| title: 文章数据列表操作菜单 | ||||
| description: 扩展文章数据列表操作菜单 - post:list-item:operation:create | ||||
| --- | ||||
| 
 | ||||
| 此扩展点用于扩展文章数据列表的操作菜单项。 | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
| ## 定义方式 | ||||
| 
 | ||||
| ```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 /> | ||||
| ``` | ||||
| @ -1,81 +0,0 @@ | ||||
| --- | ||||
| title: 回复数据列表操作菜单 | ||||
| description: 扩展回复数据列表操作菜单 - reply:list-item:operation:create | ||||
| --- | ||||
| 
 | ||||
| 此扩展点用于扩展回复数据列表的操作菜单项。 | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
| ## 定义方式 | ||||
| 
 | ||||
| ```ts | ||||
| export default definePlugin({ | ||||
|   extensionPoints: { | ||||
|     "reply:list-item:operation:create": ( | ||||
|       reply: Ref<ListedReply> | ||||
|     ): OperationItem<ListedReply>[] | Promise<OperationItem<ListedReply>[]> => { | ||||
|       return [ | ||||
|         { | ||||
|           priority: 10, | ||||
|           component: markRaw(VDropdownItem), | ||||
|           props: {}, | ||||
|           action: (item?: ListedReply) => { | ||||
|             // do something | ||||
|           }, | ||||
|           label: "foo", | ||||
|           hidden: false, | ||||
|           permissions: [], | ||||
|           children: [], | ||||
|         }, | ||||
|       ]; | ||||
|     }, | ||||
|   }, | ||||
| }); | ||||
| ``` | ||||
| 
 | ||||
| ```mdx-code-block | ||||
| import OperationItem from "./interface/OperationItem.md"; | ||||
| 
 | ||||
| <OperationItem /> | ||||
| ``` | ||||
| 
 | ||||
| ## 示例 | ||||
| 
 | ||||
| 此示例将实现一个操作菜单项。 | ||||
| 
 | ||||
| ```ts | ||||
| import type { ListedReply } from "@halo-dev/api-client"; | ||||
| import { VDropdownItem } from "@halo-dev/components"; | ||||
| import { definePlugin } from "@halo-dev/console-shared"; | ||||
| import { markRaw } from "vue"; | ||||
| 
 | ||||
| export default definePlugin({ | ||||
|   extensionPoints: { | ||||
|     "reply:list-item:operation:create": () => { | ||||
|       return [ | ||||
|         { | ||||
|           priority: 21, | ||||
|           component: markRaw(VDropdownItem), | ||||
|           label: "测试回复菜单", | ||||
|           visible: true, | ||||
|           permissions: [], | ||||
|           action: async (reply: ListedReply) => { | ||||
|             console.log(reply) | ||||
|           }, | ||||
|         }, | ||||
|       ]; | ||||
|     }, | ||||
|   }, | ||||
| }); | ||||
| ``` | ||||
| 
 | ||||
| ## 类型定义 | ||||
| 
 | ||||
| ### ListedReply | ||||
| 
 | ||||
| ```mdx-code-block | ||||
| import ListedReply from "./interface/ListedReply.md"; | ||||
| 
 | ||||
| <ListedReply /> | ||||
| ``` | ||||
| @ -1,91 +0,0 @@ | ||||
| --- | ||||
| title: 主题数据列表操作菜单 | ||||
| description: 扩展主题数据列表操作菜单 - theme:list-item:operation:create | ||||
| --- | ||||
| 
 | ||||
| 此扩展点用于扩展主题数据列表的操作菜单项。 | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
| ## 定义方式 | ||||
| 
 | ||||
| ```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](https://github.com/halo-dev/plugin-app-store) | ||||
| 
 | ||||
| ## 类型定义 | ||||
| 
 | ||||
| ### Theme | ||||
| 
 | ||||
| ```mdx-code-block | ||||
| import Theme from "./interface/Theme.md"; | ||||
| 
 | ||||
| <Theme /> | ||||
| ``` | ||||
| @ -1,44 +0,0 @@ | ||||
| --- | ||||
| title: 主题管理界面选项卡 | ||||
| description: 扩展主题管理界面选项卡 - theme:list:tabs:create | ||||
| --- | ||||
| 
 | ||||
| 目前在 Halo 的主题管理中原生支持本地上传和远程下载的方式安装主题,此扩展点用于扩展主题管理界面的选项卡,以支持更多的安装方式。 | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
| ## 定义方式 | ||||
| 
 | ||||
| ```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](https://github.com/halo-dev/plugin-app-store) | ||||
| @ -1,4 +0,0 @@ | ||||
| --- | ||||
| title: 附录 | ||||
| description: 附录 | ||||
| --- | ||||
| @ -1,12 +0,0 @@ | ||||
| --- | ||||
| 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) | ||||
Some files were not shown because too many files have changed in this diff Show More
					Loading…
					
					
				
		Reference in new issue