mirror of https://github.com/halo-dev/halo
				
				
				
			feat: make comment subject ref provider extensible (#4039)
#### What type of PR is this? /area console /kind feature #### What this PR does / why we need it: 让评论来源的显示支持通过插件扩展,目前如 [瞬间](https://github.com/halo-sigs/plugin-moments) 这类的插件如果使用了评论模块,那么在评论管理是无法显示具体来源的: <img width="627" alt="image" src="https://github.com/halo-dev/halo/assets/21301288/0df354dc-ed42-4217-abbd-5bce67329e0d"> 此 PR 为 Console 端提供了拓展方法,使用方式如下: ```ts import { definePlugin } from "@halo-dev/console-shared"; import type { CommentSubjectRefResult } from "@halo-dev/console-shared"; import type { Extension } from "@halo-dev/api-client"; import type { Moment } from "./types"; export default definePlugin({ components: {}, 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", }, }; }, }, ]; }, }, }); ``` #### Which issue(s) this PR fixes: Fixes #3554 #### Special notes for your reviewer: #### Does this PR introduce a user-facing change? ```release-note Console 端的评论来源显示支持通过插件扩展 ```pull/4114/head^2
							parent
							
								
									ac47942a04
								
							
						
					
					
						commit
						8c05a6d30e
					
				| 
						 | 
				
			
			@ -0,0 +1,59 @@
 | 
			
		|||
# 评论来源显示拓展点
 | 
			
		||||
 | 
			
		||||
在 Console 中,评论管理列表的评论来源默认仅支持显示来自文章和页面的评论,如果其他插件中的业务模块也使用了评论,那么就可以通过该拓展点来扩展评论来源的显示。
 | 
			
		||||
 | 
			
		||||
## 定义方式
 | 
			
		||||
 | 
			
		||||
假设以文章为例:
 | 
			
		||||
 | 
			
		||||
```ts
 | 
			
		||||
import { definePlugin } from "@halo-dev/console-shared";
 | 
			
		||||
import type { CommentSubjectRefResult } from "@halo-dev/console-shared";
 | 
			
		||||
import type { Extension } from "@halo-dev/api-client";
 | 
			
		||||
import type { Post } from "./types";
 | 
			
		||||
 | 
			
		||||
export default definePlugin({
 | 
			
		||||
  components: {},
 | 
			
		||||
  extensionPoints: {
 | 
			
		||||
    "comment:subject-ref:create": () => {
 | 
			
		||||
      return [
 | 
			
		||||
        {
 | 
			
		||||
          kind: "Post",
 | 
			
		||||
          group: "post.halo.run",
 | 
			
		||||
          resolve: (subject: Extension): CommentSubjectRefResult => {
 | 
			
		||||
            const post = subject as Post;
 | 
			
		||||
            return {
 | 
			
		||||
              label: "文章",
 | 
			
		||||
              title: post.spec.title,
 | 
			
		||||
              externalUrl: post.status.permalink,
 | 
			
		||||
              route: {
 | 
			
		||||
                name: "PostEditor",
 | 
			
		||||
                params: {
 | 
			
		||||
                  name: post.metadata.name
 | 
			
		||||
                }
 | 
			
		||||
              },
 | 
			
		||||
            };
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
      ];
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
类型定义如下:
 | 
			
		||||
 | 
			
		||||
```ts
 | 
			
		||||
type CommentSubjectRefProvider = {
 | 
			
		||||
  kind: string;               // 自定义模型的类型
 | 
			
		||||
  group: string;              // 自定义模型的分组
 | 
			
		||||
  resolve: (subject: Extension) => CommentSubjectRefResult;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface CommentSubjectRefResult {
 | 
			
		||||
  label: string;              // 来源名称(类型)
 | 
			
		||||
  title: string;              // 来源标题
 | 
			
		||||
  route?: RouteLocationRaw;   // Console 的路由,可以设置为来源的详情或者编辑页面
 | 
			
		||||
  externalUrl?: string;       // 访问地址,可以设置为前台资源的访问地址
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
| 
						 | 
				
			
			@ -4,3 +4,4 @@ export * from "./core/plugins";
 | 
			
		|||
export * from "./states/pages";
 | 
			
		||||
export * from "./states/attachment-selector";
 | 
			
		||||
export * from "./states/editor";
 | 
			
		||||
export * from "./states/comment-subject-ref";
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,15 @@
 | 
			
		|||
import type { Extension } from "@halo-dev/api-client";
 | 
			
		||||
import type { RouteLocationRaw } from "vue-router";
 | 
			
		||||
 | 
			
		||||
export interface CommentSubjectRefResult {
 | 
			
		||||
  label: string;
 | 
			
		||||
  title: string;
 | 
			
		||||
  route?: RouteLocationRaw;
 | 
			
		||||
  externalUrl?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type CommentSubjectRefProvider = {
 | 
			
		||||
  kind: string;
 | 
			
		||||
  group: string;
 | 
			
		||||
  resolve: (subject: Extension) => CommentSubjectRefResult;
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -3,6 +3,7 @@ import type { RouteRecordRaw, RouteRecordName } from "vue-router";
 | 
			
		|||
import type { FunctionalPage } from "../states/pages";
 | 
			
		||||
import type { AttachmentSelectProvider } from "../states/attachment-selector";
 | 
			
		||||
import type { EditorProvider } from "..";
 | 
			
		||||
import type { CommentSubjectRefProvider } from "@/states/comment-subject-ref";
 | 
			
		||||
 | 
			
		||||
export interface RouteRecordAppend {
 | 
			
		||||
  parentName: RouteRecordName;
 | 
			
		||||
| 
						 | 
				
			
			@ -18,6 +19,8 @@ export interface ExtensionPoint {
 | 
			
		|||
    | Promise<AttachmentSelectProvider[]>;
 | 
			
		||||
 | 
			
		||||
  "editor:create"?: () => EditorProvider[] | Promise<EditorProvider[]>;
 | 
			
		||||
 | 
			
		||||
  "comment:subject-ref:create"?: () => CommentSubjectRefProvider[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface PluginModule {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -24,14 +24,18 @@ import type {
 | 
			
		|||
  SinglePage,
 | 
			
		||||
} from "@halo-dev/api-client";
 | 
			
		||||
import { formatDatetime } from "@/utils/date";
 | 
			
		||||
import { computed, provide, ref, type Ref } from "vue";
 | 
			
		||||
import { computed, provide, ref, onMounted, type Ref } from "vue";
 | 
			
		||||
import ReplyListItem from "./ReplyListItem.vue";
 | 
			
		||||
import { apiClient } from "@/utils/api-client";
 | 
			
		||||
import type { RouteLocationRaw } from "vue-router";
 | 
			
		||||
import cloneDeep from "lodash.clonedeep";
 | 
			
		||||
import { usePermission } from "@/utils/permission";
 | 
			
		||||
import { useQuery } from "@tanstack/vue-query";
 | 
			
		||||
import { useI18n } from "vue-i18n";
 | 
			
		||||
import { usePluginModuleStore, type PluginModule } from "@/stores/plugin";
 | 
			
		||||
import type {
 | 
			
		||||
  CommentSubjectRefProvider,
 | 
			
		||||
  CommentSubjectRefResult,
 | 
			
		||||
} from "packages/shared/dist";
 | 
			
		||||
 | 
			
		||||
const { currentUserHasPermission } = usePermission();
 | 
			
		||||
const { t } = useI18n();
 | 
			
		||||
| 
						 | 
				
			
			@ -195,18 +199,11 @@ const onReplyCreationModalClose = () => {
 | 
			
		|||
};
 | 
			
		||||
 | 
			
		||||
// Subject ref processing
 | 
			
		||||
interface SubjectRefResult {
 | 
			
		||||
  label: string;
 | 
			
		||||
  title: string;
 | 
			
		||||
  route?: RouteLocationRaw;
 | 
			
		||||
  externalUrl?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const SubjectRefProvider = ref<
 | 
			
		||||
  Record<string, (subject: Extension) => SubjectRefResult>[]
 | 
			
		||||
>([
 | 
			
		||||
const SubjectRefProviders = ref<CommentSubjectRefProvider[]>([
 | 
			
		||||
  {
 | 
			
		||||
    Post: (subject: Extension): SubjectRefResult => {
 | 
			
		||||
    kind: "Post",
 | 
			
		||||
    group: "content.halo.run",
 | 
			
		||||
    resolve: (subject: Extension): CommentSubjectRefResult => {
 | 
			
		||||
      const post = subject as Post;
 | 
			
		||||
      return {
 | 
			
		||||
        label: t("core.comment.subject_refs.post"),
 | 
			
		||||
| 
						 | 
				
			
			@ -222,7 +219,9 @@ const SubjectRefProvider = ref<
 | 
			
		|||
    },
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    SinglePage: (subject: Extension): SubjectRefResult => {
 | 
			
		||||
    kind: "SinglePage",
 | 
			
		||||
    group: "content.halo.run",
 | 
			
		||||
    resolve: (subject: Extension): CommentSubjectRefResult => {
 | 
			
		||||
      const singlePage = subject as SinglePage;
 | 
			
		||||
      return {
 | 
			
		||||
        label: t("core.comment.subject_refs.page"),
 | 
			
		||||
| 
						 | 
				
			
			@ -239,6 +238,27 @@ const SubjectRefProvider = ref<
 | 
			
		|||
  },
 | 
			
		||||
]);
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  const { pluginModules } = usePluginModuleStore();
 | 
			
		||||
 | 
			
		||||
  pluginModules.forEach((pluginModule: PluginModule) => {
 | 
			
		||||
    const { extensionPoints } = pluginModule;
 | 
			
		||||
    if (!extensionPoints?.["comment:subject-ref:create"]) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const providers = extensionPoints[
 | 
			
		||||
      "comment:subject-ref:create"
 | 
			
		||||
    ]() as CommentSubjectRefProvider[];
 | 
			
		||||
 | 
			
		||||
    if (providers) {
 | 
			
		||||
      providers.forEach((provider) => {
 | 
			
		||||
        SubjectRefProviders.value.push(provider);
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const subjectRefResult = computed(() => {
 | 
			
		||||
  const { subject } = props.comment;
 | 
			
		||||
  if (!subject) {
 | 
			
		||||
| 
						 | 
				
			
			@ -247,8 +267,10 @@ const subjectRefResult = computed(() => {
 | 
			
		|||
      title: t("core.comment.subject_refs.unknown"),
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
  const subjectRef = SubjectRefProvider.value.find((provider) =>
 | 
			
		||||
    Object.keys(provider).includes(subject.kind)
 | 
			
		||||
  const subjectRef = SubjectRefProviders.value.find(
 | 
			
		||||
    (provider) =>
 | 
			
		||||
      provider.kind === subject.kind &&
 | 
			
		||||
      subject.apiVersion.startsWith(provider.group)
 | 
			
		||||
  );
 | 
			
		||||
  if (!subjectRef) {
 | 
			
		||||
    return {
 | 
			
		||||
| 
						 | 
				
			
			@ -256,7 +278,7 @@ const subjectRefResult = computed(() => {
 | 
			
		|||
      title: t("core.comment.subject_refs.unknown"),
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
  return subjectRef[subject.kind](subject);
 | 
			
		||||
  return subjectRef.resolve(subject);
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue