From 7021cd41eaf8105820a736a3ca5f34a159738e22 Mon Sep 17 00:00:00 2001 From: Thomas Trompette Date: Thu, 11 Dec 2025 08:43:29 +0100 Subject: [PATCH] Use rich text editor in form field (#16474) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix https://github.com/twentyhq/twenty/issues/15948 Before Capture d’écran 2025-12-10 à 17 26
17 After Capture d’écran 2025-12-10 à 17 25
49 --- .../components/FormRichTextV2FieldInput.tsx | 31 ++++++++++++++----- .../FormRichTextV2FieldInput.stories.tsx | 24 ++++++++------ .../getInitialAdvancedTextEditorContent.ts | 9 ++++++ 3 files changed, 46 insertions(+), 18 deletions(-) diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/form-types/components/FormRichTextV2FieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/ui/form-types/components/FormRichTextV2FieldInput.tsx index fa70f63d80..ff9601dc6a 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/form-types/components/FormRichTextV2FieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/form-types/components/FormRichTextV2FieldInput.tsx @@ -1,4 +1,4 @@ -import { FormTextFieldInput } from '@/object-record/record-field/ui/form-types/components/FormTextFieldInput'; +import { FormAdvancedTextFieldInput } from '@/object-record/record-field/ui/form-types/components/FormAdvancedTextFieldInput'; import { type VariablePickerComponent } from '@/object-record/record-field/ui/form-types/types/VariablePickerComponent'; import { type FieldRichTextV2Value } from '@/object-record/record-field/ui/types/FieldMetadata'; @@ -14,6 +14,22 @@ type FormRichTextV2FieldInputProps = { VariablePicker?: VariablePickerComponent; }; +const RICH_TEXT_V2_EDITOR_MIN_HEIGHT = 340; + +const RICH_TEXT_V2_EDITOR_MAX_WIDTH = 600; + +const mapTipTapToBlockNote = (tiptapJson: string): string => { + try { + const json = JSON.parse(tiptapJson); + if (json.type === 'doc' && Array.isArray(json.content)) { + return JSON.stringify(json.content); + } + return tiptapJson; + } catch { + return tiptapJson; + } +}; + export const FormRichTextV2FieldInput = ({ label, error, @@ -21,29 +37,28 @@ export const FormRichTextV2FieldInput = ({ defaultValue, placeholder, onChange, - onBlur, readonly, VariablePicker, }: FormRichTextV2FieldInputProps) => { const handleChange = (value: string) => { onChange({ - blocknote: null, - markdown: value, + blocknote: mapTipTapToBlockNote(value), + markdown: null, }); }; return ( - ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/form-types/components/__stories__/FormRichTextV2FieldInput.stories.tsx b/packages/twenty-front/src/modules/object-record/record-field/ui/form-types/components/__stories__/FormRichTextV2FieldInput.stories.tsx index 1dce5e37ff..f84efb4751 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/form-types/components/__stories__/FormRichTextV2FieldInput.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/form-types/components/__stories__/FormRichTextV2FieldInput.stories.tsx @@ -89,8 +89,9 @@ export const WithVariable: Story = { await waitFor(() => { expect(args.onChange).toHaveBeenCalledWith({ - blocknote: null, - markdown: `## Title \nVariable: {{${MOCKED_STEP_ID}.name}}`, + blocknote: + '[{"type":"paragraph","content":[{"type":"text","text":"## Title"},{"type":"hardBreak"},{"type":"text","text":"Variable: "},{"type":"variableTag","attrs":{"variable":"{{04d5f3bf-9714-400d-ba27-644006a5fb1b.name}}"}}]}]', + markdown: null, }); }); expect(args.onChange).toHaveBeenCalledTimes(1); @@ -135,8 +136,9 @@ export const WithDeletableVariable: Story = { await waitFor(() => { expect(args.onChange).toHaveBeenCalledWith({ - blocknote: null, - markdown: 'test test', + blocknote: + '[{"type":"paragraph","content":[{"type":"text","text":"test test"}]}]', + markdown: null, }); }); expect(args.onChange).toHaveBeenCalledTimes(1); @@ -252,16 +254,17 @@ export const HasHistory: Story = { await userEvent.click(addVariableButton); expect(args.onChange).toHaveBeenLastCalledWith({ - blocknote: null, - markdown: `Hello World {{${MOCKED_STEP_ID}.name}}`, + blocknote: + '[{"type":"paragraph","content":[{"type":"text","text":"Hello World "},{"type":"variableTag","attrs":{"variable":"{{04d5f3bf-9714-400d-ba27-644006a5fb1b.name}}"}}]}]', + markdown: null, }); await userEvent.type(editor, `{${controlKey}>}z{/${controlKey}}`); expect(editor).toHaveTextContent(''); expect(args.onChange).toHaveBeenLastCalledWith({ - blocknote: null, - markdown: '', + blocknote: '[{"type":"paragraph"}]', + markdown: null, }); await userEvent.type( @@ -271,8 +274,9 @@ export const HasHistory: Story = { expect(editor).toHaveTextContent(`Hello World Name`); expect(args.onChange).toHaveBeenLastCalledWith({ - blocknote: null, - markdown: `Hello World {{${MOCKED_STEP_ID}.name}}`, + blocknote: + '[{"type":"paragraph","content":[{"type":"text","text":"Hello World "},{"type":"variableTag","attrs":{"variable":"{{04d5f3bf-9714-400d-ba27-644006a5fb1b.name}}"}}]}]', + markdown: null, }); }, }; diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/utils/getInitialAdvancedTextEditorContent.ts b/packages/twenty-front/src/modules/workflow/workflow-variables/utils/getInitialAdvancedTextEditorContent.ts index 4f99d628fb..dda5907e48 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-variables/utils/getInitialAdvancedTextEditorContent.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-variables/utils/getInitialAdvancedTextEditorContent.ts @@ -23,6 +23,15 @@ export const getInitialAdvancedTextEditorContent = ( try { const json = JSON.parse(rawContent); + + // Handle BlockNote array format (wrap in doc structure for TipTap) + if (Array.isArray(json)) { + return { + type: 'doc', + content: json, + }; + } + return json; } catch (error) { logError(error);