From 52d7f58343ce46fe6fc50fc9afe0125ecb37e8f4 Mon Sep 17 00:00:00 2001 From: "AIMEUR M. Amin" <43800537+AimeurAmin@users.noreply.github.com> Date: Wed, 20 Nov 2024 09:41:47 +0000 Subject: [PATCH] (fix) Rtf editor markdown not working properly (#4445) * changes text editor to support markdown mode * usage of text editor markdown instead of remark component * fixes failing tests * fixes linting issues * fixes tests * removes duplicates in text editor nodes * handling the case of no description provided for markdown --- .pnp.cjs | 4 + .../__tests__/OutputDetailPage.test.tsx | 3 + packages/react-components/package.json | 1 + .../react-components/src/atoms/TextEditor.tsx | 123 ++++++++++++------ packages/react-components/src/form.ts | 13 +- .../SharedResearchDetailsTagsCard.tsx | 10 +- .../SharedResearchDetailsTagsCard.test.tsx | 5 + .../__tests__/SharedResearchOutput.test.tsx | 4 + yarn.lock | 1 + 9 files changed, 118 insertions(+), 46 deletions(-) diff --git a/.pnp.cjs b/.pnp.cjs index 5f8c1d263e..5c31737853 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -13326,6 +13326,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@emotion/jest", "virtual:d0e958afa23d83fcbe1e8341a17fed5245116729be9039e4a7f723d5645a3b4a0db7dc084c7f30fa2114faa4c170592f819708d736b1e487b4ae7cd58824e005#npm:11.13.0"],\ ["@emotion/react", "virtual:3a393e218825bde954376ca1a828a8b21ca2967b8d720dd56f28d8017fc081fa726c0b293069a94a55394c33a36ded19a9a4675c0d537b344c90f8add76eb926#npm:11.11.1"],\ ["@emotion/serialize", "npm:1.1.2"],\ + ["@lexical/code", "npm:0.18.0"],\ ["@lexical/link", "npm:0.18.0"],\ ["@lexical/list", "npm:0.18.0"],\ ["@lexical/markdown", "npm:0.18.0"],\ @@ -13423,6 +13424,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@emotion/jest", "virtual:d0e958afa23d83fcbe1e8341a17fed5245116729be9039e4a7f723d5645a3b4a0db7dc084c7f30fa2114faa4c170592f819708d736b1e487b4ae7cd58824e005#npm:11.13.0"],\ ["@emotion/react", "virtual:3a393e218825bde954376ca1a828a8b21ca2967b8d720dd56f28d8017fc081fa726c0b293069a94a55394c33a36ded19a9a4675c0d537b344c90f8add76eb926#npm:11.11.1"],\ ["@emotion/serialize", "npm:1.1.2"],\ + ["@lexical/code", "npm:0.18.0"],\ ["@lexical/link", "npm:0.18.0"],\ ["@lexical/list", "npm:0.18.0"],\ ["@lexical/markdown", "npm:0.18.0"],\ @@ -13524,6 +13526,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@emotion/jest", "virtual:d0e958afa23d83fcbe1e8341a17fed5245116729be9039e4a7f723d5645a3b4a0db7dc084c7f30fa2114faa4c170592f819708d736b1e487b4ae7cd58824e005#npm:11.13.0"],\ ["@emotion/react", "virtual:3a393e218825bde954376ca1a828a8b21ca2967b8d720dd56f28d8017fc081fa726c0b293069a94a55394c33a36ded19a9a4675c0d537b344c90f8add76eb926#npm:11.11.1"],\ ["@emotion/serialize", "npm:1.1.2"],\ + ["@lexical/code", "npm:0.18.0"],\ ["@lexical/link", "npm:0.18.0"],\ ["@lexical/list", "npm:0.18.0"],\ ["@lexical/markdown", "npm:0.18.0"],\ @@ -13626,6 +13629,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@emotion/jest", "virtual:d0e958afa23d83fcbe1e8341a17fed5245116729be9039e4a7f723d5645a3b4a0db7dc084c7f30fa2114faa4c170592f819708d736b1e487b4ae7cd58824e005#npm:11.13.0"],\ ["@emotion/react", "virtual:3a393e218825bde954376ca1a828a8b21ca2967b8d720dd56f28d8017fc081fa726c0b293069a94a55394c33a36ded19a9a4675c0d537b344c90f8add76eb926#npm:11.11.1"],\ ["@emotion/serialize", "npm:1.1.2"],\ + ["@lexical/code", "npm:0.18.0"],\ ["@lexical/link", "npm:0.18.0"],\ ["@lexical/list", "npm:0.18.0"],\ ["@lexical/markdown", "npm:0.18.0"],\ diff --git a/packages/gp2-components/src/templates/__tests__/OutputDetailPage.test.tsx b/packages/gp2-components/src/templates/__tests__/OutputDetailPage.test.tsx index 01cdacb0a2..1ff9948b01 100644 --- a/packages/gp2-components/src/templates/__tests__/OutputDetailPage.test.tsx +++ b/packages/gp2-components/src/templates/__tests__/OutputDetailPage.test.tsx @@ -3,6 +3,9 @@ import { render } from '@testing-library/react'; import OutputDetailPage from '../OutputDetailPage'; +beforeEach(() => { + jest.spyOn(console, 'error').mockImplementation(); +}); describe('OutputDetailPage', () => { it('displays edit and duplicate buttons if user is administrator', () => { const { queryByTitle } = render( diff --git a/packages/react-components/package.json b/packages/react-components/package.json index a77f72ec03..f093a16f9d 100644 --- a/packages/react-components/package.json +++ b/packages/react-components/package.json @@ -31,6 +31,7 @@ "@babel/runtime-corejs3": "7.23.8", "@emotion/react": "11.11.1", "@emotion/serialize": "1.1.2", + "@lexical/code": "0.18.0", "@lexical/link": "0.18.0", "@lexical/list": "0.18.0", "@lexical/markdown": "0.18.0", diff --git a/packages/react-components/src/atoms/TextEditor.tsx b/packages/react-components/src/atoms/TextEditor.tsx index 929f9c5683..d0ef1f9834 100644 --- a/packages/react-components/src/atoms/TextEditor.tsx +++ b/packages/react-components/src/atoms/TextEditor.tsx @@ -1,27 +1,30 @@ -import { useEffect } from 'react'; import { css } from '@emotion/react'; -import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'; -import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin'; +import { CodeNode } from '@lexical/code'; +import { AutoLinkNode, LinkNode } from '@lexical/link'; +import { ListItemNode, ListNode } from '@lexical/list'; import { AutoFocusPlugin } from '@lexical/react/LexicalAutoFocusPlugin'; import { LexicalComposer } from '@lexical/react/LexicalComposer'; -import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin'; +import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'; import { ContentEditable } from '@lexical/react/LexicalContentEditable'; +import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary'; import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin'; import { ListPlugin } from '@lexical/react/LexicalListPlugin'; -import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary'; -import { ListNode, ListItemNode } from '@lexical/list'; -import { AutoLinkNode, LinkNode } from '@lexical/link'; -import { HeadingNode } from '@lexical/rich-text'; -import { EditorState } from 'lexical'; +import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin'; +import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin'; +import { HeadingNode, QuoteNode } from '@lexical/rich-text'; +import { useEffect } from 'react'; + import { $convertFromMarkdownString, $convertToMarkdownString, TRANSFORMERS, } from '@lexical/markdown'; -import ToolbarPlugin from './TextEditorToolbar'; -import { useValidation, styles, validationMessageStyles } from '../form'; -import { noop } from '../utils'; +import { MarkdownShortcutPlugin } from '@lexical/react/LexicalMarkdownShortcutPlugin'; +import { EditorState } from 'lexical'; import { ember } from '../colors'; +import { styles, useValidation, validationMessageStyles } from '../form'; +import { noop } from '../utils'; +import ToolbarPlugin from './TextEditorToolbar'; const theme = { paragraph: 'editor-paragraph', @@ -149,6 +152,28 @@ const placeholderStyles = css({ pointerEvents: 'none', }); +const markdownStyles = css({ + fontSize: '1em', + + '& .editor-paragraph': { + margin: 0, + marginBottom: '16px', + position: 'relative', + '&:last-child': { + marginBottom: 0, + }, + }, + + '& .editor-list-ol, & .editor-list-ul': { + margin: '0 0 16px 16px', + padding: 0, + }, + + '& .editor-listItem': { + margin: '8px 0', + }, +}); + const onChangeHandler = ( editorState: EditorState, onChange: (content: string) => void, @@ -162,7 +187,8 @@ export type TextEditorProps = { readonly maxLength?: number; readonly value: string; readonly enabled?: boolean; - onChange: (content: string) => void; + readonly isMarkdown?: boolean; + onChange?: (content: string) => void; }; const EnablePlugin = ({ enabled }: { enabled: boolean }) => { @@ -182,11 +208,13 @@ const TextEditor = ({ customValidationMessage = '', enabled = true, getValidationMessage, + isMarkdown = false, }: TextEditorProps) => { const { validationMessage, validationTargetProps } = useValidation( customValidationMessage, getValidationMessage, + isMarkdown, ); const initialConfig = { @@ -194,19 +222,32 @@ const TextEditor = ({ $convertFromMarkdownString(value, TRANSFORMERS); }, namespace: 'Editor', - nodes: [AutoLinkNode, LinkNode, ListNode, ListItemNode, HeadingNode], + nodes: [ + AutoLinkNode, + LinkNode, + ListNode, + ListItemNode, + HeadingNode, + QuoteNode, + CodeNode, + ], theme, // eslint-disable-next-line no-console onError: console.error, }; + if (isMarkdown && !value) return <>; + return (
- - onChangeHandler(editorState, onChange)} - /> + + {!isMarkdown && } + {onChange && ( + onChangeHandler(editorState, onChange)} + /> + )}
[ - styles, - inputStyles, - validationMessage && { - borderColor: ember.rgb, - }, - colors?.primary500 && { - ':focus': { - borderColor: colors?.primary500.rgba, - }, - }, - ]} + css={({ colors }) => + !isMarkdown + ? [ + styles, + inputStyles, + validationMessage && { + borderColor: ember.rgb, + }, + colors?.primary500 && { + ':focus': { + borderColor: colors?.primary500.rgba, + }, + }, + ] + : [markdownStyles] + } /> } placeholder={ @@ -244,14 +289,16 @@ const TextEditor = ({ } ErrorBoundary={LexicalErrorBoundary} /> -