[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 = {
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<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
*/
@ -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<TFormData>({
@ -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 <DescriptionInputLoader />;
return (
<>
{localDescription.description_html ? (
<Controller
name="description_html"
control={control}
render={({ field: { onChange } }) => (
<RichTextEditor
key={entityId}
editable={!disabled}
ref={editorRef}
id={entityId}
issueSequenceId={issueSequenceId}
disabledExtensions={disabledExtensions}
initialValue={localDescription.description_html ?? "<p></p>"}
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.");
}
}}
/>
)}
<Controller
name="description_html"
control={control}
render={({ field: { onChange } }) => (
<RichTextEditor
key={entityId}
editable={!disabled}
ref={editorRef}
id={entityId}
issueSequenceId={issueSequenceId}
disabledExtensions={disabledExtensions}
initialValue={localDescription.description_html ?? "<p></p>"}
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.");
}
}}
/>
) : (
<DescriptionInputLoader />
)}
</>
/>
);
});

View file

@ -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" } : {}),
});
}}

View file

@ -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" } : {}),
});
}}

View file

@ -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" } : {}),
});
}}