From 2e429e5198c4b128a91ebc4dd9a361dd37d06f38 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Thu, 5 Mar 2026 19:37:36 +0530 Subject: [PATCH] [WIKI-874] refactor: description input component (#8544) * refactor: description input component * fix: add missing prop to rich text editor --- .../rich-text/description-input/root.tsx | 159 +++++++++--------- .../components/inbox/content/issue-root.tsx | 2 +- .../issues/issue-detail/main-content.tsx | 2 +- .../issues/peek-overview/issue-detail.tsx | 2 +- 4 files changed, 87 insertions(+), 78 deletions(-) diff --git a/apps/web/core/components/editor/rich-text/description-input/root.tsx b/apps/web/core/components/editor/rich-text/description-input/root.tsx index 42b5b1224..3e4a12604 100644 --- a/apps/web/core/components/editor/rich-text/description-input/root.tsx +++ b/apps/web/core/components/editor/rich-text/description-input/root.tsx @@ -28,6 +28,7 @@ const workspaceService = new WorkspaceService(); type TFormData = { id: string; description_html: string; + description_json?: object; isMigrationUpdate: boolean; }; @@ -67,7 +68,13 @@ type Props = { /** * @description Submit handler, the actual function which will be called when the form is submitted */ - onSubmit: (value: string, isMigrationUpdate?: boolean) => Promise; + onSubmit: ( + value: { + description_html: string; + description_json: object | undefined; + }, + isMigrationUpdate?: boolean + ) => Promise; /** * @description Placeholder, if not provided, the placeholder will be the default placeholder */ @@ -107,13 +114,13 @@ export const DescriptionInput = observer(function DescriptionInput(props: Props) entityId, fileAssetType, initialValue, + issueSequenceId, onSubmit, placeholder, projectId, setIsSubmitting, swrDescription, workspaceSlug, - issueSequenceId, } = props; // states const [localDescription, setLocalDescription] = useState({ @@ -144,7 +151,13 @@ export const DescriptionInput = observer(function DescriptionInput(props: Props) // submit handler const handleDescriptionFormSubmit = useCallback( async (formData: TFormData) => { - await onSubmit(formData.description_html, formData.isMigrationUpdate); + await onSubmit( + { + description_html: formData.description_html, + description_json: formData.description_json, + }, + formData.isMigrationUpdate + ); // Update lastSavedContent after successful save lastSavedContent.current = formData.description_html; }, @@ -209,80 +222,76 @@ export const DescriptionInput = observer(function DescriptionInput(props: Props) if (!workspaceDetails) return null; + if (!localDescription.description_html) return ; + return ( - <> - {localDescription.description_html ? ( - ( -

"} - value={swrDescription ?? null} - workspaceSlug={workspaceSlug} - workspaceId={workspaceDetails.id} - projectId={projectId} - dragDropEnabled - onChange={(_description, description_html, options) => { - // Skip if content hasn't actually changed (handles editor normalization on init) - if (description_html === lastSavedContent.current) return; - setIsSubmitting("submitting"); - onChange(description_html); - setValue("isMigrationUpdate", options?.isMigrationUpdate ?? false); - hasUnsavedChanges.current = true; - debouncedFormSave(); - }} - placeholder={placeholder ?? ((isFocused, value) => t(getDescriptionPlaceholderI18n(isFocused, value)))} - searchMentionCallback={async (payload) => - await workspaceService.searchEntity(workspaceSlug?.toString() ?? "", { - ...payload, - project_id: projectId, - }) - } - containerClassName={containerClassName} - uploadFile={async (blockId, file) => { - try { - const { asset_id } = await uploadEditorAsset({ - blockId, - data: { - entity_identifier: entityId, - entity_type: fileAssetType, - }, - file, - projectId, - workspaceSlug, - }); - return asset_id; - } catch (error) { - console.log("Error in uploading asset:", error); - throw new Error("Asset upload failed. Please try again later."); - } - }} - duplicateFile={async (assetId: string) => { - try { - const { asset_id } = await duplicateEditorAsset({ - assetId, - entityType: fileAssetType, - projectId, - workspaceSlug, - }); - return asset_id; - } catch { - throw new Error("Asset duplication failed. Please try again later."); - } - }} - /> - )} + ( +

"} + value={swrDescription ?? null} + workspaceSlug={workspaceSlug} + workspaceId={workspaceDetails.id} + projectId={projectId} + dragDropEnabled + onChange={(description_json, description_html, options) => { + if (description_html === lastSavedContent.current) return; + setIsSubmitting("submitting"); + onChange(description_html); + setValue("isMigrationUpdate", !!options?.isMigrationUpdate); + setValue("description_json", description_json); + hasUnsavedChanges.current = true; + debouncedFormSave(); + }} + placeholder={placeholder ?? ((isFocused, value) => t(getDescriptionPlaceholderI18n(isFocused, value)))} + searchMentionCallback={async (payload) => + await workspaceService.searchEntity(workspaceSlug?.toString() ?? "", { + ...payload, + project_id: projectId, + }) + } + containerClassName={containerClassName} + uploadFile={async (blockId, file) => { + try { + const { asset_id } = await uploadEditorAsset({ + blockId, + data: { + entity_identifier: entityId, + entity_type: fileAssetType, + }, + file, + projectId, + workspaceSlug, + }); + return asset_id; + } catch (error) { + console.log("Error in uploading asset:", error); + throw new Error("Asset upload failed. Please try again later."); + } + }} + duplicateFile={async (assetId: string) => { + try { + const { asset_id } = await duplicateEditorAsset({ + assetId, + entityType: fileAssetType, + projectId, + workspaceSlug, + }); + return asset_id; + } catch { + throw new Error("Asset duplication failed. Please try again later."); + } + }} /> - ) : ( - )} - + /> ); }); diff --git a/apps/web/core/components/inbox/content/issue-root.tsx b/apps/web/core/components/inbox/content/issue-root.tsx index 702333154..9538779a5 100644 --- a/apps/web/core/components/inbox/content/issue-root.tsx +++ b/apps/web/core/components/inbox/content/issue-root.tsx @@ -180,7 +180,7 @@ export const InboxIssueMainContent = observer(function InboxIssueMainContent(pro onSubmit={async (value, isMigrationUpdate) => { if (!issue.id || !issue.project_id) return; await issueOperations.update(workspaceSlug, issue.project_id, issue.id, { - description_html: value, + description_html: value.description_html, ...(isMigrationUpdate ? { skip_activity: "true" } : {}), }); }} diff --git a/apps/web/core/components/issues/issue-detail/main-content.tsx b/apps/web/core/components/issues/issue-detail/main-content.tsx index 871b515ea..f482e240c 100644 --- a/apps/web/core/components/issues/issue-detail/main-content.tsx +++ b/apps/web/core/components/issues/issue-detail/main-content.tsx @@ -144,7 +144,7 @@ export const IssueMainContent = observer(function IssueMainContent(props: Props) onSubmit={async (value, isMigrationUpdate) => { if (!issue.id || !issue.project_id) return; await issueOperations.update(workspaceSlug, issue.project_id, issue.id, { - description_html: value, + description_html: value.description_html, ...(isMigrationUpdate ? { skip_activity: "true" } : {}), }); }} diff --git a/apps/web/core/components/issues/peek-overview/issue-detail.tsx b/apps/web/core/components/issues/peek-overview/issue-detail.tsx index 83e37c9d1..bac7ea28b 100644 --- a/apps/web/core/components/issues/peek-overview/issue-detail.tsx +++ b/apps/web/core/components/issues/peek-overview/issue-detail.tsx @@ -144,7 +144,7 @@ export const PeekOverviewIssueDetails = observer(function PeekOverviewIssueDetai onSubmit={async (value, isMigrationUpdate) => { if (!issue.id || !issue.project_id) return; await issueOperations.update(workspaceSlug, issue.project_id, issue.id, { - description_html: value, + description_html: value.description_html, ...(isMigrationUpdate ? { skip_activity: "true" } : {}), }); }}