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:
+23
-8
@@ -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}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
+14
-10
@@ -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,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
+9
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user