[WIKI-874] refactor: description input component (#8544)

* refactor: description input component

* fix: add missing prop to rich text editor
This commit is contained in:
Aaryan Khandelwal 2026-03-05 19:37:36 +05:30 committed by GitHub
parent c3a9f99789
commit 2e429e5198
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 87 additions and 78 deletions

View file

@ -28,6 +28,7 @@ const workspaceService = new WorkspaceService();
type TFormData = { type TFormData = {
id: string; id: string;
description_html: string; description_html: string;
description_json?: object;
isMigrationUpdate: boolean; isMigrationUpdate: boolean;
}; };
@ -67,7 +68,13 @@ type Props = {
/** /**
* @description Submit handler, the actual function which will be called when the form is submitted * @description Submit handler, the actual function which will be called when the form is submitted
*/ */
onSubmit: (value: string, isMigrationUpdate?: boolean) => Promise<void>; onSubmit: (
value: {
description_html: string;
description_json: object | undefined;
},
isMigrationUpdate?: boolean
) => Promise<void>;
/** /**
* @description Placeholder, if not provided, the placeholder will be the default placeholder * @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, entityId,
fileAssetType, fileAssetType,
initialValue, initialValue,
issueSequenceId,
onSubmit, onSubmit,
placeholder, placeholder,
projectId, projectId,
setIsSubmitting, setIsSubmitting,
swrDescription, swrDescription,
workspaceSlug, workspaceSlug,
issueSequenceId,
} = props; } = props;
// states // states
const [localDescription, setLocalDescription] = useState<TFormData>({ const [localDescription, setLocalDescription] = useState<TFormData>({
@ -144,7 +151,13 @@ export const DescriptionInput = observer(function DescriptionInput(props: Props)
// submit handler // submit handler
const handleDescriptionFormSubmit = useCallback( const handleDescriptionFormSubmit = useCallback(
async (formData: TFormData) => { 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 // Update lastSavedContent after successful save
lastSavedContent.current = formData.description_html; lastSavedContent.current = formData.description_html;
}, },
@ -209,80 +222,76 @@ export const DescriptionInput = observer(function DescriptionInput(props: Props)
if (!workspaceDetails) return null; if (!workspaceDetails) return null;
if (!localDescription.description_html) return <DescriptionInputLoader />;
return ( return (
<> <Controller
{localDescription.description_html ? ( name="description_html"
<Controller control={control}
name="description_html" render={({ field: { onChange } }) => (
control={control} <RichTextEditor
render={({ field: { onChange } }) => ( key={entityId}
<RichTextEditor editable={!disabled}
key={entityId} ref={editorRef}
editable={!disabled} id={entityId}
ref={editorRef} issueSequenceId={issueSequenceId}
id={entityId} disabledExtensions={disabledExtensions}
issueSequenceId={issueSequenceId} initialValue={localDescription.description_html ?? "<p></p>"}
disabledExtensions={disabledExtensions} value={swrDescription ?? null}
initialValue={localDescription.description_html ?? "<p></p>"} workspaceSlug={workspaceSlug}
value={swrDescription ?? null} workspaceId={workspaceDetails.id}
workspaceSlug={workspaceSlug} projectId={projectId}
workspaceId={workspaceDetails.id} dragDropEnabled
projectId={projectId} onChange={(description_json, description_html, options) => {
dragDropEnabled if (description_html === lastSavedContent.current) return;
onChange={(_description, description_html, options) => { setIsSubmitting("submitting");
// Skip if content hasn't actually changed (handles editor normalization on init) onChange(description_html);
if (description_html === lastSavedContent.current) return; setValue("isMigrationUpdate", !!options?.isMigrationUpdate);
setIsSubmitting("submitting"); setValue("description_json", description_json);
onChange(description_html); hasUnsavedChanges.current = true;
setValue("isMigrationUpdate", options?.isMigrationUpdate ?? false); debouncedFormSave();
hasUnsavedChanges.current = true; }}
debouncedFormSave(); placeholder={placeholder ?? ((isFocused, value) => t(getDescriptionPlaceholderI18n(isFocused, value)))}
}} searchMentionCallback={async (payload) =>
placeholder={placeholder ?? ((isFocused, value) => t(getDescriptionPlaceholderI18n(isFocused, value)))} await workspaceService.searchEntity(workspaceSlug?.toString() ?? "", {
searchMentionCallback={async (payload) => ...payload,
await workspaceService.searchEntity(workspaceSlug?.toString() ?? "", { project_id: projectId,
...payload, })
project_id: projectId, }
}) containerClassName={containerClassName}
} uploadFile={async (blockId, file) => {
containerClassName={containerClassName} try {
uploadFile={async (blockId, file) => { const { asset_id } = await uploadEditorAsset({
try { blockId,
const { asset_id } = await uploadEditorAsset({ data: {
blockId, entity_identifier: entityId,
data: { entity_type: fileAssetType,
entity_identifier: entityId, },
entity_type: fileAssetType, file,
}, projectId,
file, workspaceSlug,
projectId, });
workspaceSlug, return asset_id;
}); } catch (error) {
return asset_id; console.log("Error in uploading asset:", error);
} catch (error) { throw new Error("Asset upload failed. Please try again later.");
console.log("Error in uploading asset:", error); }
throw new Error("Asset upload failed. Please try again later."); }}
} duplicateFile={async (assetId: string) => {
}} try {
duplicateFile={async (assetId: string) => { const { asset_id } = await duplicateEditorAsset({
try { assetId,
const { asset_id } = await duplicateEditorAsset({ entityType: fileAssetType,
assetId, projectId,
entityType: fileAssetType, workspaceSlug,
projectId, });
workspaceSlug, return asset_id;
}); } catch {
return asset_id; throw new Error("Asset duplication failed. Please try again later.");
} catch { }
throw new Error("Asset duplication failed. Please try again later."); }}
}
}}
/>
)}
/> />
) : (
<DescriptionInputLoader />
)} )}
</> />
); );
}); });

View file

@ -180,7 +180,7 @@ export const InboxIssueMainContent = observer(function InboxIssueMainContent(pro
onSubmit={async (value, isMigrationUpdate) => { onSubmit={async (value, isMigrationUpdate) => {
if (!issue.id || !issue.project_id) return; if (!issue.id || !issue.project_id) return;
await issueOperations.update(workspaceSlug, issue.project_id, issue.id, { await issueOperations.update(workspaceSlug, issue.project_id, issue.id, {
description_html: value, description_html: value.description_html,
...(isMigrationUpdate ? { skip_activity: "true" } : {}), ...(isMigrationUpdate ? { skip_activity: "true" } : {}),
}); });
}} }}

View file

@ -144,7 +144,7 @@ export const IssueMainContent = observer(function IssueMainContent(props: Props)
onSubmit={async (value, isMigrationUpdate) => { onSubmit={async (value, isMigrationUpdate) => {
if (!issue.id || !issue.project_id) return; if (!issue.id || !issue.project_id) return;
await issueOperations.update(workspaceSlug, issue.project_id, issue.id, { await issueOperations.update(workspaceSlug, issue.project_id, issue.id, {
description_html: value, description_html: value.description_html,
...(isMigrationUpdate ? { skip_activity: "true" } : {}), ...(isMigrationUpdate ? { skip_activity: "true" } : {}),
}); });
}} }}

View file

@ -144,7 +144,7 @@ export const PeekOverviewIssueDetails = observer(function PeekOverviewIssueDetai
onSubmit={async (value, isMigrationUpdate) => { onSubmit={async (value, isMigrationUpdate) => {
if (!issue.id || !issue.project_id) return; if (!issue.id || !issue.project_id) return;
await issueOperations.update(workspaceSlug, issue.project_id, issue.id, { await issueOperations.update(workspaceSlug, issue.project_id, issue.id, {
description_html: value, description_html: value.description_html,
...(isMigrationUpdate ? { skip_activity: "true" } : {}), ...(isMigrationUpdate ? { skip_activity: "true" } : {}),
}); });
}} }}