From 5cd44b742347a49d3caba1c869e75ba68b93e211 Mon Sep 17 00:00:00 2001 From: liuyangjuncong20202570 Date: Sun, 10 Aug 2025 20:48:22 +0800 Subject: [PATCH 1/2] feat:add feature of modifying content --- .../web/components/Issues/IssueDetailPage.tsx | 11 +- .../web/components/Issues/utils/store.tsx | 10 ++ .../components/MrView/CommentDropdownMenu.tsx | 30 +++- moon/apps/web/components/MrView/MRComment.tsx | 156 ++++++++++++++++-- moon/apps/web/hooks/issues/usePostComment.ts | 16 ++ 5 files changed, 200 insertions(+), 23 deletions(-) create mode 100644 moon/apps/web/hooks/issues/usePostComment.ts diff --git a/moon/apps/web/components/Issues/IssueDetailPage.tsx b/moon/apps/web/components/Issues/IssueDetailPage.tsx index e2d236b8f..1e5b5e442 100644 --- a/moon/apps/web/components/Issues/IssueDetailPage.tsx +++ b/moon/apps/web/components/Issues/IssueDetailPage.tsx @@ -41,7 +41,7 @@ import { useLabelsSelector, useMemberMap } from './utils/sideEffect' -import { idAtom } from './utils/store' +import { idAtom, useRefresh } from './utils/store' export default function IssueDetailPage({ link }: { link: string }) { const [id] = useAtom(idAtom) @@ -107,6 +107,15 @@ export default function IssueDetailPage({ link }: { link: string }) { fetchDetail() }, [fetchDetail, link]) + const refresh = useRefresh() + + useEffect(() => { + fetchDetail() + console.log(refresh) + // return () => setRefresh(false) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [refresh.current]) + const [_loadings, setLoadings] = useState([]) const router = useRouter() diff --git a/moon/apps/web/components/Issues/utils/store.tsx b/moon/apps/web/components/Issues/utils/store.tsx index de3121010..c3e77288f 100644 --- a/moon/apps/web/components/Issues/utils/store.tsx +++ b/moon/apps/web/components/Issues/utils/store.tsx @@ -1,3 +1,4 @@ +import { useRef } from 'react' import { CookieValueTypes } from 'cookies-next' import { atom } from 'jotai' import { atomFamily } from 'jotai/utils' @@ -42,3 +43,12 @@ export const labelsCloseCurrentPage = atomWithWebStorage('LabelsCloseCurrentPage export const idAtom = atom(0) export const mridAtom = atom(0) + +export const FALSE_EDIT_VAL = -1 +export const editIdAtom = atom(0) + +export const useRefresh = () => { + const refresh = useRef(0) + + return refresh +} diff --git a/moon/apps/web/components/MrView/CommentDropdownMenu.tsx b/moon/apps/web/components/MrView/CommentDropdownMenu.tsx index 35bd5469a..292038dff 100644 --- a/moon/apps/web/components/MrView/CommentDropdownMenu.tsx +++ b/moon/apps/web/components/MrView/CommentDropdownMenu.tsx @@ -1,10 +1,24 @@ -import { Button, CopyIcon, DotsHorizontal, PreferenceIcon, PencilIcon, QuoteIcon, TrashIcon, EyeHideIcon } from '@gitmono/ui' +import { useAtom } from 'jotai' + +import { ConversationItem } from '@gitmono/types/generated' +import { + Button, + CopyIcon, + DotsHorizontal, + EyeHideIcon, + PencilIcon, + PreferenceIcon, + QuoteIcon, + TrashIcon +} from '@gitmono/ui' import { DropdownMenu } from '@gitmono/ui/DropdownMenu' import { buildMenuItems } from '@gitmono/ui/Menu' -import { ConversationItem } from '@gitmono/types/generated'; + import { useDeleteIssueComment } from '@/hooks/issues/useDeleteIssueComment' import { useDeleteMrCommentDelete } from '@/hooks/useDeleteMrCommentDelete' +import { editIdAtom } from '../Issues/utils/store' + interface CommentDropdownMenuProps { id: string Conversation: ConversationItem @@ -14,6 +28,7 @@ interface CommentDropdownMenuProps { export function CommentDropdownMenu({ Conversation, id, CommentType }: CommentDropdownMenuProps) { const { mutate: deleteComment } = useDeleteMrCommentDelete(id) const { mutate: deleteIssueComment } = useDeleteIssueComment(id) + const [_editId, setEditId] = useAtom(editIdAtom) const handleDelete = () => { switch (CommentType) { @@ -32,28 +47,29 @@ export function CommentDropdownMenu({ Conversation, id, CommentType }: CommentDr { type: 'item', label: 'Copy', - leftSlot: , + leftSlot: }, { type: 'item', label: 'Quote', - leftSlot: , + leftSlot: }, { type: 'item', label: 'Reference', - leftSlot: , + leftSlot: }, { type: 'separator' }, { type: 'item', label: 'Edit', leftSlot: , + onSelect: () => setEditId(Conversation.id) }, { type: 'item', label: 'Hide', - leftSlot: , + leftSlot: }, { type: 'item', @@ -64,8 +80,6 @@ export function CommentDropdownMenu({ Conversation, id, CommentType }: CommentDr } ]) - - return ( <> { const { data: member } = useGetOrganizationMember({ username: conv.username }) - + const extensions = useMemo(() => getNoteExtensions({ linkUnfurl: {} }), []) const handleReactionSelect = useHandleExpression({ conv, id, type: whoamI }) + const [editId, setEditId] = useAtom(editIdAtom) + const editInputRef = useRef<{ handleUpdate: () => void }>() + + const refresh = useRefresh() + + useEffect(() => { + // fetchDetail() + console.log(refresh.current) + // return () => setRefresh(false) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [refresh.current]) return (
-
+
{ )} > - { member ? : "Avatar not found"} + {member ? : 'Avatar not found'}
@@ -58,7 +84,7 @@ const Comment = ({ conv, id, whoamI }: CommentProps) => {
- +
@@ -75,16 +101,118 @@ const Comment = ({ conv, id, whoamI }: CommentProps) => { } onReactionSelect={handleReactionSelect} /> - +
- { conv.comment && } + {conv.comment && editId !== conv.id && } + {editId === conv.id && ( + <> + +
+ + +
+ + )}
) } +const EditInput = forwardRef(({ comment }: { comment: ConversationItem }, ref) => { + const editorRef = useRef(null) + const onKeyDownScrollHandler = useHandleBottomScrollOffset({ + editor: editorRef.current?.editor + }) + const { dropzone } = useUploadHelpers({ + upload: editorRef.current?.uploadAndAppendAttachments + }) + const { handleChange } = useChange({}) + const [isReactionPickerOpen, setIsReactionPickerOpen] = useState(false) + const { mutate: updateComment } = usePostComment() + const [_, setEditId] = useAtom(editIdAtom) + const refresh = useRefresh() + + const handleUpdate = () => { + const currentContentHTML = editorRef.current?.editor?.getHTML() ?? '

' + + if (trimHtml(currentContentHTML) === '') { + toast.error('comment can not be empty!') + return + } + + updateComment( + { commentId: comment.id, data: { content: currentContentHTML } }, + { + onError: (err) => apiErrorToast(new Error(err.message)), + onSuccess: () => { + console.log('success') + setTimeout(() => { + toast.success('update successfully!') + refresh.current = Date.now() + }, 0) + }, + onSettled: () => { + console.log('settled') + setTimeout(() => { + refresh.current = Date.now() + console.log(refresh.current) + setEditId(FALSE_EDIT_VAL) + }, 0) + } + } + ) + } + + useImperativeHandle(ref, () => ({ + handleUpdate + })) + + return ( + <> +
+ +
+ handleChange(html)} + /> +
+
+ + ) +}) + +EditInput.displayName = 'EditInput' + export default Comment diff --git a/moon/apps/web/hooks/issues/usePostComment.ts b/moon/apps/web/hooks/issues/usePostComment.ts new file mode 100644 index 000000000..916b2c48a --- /dev/null +++ b/moon/apps/web/hooks/issues/usePostComment.ts @@ -0,0 +1,16 @@ +import { useMutation } from '@tanstack/react-query' + +import { ContentPayload, PostApiConversationByCommentIdData, RequestParams } from '@gitmono/types/generated' + +import { legacyApiClient } from '@/utils/queryClient' + +export function usePostComment() { + return useMutation< + PostApiConversationByCommentIdData, + Error, + { commentId: number; data: ContentPayload; params?: RequestParams } + >({ + mutationFn: ({ commentId, data, params }) => + legacyApiClient.v1.postApiConversationByCommentId().request(commentId, data, params) + }) +} From 1f1479d68db199bf2e6473e5133b64bf20028f82 Mon Sep 17 00:00:00 2001 From: liuyangjuncong Date: Mon, 11 Aug 2025 14:55:54 +0800 Subject: [PATCH 2/2] feat:add feature of modefying conversation & proxy of sse service --- .../web/components/Issues/IssueDetailPage.tsx | 24 ++++--- .../web/components/Issues/utils/store.tsx | 7 +- moon/apps/web/components/MrView/MRComment.tsx | 63 ++++++----------- .../MrView/components/Checks/index.tsx | 7 +- moon/apps/web/next.config.js | 7 +- moon/apps/web/pages/[org]/mr/[link]/index.tsx | 17 ++++- moon/apps/web/tsconfig.json | 70 ++++++++++++++----- 7 files changed, 118 insertions(+), 77 deletions(-) diff --git a/moon/apps/web/components/Issues/IssueDetailPage.tsx b/moon/apps/web/components/Issues/IssueDetailPage.tsx index 1e5b5e442..f8f1bdf69 100644 --- a/moon/apps/web/components/Issues/IssueDetailPage.tsx +++ b/moon/apps/web/components/Issues/IssueDetailPage.tsx @@ -41,7 +41,7 @@ import { useLabelsSelector, useMemberMap } from './utils/sideEffect' -import { idAtom, useRefresh } from './utils/store' +import { editIdAtom, FALSE_EDIT_VAL, idAtom, refreshAtom } from './utils/store' export default function IssueDetailPage({ link }: { link: string }) { const [id] = useAtom(idAtom) @@ -107,14 +107,18 @@ export default function IssueDetailPage({ link }: { link: string }) { fetchDetail() }, [fetchDetail, link]) - const refresh = useRefresh() - + const [refresh, setRefresh] = useAtom(refreshAtom) + const [_, setEditId] = useAtom(editIdAtom) + useEffect(() => { - fetchDetail() - console.log(refresh) - // return () => setRefresh(false) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [refresh.current]) + const load = async () => { + await refetch() + setEditId(FALSE_EDIT_VAL) + setRefresh(0) + } + + load() + }, [refresh, refetch, setEditId, setRefresh]) const [_loadings, setLoadings] = useState([]) const router = useRouter() @@ -315,12 +319,14 @@ export default function IssueDetailPage({ link }: { link: string }) {
{info?.title && (
-
+
{!isEdit && ( <>
{`${issueDetail?.title || ''}`} +   + ${issueDetail?.id} +