From 09cd1f7f745923e16dcc32ebbec5aea19761975a Mon Sep 17 00:00:00 2001 From: Ryan Wang Date: Tue, 12 Aug 2025 14:26:47 +0800 Subject: [PATCH] Comments now support rich text formatting display (#7674) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #### What type of PR is this? /area ui /kind feature /milestone 2.21.x #### What this PR does / why we need it: Comments now support rich text format display. Still need to: 1. Test for XSS vulnerabilities 2. Optimize content styling 3. Editor #### Which issue(s) this PR fixes: Fixes https://github.com/halo-dev/halo/issues/7671 #### Special notes for your reviewer: #### Does this PR introduce a user-facing change? ```release-note 评论内容支持以富文本格式显示 ``` --- .../components/CommentDetailModal.vue | 37 ++++++---- .../comments/components/CommentEditor.vue | 65 ++++++++++++++++ .../comments/components/CommentListItem.vue | 11 ++- .../components/DefaultCommentContent.vue | 22 ++++++ ...FormItems.vue => DefaultCommentEditor.vue} | 12 +-- .../components/ReplyCreationModal.vue | 36 ++++----- .../comments/components/ReplyDetailModal.vue | 69 ++++++++++------- .../comments/components/ReplyListItem.vue | 30 +++++--- .../use-content-provider-extension-point.ts | 38 ++++++++++ .../widgets/presets/comments/CommentItem.vue | 11 ++- ui/docs/extension-points/comment-content.md | 29 ++++++++ ui/docs/extension-points/comment-editor.md | 32 ++++++++ ui/package.json | 2 + ui/packages/shared/src/index.ts | 1 + ui/packages/shared/src/states/comment.ts | 9 +++ ui/packages/shared/src/types/plugin.ts | 12 +++ ui/pnpm-lock.yaml | 74 ++++++++++++++++++- ui/src/vite/config-builder.ts | 1 - 18 files changed, 403 insertions(+), 88 deletions(-) create mode 100644 ui/console-src/modules/contents/comments/components/CommentEditor.vue create mode 100644 ui/console-src/modules/contents/comments/components/DefaultCommentContent.vue rename ui/console-src/modules/contents/comments/components/{ReplyFormItems.vue => DefaultCommentEditor.vue} (88%) create mode 100644 ui/console-src/modules/contents/comments/composables/use-content-provider-extension-point.ts create mode 100644 ui/docs/extension-points/comment-content.md create mode 100644 ui/docs/extension-points/comment-editor.md create mode 100644 ui/packages/shared/src/states/comment.ts diff --git a/ui/console-src/modules/contents/comments/components/CommentDetailModal.vue b/ui/console-src/modules/contents/comments/components/CommentDetailModal.vue index ad5743fef..db3cae160 100644 --- a/ui/console-src/modules/contents/comments/components/CommentDetailModal.vue +++ b/ui/console-src/modules/contents/comments/components/CommentDetailModal.vue @@ -19,9 +19,10 @@ import { useQueryClient } from "@tanstack/vue-query"; import { useUserAgent } from "@uc/modules/profile/tabs/composables/use-user-agent"; import { computed, ref, useTemplateRef } from "vue"; import { useI18n } from "vue-i18n"; +import { useContentProviderExtensionPoint } from "../composables/use-content-provider-extension-point"; import { useSubjectRef } from "../composables/use-subject-ref"; +import CommentEditor from "./CommentEditor.vue"; import OwnerButton from "./OwnerButton.vue"; -import ReplyFormItems from "./ReplyFormItems.vue"; const props = withDefaults( defineProps<{ @@ -48,10 +49,19 @@ const creationTime = computed(() => { ); }); -const newReply = ref(""); +const editorContent = ref(""); +const editorCharacterCount = ref(0); + +function onCommentEditorUpdate(value: { + content: string; + characterCount: number; +}) { + editorContent.value = value.content; + editorCharacterCount.value = value.characterCount; +} async function handleApprove() { - if (!newReply.value) { + if (!editorCharacterCount.value) { await coreApiClient.content.comment.patchComment({ name: props.comment.comment.metadata.name, jsonPatchInner: [ @@ -71,8 +81,8 @@ async function handleApprove() { await consoleApiClient.content.comment.createReply({ name: props.comment?.comment.metadata.name as string, replyRequest: { - raw: newReply.value, - content: newReply.value, + raw: editorContent.value, + content: editorContent.value, allowNotification: true, quoteReply: undefined, }, @@ -88,6 +98,8 @@ const { subjectRefResult } = useSubjectRef(props.comment); const websiteOfAnonymous = computed(() => { return props.comment.comment.spec.owner.annotations?.["website"]; }); + +const { data: contentProvider } = useContentProviderExtensionPoint();