Use rich text editor in form field (#16474)

Fix https://github.com/twentyhq/twenty/issues/15948

Before
<img width="982" height="505" alt="Capture d’écran 2025-12-10 à 17 26
17"
src="https://github.com/user-attachments/assets/661a8205-cb1f-4b4b-8f0a-813c09ac9d16"
/>

After
<img width="982" height="767" alt="Capture d’écran 2025-12-10 à 17 25
49"
src="https://github.com/user-attachments/assets/58fa83a8-890e-42cb-a0bd-c5f6d7890d37"
/>
This commit is contained in:
Thomas Trompette
2025-12-11 08:43:29 +01:00
committed by GitHub
parent 1077cf1560
commit 7021cd41ea
3 changed files with 46 additions and 18 deletions
@@ -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 (
<FormTextFieldInput
<FormAdvancedTextFieldInput
label={label}
error={error}
hint={hint}
defaultValue={defaultValue?.markdown ?? ''}
defaultValue={defaultValue?.blocknote ?? defaultValue?.markdown}
placeholder={placeholder}
onChange={handleChange}
onBlur={onBlur}
multiline
readonly={readonly}
VariablePicker={VariablePicker}
minHeight={RICH_TEXT_V2_EDITOR_MIN_HEIGHT}
maxWidth={RICH_TEXT_V2_EDITOR_MAX_WIDTH}
/>
);
};
@@ -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,
});
},
};
@@ -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);