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/pages";
|
||||||
export * from "./states/attachment-selector";
|
export * from "./states/attachment-selector";
|
||||||
export * from "./states/editor";
|
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 { FunctionalPage } from "../states/pages";
|
||||||
import type { AttachmentSelectProvider } from "../states/attachment-selector";
|
import type { AttachmentSelectProvider } from "../states/attachment-selector";
|
||||||
import type { EditorProvider } from "..";
|
import type { EditorProvider } from "..";
|
||||||
|
import type { CommentSubjectRefProvider } from "@/states/comment-subject-ref";
|
||||||
|
|
||||||
export interface RouteRecordAppend {
|
export interface RouteRecordAppend {
|
||||||
parentName: RouteRecordName;
|
parentName: RouteRecordName;
|
||||||
|
@ -18,6 +19,8 @@ export interface ExtensionPoint {
|
||||||
| Promise<AttachmentSelectProvider[]>;
|
| Promise<AttachmentSelectProvider[]>;
|
||||||
|
|
||||||
"editor:create"?: () => EditorProvider[] | Promise<EditorProvider[]>;
|
"editor:create"?: () => EditorProvider[] | Promise<EditorProvider[]>;
|
||||||
|
|
||||||
|
"comment:subject-ref:create"?: () => CommentSubjectRefProvider[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PluginModule {
|
export interface PluginModule {
|
||||||
|
|
|
@ -24,14 +24,18 @@ import type {
|
||||||
SinglePage,
|
SinglePage,
|
||||||
} from "@halo-dev/api-client";
|
} from "@halo-dev/api-client";
|
||||||
import { formatDatetime } from "@/utils/date";
|
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 ReplyListItem from "./ReplyListItem.vue";
|
||||||
import { apiClient } from "@/utils/api-client";
|
import { apiClient } from "@/utils/api-client";
|
||||||
import type { RouteLocationRaw } from "vue-router";
|
|
||||||
import cloneDeep from "lodash.clonedeep";
|
import cloneDeep from "lodash.clonedeep";
|
||||||
import { usePermission } from "@/utils/permission";
|
import { usePermission } from "@/utils/permission";
|
||||||
import { useQuery } from "@tanstack/vue-query";
|
import { useQuery } from "@tanstack/vue-query";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { usePluginModuleStore, type PluginModule } from "@/stores/plugin";
|
||||||
|
import type {
|
||||||
|
CommentSubjectRefProvider,
|
||||||
|
CommentSubjectRefResult,
|
||||||
|
} from "packages/shared/dist";
|
||||||
|
|
||||||
const { currentUserHasPermission } = usePermission();
|
const { currentUserHasPermission } = usePermission();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
@ -195,18 +199,11 @@ const onReplyCreationModalClose = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Subject ref processing
|
// Subject ref processing
|
||||||
interface SubjectRefResult {
|
const SubjectRefProviders = ref<CommentSubjectRefProvider[]>([
|
||||||
label: string;
|
|
||||||
title: string;
|
|
||||||
route?: RouteLocationRaw;
|
|
||||||
externalUrl?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const SubjectRefProvider = ref<
|
|
||||||
Record<string, (subject: Extension) => SubjectRefResult>[]
|
|
||||||
>([
|
|
||||||
{
|
{
|
||||||
Post: (subject: Extension): SubjectRefResult => {
|
kind: "Post",
|
||||||
|
group: "content.halo.run",
|
||||||
|
resolve: (subject: Extension): CommentSubjectRefResult => {
|
||||||
const post = subject as Post;
|
const post = subject as Post;
|
||||||
return {
|
return {
|
||||||
label: t("core.comment.subject_refs.post"),
|
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;
|
const singlePage = subject as SinglePage;
|
||||||
return {
|
return {
|
||||||
label: t("core.comment.subject_refs.page"),
|
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 subjectRefResult = computed(() => {
|
||||||
const { subject } = props.comment;
|
const { subject } = props.comment;
|
||||||
if (!subject) {
|
if (!subject) {
|
||||||
|
@ -247,8 +267,10 @@ const subjectRefResult = computed(() => {
|
||||||
title: t("core.comment.subject_refs.unknown"),
|
title: t("core.comment.subject_refs.unknown"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const subjectRef = SubjectRefProvider.value.find((provider) =>
|
const subjectRef = SubjectRefProviders.value.find(
|
||||||
Object.keys(provider).includes(subject.kind)
|
(provider) =>
|
||||||
|
provider.kind === subject.kind &&
|
||||||
|
subject.apiVersion.startsWith(provider.group)
|
||||||
);
|
);
|
||||||
if (!subjectRef) {
|
if (!subjectRef) {
|
||||||
return {
|
return {
|
||||||
|
@ -256,7 +278,7 @@ const subjectRefResult = computed(() => {
|
||||||
title: t("core.comment.subject_refs.unknown"),
|
title: t("core.comment.subject_refs.unknown"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return subjectRef[subject.kind](subject);
|
return subjectRef.resolve(subject);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue