diff --git a/.github/workflows/cypress-testing.yml b/.github/workflows/cypress-testing.yml
index aaf344530..4004b6b2b 100644
--- a/.github/workflows/cypress-testing.yml
+++ b/.github/workflows/cypress-testing.yml
@@ -4,7 +4,7 @@ on:
push:
branches: [master]
pull_request:
- branches: [master]
+ branches: [master, migration-from-draft-js]
jobs:
glific:
@@ -95,6 +95,9 @@ jobs:
echo clone cypress repo
git clone https://github.com/glific/cypress-testing.git
echo done. go to dir.
+ cd cypress-testing
+ git checkout lexical-editor
+ cd ..
cp -r cypress-testing/cypress cypress
yarn add cypress
echo Create cypress.config.ts from example
diff --git a/package.json b/package.json
index f839115ac..5bfe32537 100644
--- a/package.json
+++ b/package.json
@@ -13,13 +13,12 @@
"@appsignal/plugin-window-events": "^1.0.20",
"@appsignal/react": "^1.0.23",
"@date-io/dayjs": "^3.0.0",
- "@draft-js-plugins/editor": "^4.1.4",
- "@draft-js-plugins/mention": "^5.2.2",
"@emoji-mart/data": "^1.1.2",
"@emoji-mart/react": "^1.1.1",
"@emotion/react": "^11.11.3",
"@emotion/styled": "^11.11.0",
"@glific/flow-editor": "^1.19.3-5",
+ "@lexical/react": "^0.13.1",
"@mui/icons-material": "^5.15.6",
"@mui/material": "^5.15.6",
"@mui/x-date-pickers": "^6.19.2",
@@ -32,7 +31,6 @@
"axios": "^1.6.7",
"buffer": "^6.0.3",
"dayjs": "^1.11.10",
- "draft-js": "^0.11.7",
"emoji-mart": "^5.5.2",
"formik": "^2.4.5",
"graphql": "^16.8.1",
@@ -41,6 +39,8 @@
"i18next-browser-languagedetector": "^7.2.0",
"interweave": "^13.1.0",
"interweave-autolink": "^5.1.1",
+ "lexical": "^0.13.1",
+ "lexical-beautiful-mentions": "^0.1.30",
"pino": "^8.17.2",
"pino-logflare": "^0.4.2",
"react": "^18.2.0",
diff --git a/src/App.test.tsx b/src/App.test.tsx
index b13876d04..476c38a63 100644
--- a/src/App.test.tsx
+++ b/src/App.test.tsx
@@ -1,3 +1,4 @@
+import 'mocks/matchMediaMock';
import { MemoryRouter } from 'react-router-dom';
import { MockedProvider } from '@apollo/client/testing';
import { waitFor, render } from '@testing-library/react';
diff --git a/src/common/LexicalWrapper.tsx b/src/common/LexicalWrapper.tsx
new file mode 100644
index 000000000..69ef86a4f
--- /dev/null
+++ b/src/common/LexicalWrapper.tsx
@@ -0,0 +1,20 @@
+import { LexicalComposer } from '@lexical/react/LexicalComposer';
+import { BeautifulMentionNode } from 'lexical-beautiful-mentions';
+
+interface LexicalWrapperProps {
+ children: any;
+}
+
+export const LexicalWrapper = ({ children }: LexicalWrapperProps) => {
+ return (
+ console.log(error),
+ nodes: [BeautifulMentionNode],
+ }}
+ >
+ {children}
+
+ );
+};
diff --git a/src/common/RichEditor.tsx b/src/common/RichEditor.tsx
index b796d820d..c439cf26e 100644
--- a/src/common/RichEditor.tsx
+++ b/src/common/RichEditor.tsx
@@ -1,4 +1,3 @@
-import { EditorState, ContentState } from 'draft-js';
import CallIcon from '@mui/icons-material/Call';
import OpenInNewIcon from '@mui/icons-material/OpenInNew';
import { Interweave } from 'interweave';
@@ -8,12 +7,30 @@ import { UrlMatcher } from 'interweave-autolink';
const regexForLink =
/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{2,256}\.[a-z]{2,4}\b([-a-zA-Z0-9@:%_+.~#?&/=]*)/gi;
-// Convert Draft.js to WhatsApp message format.
-export const getPlainTextFromEditor = (editorState: any) =>
- editorState.getCurrentContent().getPlainText();
+export const handleFormatterEvents = (event: KeyboardEvent) => {
+ if ((event.ctrlKey || event.metaKey) && event.code === 'KeyB') {
+ return 'bold';
+ } else if ((event.ctrlKey || event.metaKey) && event.code === 'KeyI') {
+ return 'italic';
+ } else if ((event.ctrlKey || event.metaKey) && event.code === 'KeyS') {
+ return 'strikethrough';
+ }
+
+ return '';
+};
-export const getEditorFromContent = (text: string) =>
- EditorState.createWithContent(ContentState.createFromText(text));
+export const handleFormatting = (text: string, formatter: string) => {
+ switch (formatter) {
+ case 'bold':
+ return `*${text}*`;
+ case 'italic':
+ return `_${text}_`;
+ case 'strikethrough':
+ return `~${text}~`;
+ default:
+ return text;
+ }
+};
const isAlphanumeric = (c: any) => {
const x = c.charCodeAt();
diff --git a/src/components/UI/EmojiPicker/EmojiPicker.tsx b/src/components/UI/EmojiPicker/EmojiPicker.tsx
index 6e11e9521..2c8481300 100644
--- a/src/components/UI/EmojiPicker/EmojiPicker.tsx
+++ b/src/components/UI/EmojiPicker/EmojiPicker.tsx
@@ -6,10 +6,12 @@ export interface EmojiPickerProps {
onEmojiSelect: Function;
}
-export const EmojiPicker = ({ displayStyle, onEmojiSelect }: EmojiPickerProps) => (
-
-);
+export const EmojiPicker = ({ displayStyle, onEmojiSelect }: EmojiPickerProps) => {
+ return (
+
+ );
+};
export default EmojiPicker;
diff --git a/src/components/UI/Form/EmojiInput/Editor.module.css b/src/components/UI/Form/EmojiInput/Editor.module.css
new file mode 100644
index 000000000..646173caa
--- /dev/null
+++ b/src/components/UI/Form/EmojiInput/Editor.module.css
@@ -0,0 +1,160 @@
+.HelperText {
+ margin-left: 16px !important;
+ color: #93a29b !important;
+ line-height: 1 !important;
+ font-size: 12px !important;
+ margin-top: 4px !important;
+}
+
+.Editor {
+ position: relative;
+ border: 2px solid rgba(0, 0, 0, 0.23);
+ border-radius: 12px;
+ height: 10rem;
+ position: relative;
+ padding: 1rem;
+ position: relative;
+ display: block;
+ border-bottom-left-radius: 10px;
+ border-bottom-right-radius: 10px;
+}
+
+.Label {
+ font-size: 16px;
+ font-weight: 500;
+ color: #93a29b !important;
+}
+
+.DangerText {
+ margin-left: 16px !important;
+ color: #fb5c5c;
+}
+
+.EditorInput {
+ resize: none;
+ font-size: 16px;
+ width: 100%;
+ min-height: 40px;
+ height: 100%;
+ margin-top: 4px;
+ position: relative;
+ tab-size: 1;
+ outline: 0;
+}
+
+.EditorInput p {
+ overflow: scroll !important;
+ margin: 0 !important;
+}
+
+.EditorWrapper {
+ margin: 1rem 0;
+}
+
+.editorScroller {
+ border: 0;
+ display: flex;
+ position: relative;
+ outline: 0;
+ z-index: 0;
+ height: 100%;
+ overflow: auto;
+}
+
+.disabled {
+ position: relative;
+ border: 2px solid rgba(0, 0, 0, 0.23);
+ border-radius: 12px;
+ height: 10rem;
+ position: relative;
+ padding: 1rem;
+ position: relative;
+ display: block;
+ border-bottom-left-radius: 10px;
+ border-bottom-right-radius: 10px;
+ color: rgba(0, 0, 0, 0.38);
+}
+
+.editor {
+ flex: auto;
+ position: relative;
+ resize: vertical;
+ z-index: -1;
+}
+
+.editorPlaceholder {
+ color: #999;
+ overflow: hidden;
+ position: absolute;
+ text-overflow: ellipsis;
+ top: 1px;
+ left: 14px;
+ font-size: 16px;
+ user-select: none;
+ display: inline-block;
+ pointer-events: none;
+}
+
+/*
+ Overriding lexical mentions styles as per requirement
+*/
+.MentionMenu {
+ padding: 0;
+ background: #fff;
+ box-shadow: 0px 5px 10px rgba(0, 0, 0, 0.3);
+ border-radius: 8px;
+ list-style: none;
+ margin: 0;
+ max-height: 200px;
+ overflow-y: scroll;
+ -ms-overflow-style: none;
+ width: 250px;
+ scrollbar-width: none;
+}
+
+.MentionMenu ul li {
+ margin: 0;
+ min-width: 180px;
+ font-size: 14px;
+ outline: none;
+ cursor: pointer;
+ border-radius: 8px;
+}
+
+.Selected {
+ background: #eee !important;
+}
+
+.MentionMenu li {
+ padding: 8px;
+ color: #050505;
+ cursor: pointer;
+ line-height: 16px;
+ font-size: 15px;
+ display: flex;
+ align-content: center;
+ flex-direction: row;
+ flex-shrink: 0;
+ background-color: #fff;
+ border-radius: 8px;
+ border: 0;
+}
+
+.MentionMenu li.active {
+ display: flex;
+ width: 20px;
+ height: 20px;
+ background-size: contain;
+}
+
+.MentionMenu li:first-child {
+ border-radius: 8px 8px 0px 0px;
+}
+
+.MentionMenu li:last-child {
+ border-radius: 0px 0px 8px 8px;
+}
+
+.MentionMenu li:hover {
+ background-color: #eee;
+}
diff --git a/src/components/UI/Form/EmojiInput/Editor.test.tsx b/src/components/UI/Form/EmojiInput/Editor.test.tsx
new file mode 100644
index 000000000..3f2f58504
--- /dev/null
+++ b/src/components/UI/Form/EmojiInput/Editor.test.tsx
@@ -0,0 +1,31 @@
+import 'mocks/matchMediaMock';
+import { render } from '@testing-library/react';
+import { Editor } from './Editor';
+import { LexicalWrapper } from 'common/LexicalWrapper';
+
+const mockIntersectionObserver = class {
+ constructor() {}
+ observe() {}
+ unobserve() {}
+ disconnect() {}
+};
+
+const lexicalChange = vi.fn;
+
+(window as any).IntersectionObserver = mockIntersectionObserver;
+
+const wrapper = (
+
+ {} }}
+ placeholder={''}
+ onChange={lexicalChange}
+ />
+
+);
+
+it('should render lexical editor', () => {
+ const { getByTestId } = render(wrapper);
+ expect(getByTestId('editor-body')).toBeInTheDocument();
+});
diff --git a/src/components/UI/Form/EmojiInput/Editor.tsx b/src/components/UI/Form/EmojiInput/Editor.tsx
new file mode 100644
index 000000000..e7c46ef47
--- /dev/null
+++ b/src/components/UI/Form/EmojiInput/Editor.tsx
@@ -0,0 +1,151 @@
+import styles from './Editor.module.css';
+import { forwardRef, useState, useEffect } from 'react';
+import { PlainTextPlugin } from '@lexical/react/LexicalPlainTextPlugin';
+import { ContentEditable } from '@lexical/react/LexicalContentEditable';
+import {
+ $getSelection,
+ $createTextNode,
+ $getRoot,
+ $createParagraphNode,
+ KEY_DOWN_COMMAND,
+ COMMAND_PRIORITY_LOW,
+} from 'lexical';
+import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin';
+import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
+import LexicalErrorBoundary from '@lexical/react/LexicalErrorBoundary';
+import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin';
+import { FormHelperText } from '@mui/material';
+import {
+ BeautifulMentionsPlugin,
+ BeautifulMentionsMenuProps,
+ BeautifulMentionsMenuItemProps,
+} from 'lexical-beautiful-mentions';
+import { handleFormatterEvents, handleFormatting } from 'common/RichEditor';
+
+export interface EditorProps {
+ field: { name: string; onChange?: any; value: any; onBlur: any };
+ disabled?: any;
+ form?: { touched: any; errors: any };
+ placeholder: string;
+ helperText?: string;
+ picker?: any;
+ inputProp?: any;
+ onChange?: any;
+ isEditing: boolean;
+}
+
+export const Editor = ({ disabled = false, isEditing = false, ...props }: EditorProps) => {
+ const [editorState, setEditorState] = useState('');
+ const { field, form, picker, placeholder, onChange } = props;
+ const mentions = props.inputProp?.suggestions || [];
+ const suggestions = {
+ '@': mentions.map((mention: string) => mention?.split('@')[1]),
+ };
+ const [editor] = useLexicalComposerContext();
+
+ useEffect(() => {
+ if (field.value && isEditing && !editorState) {
+ editor.update(() => {
+ const root = $getRoot();
+ root.clear();
+ const paragraph = $createParagraphNode();
+ paragraph.append($createTextNode(field.value || ''));
+ root.append(paragraph);
+ });
+ }
+ }, [field.value]);
+
+ const Placeholder = () => {
+ return {placeholder}
;
+ };
+
+ useEffect(() => {
+ return editor.registerCommand(
+ KEY_DOWN_COMMAND,
+ (event: KeyboardEvent) => {
+ let formatter = handleFormatterEvents(event);
+
+ editor.update(() => {
+ const selection = $getSelection();
+ if (selection?.getTextContent() && formatter) {
+ const text = handleFormatting(selection?.getTextContent(), formatter);
+ const newNode = $createTextNode(text);
+ selection?.insertNodes([newNode]);
+ }
+ });
+ return false;
+ },
+ COMMAND_PRIORITY_LOW
+ );
+ }, [editor]);
+
+ useEffect(() => {
+ if (disabled) {
+ editor.setEditable(false);
+ }
+ }, [disabled]);
+
+ const handleChange = (editorState: any) => {
+ editorState.read(() => {
+ const root = $getRoot();
+ if (!disabled) {
+ onChange(root.getTextContent());
+ setEditorState(root.getTextContent());
+ }
+ });
+ };
+
+ return (
+
+
+
}
+ contentEditable={
+
+ }
+ ErrorBoundary={LexicalErrorBoundary}
+ />
+
+
+
+ {picker}
+
+ {form && form.errors[field.name] && form.touched[field.name] ? (
+
{form.errors[field.name]}
+ ) : null}
+ {props.helperText && (
+
{props.helperText}
+ )}
+
+ );
+};
+
+const CustomMenu = forwardRef(
+ ({ open, loading, ...props }, ref) =>
+);
+
+const CustomMenuItem = forwardRef(
+ ({ selected, item, ...props }, ref) => {
+ return (
+
+ );
+ }
+);
diff --git a/src/components/UI/Form/EmojiInput/EmojiInput.module.css b/src/components/UI/Form/EmojiInput/EmojiInput.module.css
index 8bcca3089..869df36f8 100644
--- a/src/components/UI/Form/EmojiInput/EmojiInput.module.css
+++ b/src/components/UI/Form/EmojiInput/EmojiInput.module.css
@@ -20,7 +20,7 @@
}
/*
- Overriding draft-js mentions styles as per requirement
+ Overriding lexical mentions styles as per requirement
*/
.mentionSuggestions {
composes: Font;
diff --git a/src/components/UI/Form/EmojiInput/EmojiInput.test.tsx b/src/components/UI/Form/EmojiInput/EmojiInput.test.tsx
index c69ad9217..9745fa768 100644
--- a/src/components/UI/Form/EmojiInput/EmojiInput.test.tsx
+++ b/src/components/UI/Form/EmojiInput/EmojiInput.test.tsx
@@ -1,23 +1,10 @@
+import 'mocks/matchMediaMock';
import { render } from '@testing-library/react';
import { EmojiInput } from './EmojiInput';
-import { EditorState } from 'draft-js';
import userEvent from '@testing-library/user-event';
const setFieldValueMock = vi.fn();
-Object.defineProperty(window, 'matchMedia', {
- writable: true,
- value: (query: any) => {
- return {
- matches: false,
- media: query,
- onchange: null,
- addListener: vi.fn(),
- removeListener: vi.fn(),
- };
- },
-});
-
const mockIntersectionObserver = class {
constructor() {}
observe() {}
@@ -32,10 +19,10 @@ const wrapper = (
form={{
touched: false,
errors: {},
- values: { input: EditorState.createEmpty() },
+ values: { input: '' },
setFieldValue: setFieldValueMock,
}}
- field={{ name: 'input', value: EditorState.createEmpty(), onChange: vi.fn() }}
+ field={{ name: 'input', value: '', onChange: vi.fn() }}
label="Title"
placeholder="Title"
rows={10}
@@ -43,7 +30,7 @@ const wrapper = (
);
it('renders component', () => {
const { getByTestId } = render(wrapper);
- expect(getByTestId('input')).toBeInTheDocument();
+ expect(getByTestId('editor-input')).toBeInTheDocument();
});
it('should have a emoji picker', () => {
diff --git a/src/components/UI/Form/EmojiInput/EmojiInput.tsx b/src/components/UI/Form/EmojiInput/EmojiInput.tsx
index 08c69d126..6af42c145 100644
--- a/src/components/UI/Form/EmojiInput/EmojiInput.tsx
+++ b/src/components/UI/Form/EmojiInput/EmojiInput.tsx
@@ -1,13 +1,11 @@
-import { useState, useMemo, useCallback, forwardRef } from 'react';
-import { RichUtils, Modifier, EditorState, ContentState } from 'draft-js';
-import Editor from '@draft-js-plugins/editor';
-import createMentionPlugin from '@draft-js-plugins/mention';
+import { useState } from 'react';
import { InputAdornment, IconButton, ClickAwayListener } from '@mui/material';
-
-import { getPlainTextFromEditor } from 'common/RichEditor';
import { EmojiPicker } from 'components/UI/EmojiPicker/EmojiPicker';
-import { Input } from '../Input/Input';
+import { Editor } from './Editor';
import Styles from './EmojiInput.module.css';
+import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
+import { $createTextNode, $getSelection, $isRangeSelection } from 'lexical';
+import { LexicalWrapper } from 'common/LexicalWrapper';
export interface EmojiInputProps {
field: any;
@@ -20,174 +18,80 @@ export interface EmojiInputProps {
handleBlur?: any;
getEditorValue?: any;
inputProp?: any;
+ isEditing?: boolean;
}
-const getMentionComponentAndPlugin = () => {
- const mentionPlugin = createMentionPlugin({
- theme: Styles,
- });
- const { MentionSuggestions } = mentionPlugin;
- const plugins = [mentionPlugin];
- return { plugins, MentionSuggestions };
-};
-
-const customSuggestionsFilter = (searchValue: string, suggestions: Array) => {
- const size = (list: any) => (list.constructor.name === 'List' ? list.size : list.length);
-
- const get = (obj: any, attr: any) => (obj.get ? obj.get(attr) : obj[attr]);
-
- const value = searchValue.toLowerCase();
- const filteredSuggestions = suggestions.filter(
- (suggestion) => !value || get(suggestion, 'name').toLowerCase().indexOf(value) > -1
- );
-
- /**
- * We can restrict no of values from dropdown using this
- * Currently returning all values for give dropdown
- */
- const length = size(filteredSuggestions);
- return filteredSuggestions.slice(0, length);
-};
-
-const DraftField = forwardRef((inputProps: any, ref) => {
- const {
- component: Component,
- open,
- suggestions,
- onOpenChange,
- onSearchChange,
- ...other
- } = inputProps;
-
- const { MentionSuggestions, plugins } = useMemo(getMentionComponentAndPlugin, []);
-
- return (
- <>
-
-
- >
- );
-});
+interface EmojiPickerProps {
+ handleClickAway: any;
+ showEmojiPicker: any;
+ setShowEmojiPicker: any;
+}
export const EmojiInput = ({
field: { value, name, onBlur },
handleChange,
getEditorValue,
handleBlur,
+ isEditing = false,
...props
}: EmojiInputProps) => {
const [showEmojiPicker, setShowEmojiPicker] = useState(false);
- const updateValue = (input: any, isEmoji = false) => {
- const editorContentState = value.getCurrentContent();
- const editorSelectionState: any = value.getSelection();
- const ModifiedContent = Modifier.replaceText(
- editorContentState,
- editorSelectionState,
- isEmoji ? input.native : input
- );
- let updatedEditorState = EditorState.push(value, ModifiedContent, 'insert-characters');
- if (!isEmoji) {
- const editorSelectionStateMod = updatedEditorState.getSelection();
- const updatedSelection = editorSelectionStateMod.merge({
- anchorOffset: editorSelectionStateMod.getAnchorOffset() - 1,
- focusOffset: editorSelectionStateMod.getFocusOffset() - 1,
- });
- updatedEditorState = EditorState.forceSelection(updatedEditorState, updatedSelection);
- }
- props.form.setFieldValue(name, updatedEditorState);
- };
-
- const handleKeyCommand = (command: any, editorState: any) => {
- if (command === 'underline') {
- return 'handled';
- }
- if (command === 'bold') {
- updateValue('**');
- } else if (command === 'italic') {
- updateValue('__');
- } else {
- const newState = RichUtils.handleKeyCommand(editorState, command);
- if (newState) {
- props.form.setFieldValue(name, newState);
- return 'handled';
- }
- }
- return 'not-handled';
- };
-
- const draftJsChange = (editorState: any) => {
+ const lexicalChange = (editorState: any) => {
if (handleChange) {
- handleChange(getPlainTextFromEditor(props.form.values.example));
+ handleChange(editorState);
}
if (getEditorValue) {
getEditorValue(editorState);
}
- props.form.setFieldValue(name, editorState);
- };
-
- const mentions = props.inputProp?.suggestions || [];
- const [open, setOpen] = useState(false);
- const [suggestions, setSuggestions] = useState(mentions);
-
- const onOpenChange = (_open: boolean) => {
- setOpen(_open);
- };
-
- const getSuggestions = useCallback(customSuggestionsFilter, []);
-
- const onSearchChange = ({ value: searchValue }: { value: string }) => {
- setSuggestions(getSuggestions(searchValue, mentions));
+ props.form.setFieldValue(name, editorState);
};
- const inputProps = {
- component: Editor,
- editorState: value,
- open,
- readOnly: props.disabled,
- suggestions,
- onOpenChange,
- onSearchChange,
- handlePastedText: (text: string, html: string, editorState: EditorState) => {
- const pastedBlocks = ContentState.createFromText(text).getBlockMap();
- const newState = Modifier.replaceWithFragment(
- editorState.getCurrentContent(),
- editorState.getSelection(),
- pastedBlocks
- );
- const newEditorState = EditorState.push(editorState, newState, 'insert-fragment');
- draftJsChange(newEditorState);
- return 'handled';
- },
- handleKeyCommand,
- onBlur: handleBlur,
- onChange: draftJsChange,
+ const handleClickAway = () => {
+ setShowEmojiPicker(false);
};
- const editor = { inputComponent: DraftField, inputProps };
-
- const emojiPicker = showEmojiPicker ? (
- updateValue(emojiValue, true)}
- displayStyle={{ position: 'absolute', top: '10px', right: '0px', zIndex: 2 }}
+ const picker = (
+
- ) : (
- ''
);
- const handleClickAway = () => {
- setShowEmojiPicker(false);
+ const input = (
+
+
+
+ );
+
+ return input;
+};
+
+const EmojiPickerComponent = ({
+ showEmojiPicker,
+ setShowEmojiPicker,
+ handleClickAway,
+}: EmojiPickerProps) => {
+ const [editor] = useLexicalComposerContext();
+
+ const emojiStyles = {
+ position: 'absolute',
+ bottom: '60px',
+ right: '-150px',
+ zIndex: 100,
};
- const picker = (
+ return (
-
+
- {emojiPicker}
+ {showEmojiPicker && (
+ {
+ editor.update(() => {
+ const selection = $getSelection();
+ if ($isRangeSelection(selection)) {
+ selection.insertNodes([$createTextNode(emoji.native)]);
+ }
+ });
+ }}
+ displayStyle={emojiStyles}
+ />
+ )}
);
-
- const input = (
-
- );
-
- return input;
};
diff --git a/src/components/UI/Form/WhatsAppEditor/WhatsAppEditor.module.css b/src/components/UI/Form/WhatsAppEditor/WhatsAppEditor.module.css
index a9600a4fd..35fd91a29 100644
--- a/src/components/UI/Form/WhatsAppEditor/WhatsAppEditor.module.css
+++ b/src/components/UI/Form/WhatsAppEditor/WhatsAppEditor.module.css
@@ -2,22 +2,7 @@
overflow: auto;
width: 100%;
min-height: 40px;
-}
-
-.Editor :global(.public-DraftEditor-content) > div {
- padding-top: 10px;
- padding-bottom: 10px;
- color: #073f24;
- font-size: 16px;
- line-height: 1.25;
-}
-
-.Editor :global(.public-DraftEditorPlaceholder-inner) {
- padding-top: 10px;
- margin-bottom: -32px;
- pointer-events: none;
- color: #93a29b;
- font-size: 16px;
+ position: relative;
}
/* TODO: Override emoji-mart-emoji font-size to make it medium. */
@@ -28,3 +13,28 @@ span.emoji-mart-emoji {
.emoji-mart-emoji {
font-size: medium;
}
+
+.editorInput {
+ resize: none;
+ font-size: 16px;
+ overflow: auto;
+ width: 100%;
+ min-height: 40px;
+ margin-top: 4px;
+ position: relative;
+ tab-size: 1;
+ outline: 0;
+}
+
+.editorPlaceholder {
+ color: #999;
+ overflow: hidden;
+ position: absolute;
+ text-overflow: ellipsis;
+ top: 22px;
+ left: 0px;
+ font-size: 15px;
+ user-select: none;
+ display: inline-block;
+ pointer-events: none;
+}
diff --git a/src/components/UI/Form/WhatsAppEditor/WhatsAppEditor.test.tsx b/src/components/UI/Form/WhatsAppEditor/WhatsAppEditor.test.tsx
index 806783fc3..79304f294 100644
--- a/src/components/UI/Form/WhatsAppEditor/WhatsAppEditor.test.tsx
+++ b/src/components/UI/Form/WhatsAppEditor/WhatsAppEditor.test.tsx
@@ -1,10 +1,10 @@
-import { render, fireEvent, waitFor } from '@testing-library/react';
-import draftJs, { EditorState, ContentState } from 'draft-js';
+import 'mocks/matchMediaMock';
+import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { vi } from 'vitest';
import WhatsAppEditor from './WhatsAppEditor';
-
-const mockHandleKeyCommand = vi.fn();
+import { userEvent } from '@testing-library/user-event';
+import { LexicalWrapper } from 'common/LexicalWrapper';
const mockObserve = vi.fn();
global.ResizeObserver = vi.fn().mockImplementation(() => ({
@@ -13,53 +13,57 @@ global.ResizeObserver = vi.fn().mockImplementation(() => ({
disconnect: vi.fn(),
}));
-vi.spyOn(draftJs, 'Editor').mockImplementation((props: any, _context: any) => {
- const input: any = (
- {
- props.handleKeyCommand('underline');
- mockHandleKeyCommand();
- }}
- onChange={(event) => props.onChange(event)}
- >
- );
- return input;
-});
-
describe('', () => {
const handleHeightChange = vi.fn();
const sendMessage = vi.fn();
const setEditorState = vi.fn();
- const defaultProps = (editorState: any) => {
+ const defaultProps = () => {
return {
handleHeightChange: handleHeightChange,
sendMessage: sendMessage,
- editorState: editorState,
setEditorState: setEditorState,
};
};
- const editorContent = EditorState.createWithContent(ContentState.createFromText('Hello'));
+ test('input change should trigger callBacks', async () => {
+ const { getByTestId } = render(
+
+
+
+ );
- test('input change should trigger callBacks', () => {
- const { getByTestId } = render();
- fireEvent.change(getByTestId('editor'), {
- target: { value: 10 },
- });
- expect(setEditorState).toHaveBeenCalled();
+ await userEvent.click(getByTestId('editor'));
+ await userEvent.tab();
+ await fireEvent.input(getByTestId('editor'), { data: 'test' });
+
+ expect(getByTestId('editor')).toHaveTextContent('test');
});
- test('handleKeyCommand should work with new commands', () => {
- const { getByTestId } = render();
- fireEvent.click(getByTestId('editor'));
+ test('text is changed in lexical editor', async () => {
+ const { getByTestId } = render(
+
+
+
+ );
- expect(mockHandleKeyCommand).toHaveBeenCalled();
+ const editor = screen.getByTestId('editor');
+
+ await userEvent.click(editor);
+ await userEvent.tab();
+ fireEvent.input(editor, { data: 'test' });
+
+ await waitFor(() => {
+ expect(editor).toHaveTextContent('test');
+ });
});
test('resize observer event is called', async () => {
- render();
+ render(
+
+
+
+ );
await waitFor(() => {
expect(mockObserve).toHaveBeenCalled();
});
diff --git a/src/components/UI/Form/WhatsAppEditor/WhatsAppEditor.tsx b/src/components/UI/Form/WhatsAppEditor/WhatsAppEditor.tsx
index 9b1ea22c4..90fb848be 100644
--- a/src/components/UI/Form/WhatsAppEditor/WhatsAppEditor.tsx
+++ b/src/components/UI/Form/WhatsAppEditor/WhatsAppEditor.tsx
@@ -1,110 +1,100 @@
-import { useCallback } from 'react';
-import { RichUtils, getDefaultKeyBinding, Modifier, EditorState, Editor } from 'draft-js';
+import { useEffect } from 'react';
+import { PlainTextPlugin } from '@lexical/react/LexicalPlainTextPlugin';
+import { ContentEditable } from '@lexical/react/LexicalContentEditable';
+import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin';
+import {
+ $getSelection,
+ $createTextNode,
+ $getRoot,
+ KEY_DOWN_COMMAND,
+ COMMAND_PRIORITY_LOW,
+} from 'lexical';
+import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin';
+import { ClearEditorPlugin } from '@lexical/react/LexicalClearEditorPlugin';
+import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
+import LexicalErrorBoundary from '@lexical/react/LexicalErrorBoundary';
import { useResizeDetector } from 'react-resize-detector';
-import { useTranslation } from 'react-i18next';
-import { getPlainTextFromEditor } from 'common/RichEditor';
import styles from './WhatsAppEditor.module.css';
+import { handleFormatterEvents, handleFormatting } from 'common/RichEditor';
interface WhatsAppEditorProps {
- handleHeightChange(newHeight: number): void;
- sendMessage(message: string): void;
- editorState: any;
+ sendMessage(message: any): void;
setEditorState(editorState: any): void;
readOnly?: boolean;
}
-export const updatedValue = (input: any, editorState: EditorState, isEmoji: boolean = false) => {
- const editorContentState = editorState.getCurrentContent();
- const editorSelectionState: any = editorState.getSelection();
- const ModifiedContent = Modifier.replaceText(
- editorContentState,
- editorSelectionState,
- isEmoji ? input.native : input
- );
- let updatedEditorState = EditorState.push(editorState, ModifiedContent, 'insert-characters');
- if (!isEmoji) {
- const editorSelectionStateMod = updatedEditorState.getSelection();
- const updatedSelection = editorSelectionStateMod.merge({
- anchorOffset: editorSelectionStateMod.getAnchorOffset() - 1,
- focusOffset: editorSelectionStateMod.getFocusOffset() - 1,
- });
- updatedEditorState = EditorState.forceSelection(updatedEditorState, updatedSelection);
- }
-
- return updatedEditorState;
-};
-
export const WhatsAppEditor = ({
setEditorState,
sendMessage,
- editorState,
- handleHeightChange,
readOnly = false,
}: WhatsAppEditorProps) => {
- const { t } = useTranslation();
+ const [editor] = useLexicalComposerContext();
- const handleChange = (editorStateChange: any) => {
- setEditorState(editorStateChange);
- };
+ const { ref } = useResizeDetector({
+ refreshMode: 'debounce',
+ refreshRate: 1000,
+ });
- const handleKeyCommand = (command: string, editorStateChange: any) => {
- // On enter, submit. Otherwise, deal with commands like normal.
- if (command === 'enter') {
- // Convert Draft.js to WhatsApp
- sendMessage(getPlainTextFromEditor(editorStateChange));
- return 'handled';
- }
+ const onChange = (editorState: any) => {
+ editorState.read(() => {
+ const root = $getRoot();
+ setEditorState(root.getTextContent());
+ });
+ };
- if (command === 'underline') {
- return 'handled';
+ useEffect(() => {
+ if (readOnly) {
+ editor.setEditable(false);
}
+ }, [readOnly]);
- if (command === 'bold') {
- setEditorState(updatedValue('**', editorState));
- } else if (command === 'italic') {
- setEditorState(updatedValue('__', editorState));
- } else {
- const newState = RichUtils.handleKeyCommand(editorStateChange, command);
- if (newState) {
- setEditorState(newState);
- return 'handled';
- }
- }
- return 'not-handled';
- };
+ useEffect(() => {
+ return editor.registerCommand(
+ KEY_DOWN_COMMAND,
+ (event: KeyboardEvent) => {
+ // Handle event here
+ let formatter = '';
+ if (event.code === 'Enter' && !event.shiftKey) {
+ event.preventDefault();
+ const root = $getRoot();
+ let textMessage = root.getTextContent();
+ sendMessage(textMessage);
+ return true;
+ } else {
+ formatter = handleFormatterEvents(event);
+ }
- const keyBindingFn = (e: any) => {
- // Shift-enter is by default supported. Only 'enter' needs to be changed.
- if (e.keyCode === 13 && !e.nativeEvent.shiftKey) {
- return 'enter';
- }
- return getDefaultKeyBinding(e);
- };
+ editor.update(() => {
+ const selection = $getSelection();
+ if (selection?.getTextContent() && formatter) {
+ const text = handleFormatting(selection?.getTextContent(), formatter);
+ const newNode = $createTextNode(text);
+ selection?.insertNodes([newNode]);
+ }
+ });
- const onResize = useCallback((width: number | undefined, height: number | undefined) => {
- if (height) {
- handleHeightChange(height - 40);
- }
- }, []);
+ return false;
+ },
+ COMMAND_PRIORITY_LOW
+ );
+ }, [editor]);
- const { ref } = useResizeDetector({
- refreshMode: 'debounce',
- refreshRate: 1000,
- onResize,
- });
+ const Placeholder = () => {
+ return Type a message...
;
+ };
return (
-
-
+ }
+ contentEditable={}
+ ErrorBoundary={LexicalErrorBoundary}
/>
+
+
+
);
};
diff --git a/src/components/UI/MessageDialog/MessageDialog.test.tsx b/src/components/UI/MessageDialog/MessageDialog.test.tsx
index d127b0e75..b600f9c78 100644
--- a/src/components/UI/MessageDialog/MessageDialog.test.tsx
+++ b/src/components/UI/MessageDialog/MessageDialog.test.tsx
@@ -1,8 +1,10 @@
+import 'mocks/matchMediaMock';
import { fireEvent, render } from '@testing-library/react';
import { MockedProvider } from '@apollo/client/testing';
import { MessageDialog } from './MessageDialog';
import { getAttachmentPermissionMock } from 'mocks/Attachment';
+import { LexicalWrapper } from 'common/LexicalWrapper';
const handleClose = vi.fn();
@@ -13,7 +15,9 @@ const defaultProps = {
};
const wrapper = (
-
+
+
+
);
diff --git a/src/components/UI/MessageDialog/MessageDialog.tsx b/src/components/UI/MessageDialog/MessageDialog.tsx
index fbcd0c2bc..4799db75e 100644
--- a/src/components/UI/MessageDialog/MessageDialog.tsx
+++ b/src/components/UI/MessageDialog/MessageDialog.tsx
@@ -3,6 +3,7 @@ import { Dialog, DialogContent } from '@mui/material';
import CrossDarkIcon from 'assets/images/icons/CrossDark.svg?react';
import ChatInput from 'containers/Chat/ChatMessages/ChatInput/ChatInput';
import styles from './MessageDialog.module.css';
+import { LexicalWrapper } from 'common/LexicalWrapper';
export interface MessageDialogProps {
title: string;
@@ -21,13 +22,14 @@ export const MessageDialog = ({ title, onSendMessage, handleClose }: MessageDial
{title}
- {}}
- contactStatus=""
- contactBspStatus="SESSION_AND_HSM"
- additionalStyle={styles.ChatInput}
- />
+
+
+
diff --git a/src/containers/Chat/Chat.test.tsx b/src/containers/Chat/Chat.test.tsx
index 4f244d537..c5b4f0485 100644
--- a/src/containers/Chat/Chat.test.tsx
+++ b/src/containers/Chat/Chat.test.tsx
@@ -1,3 +1,4 @@
+import 'mocks/matchMediaMock';
import { cleanup, render, waitFor } from '@testing-library/react';
import { MockedProvider } from '@apollo/client/testing';
diff --git a/src/containers/Chat/ChatConversations/ConversationList/ConversationList.test.tsx b/src/containers/Chat/ChatConversations/ConversationList/ConversationList.test.tsx
index 74615bf45..d0480dc7c 100644
--- a/src/containers/Chat/ChatConversations/ConversationList/ConversationList.test.tsx
+++ b/src/containers/Chat/ChatConversations/ConversationList/ConversationList.test.tsx
@@ -1,3 +1,4 @@
+import 'mocks/matchMediaMock';
import { BrowserRouter as Router } from 'react-router-dom';
import { render, waitFor, screen, fireEvent } from '@testing-library/react';
import { ApolloClient, ApolloProvider, InMemoryCache } from '@apollo/client';
diff --git a/src/containers/Chat/ChatInterface/ChatInterface.test.tsx b/src/containers/Chat/ChatInterface/ChatInterface.test.tsx
index 5bbf98f95..e960ffcc8 100644
--- a/src/containers/Chat/ChatInterface/ChatInterface.test.tsx
+++ b/src/containers/Chat/ChatInterface/ChatInterface.test.tsx
@@ -1,3 +1,4 @@
+import 'mocks/matchMediaMock';
import { MemoryRouter } from 'react-router-dom';
import { cleanup, render } from '@testing-library/react';
import { ApolloClient, ApolloProvider, InMemoryCache } from '@apollo/client';
diff --git a/src/containers/Chat/ChatMessages/ChatInput/ChatInput.module.css b/src/containers/Chat/ChatMessages/ChatInput/ChatInput.module.css
index c8e6bfb0e..c79280e5b 100644
--- a/src/containers/Chat/ChatMessages/ChatInput/ChatInput.module.css
+++ b/src/containers/Chat/ChatMessages/ChatInput/ChatInput.module.css
@@ -1,11 +1,7 @@
.ChatInput {
- padding-top: 15px;
- position: absolute;
- bottom: 15px;
-
- left: 0;
- right: 0;
+ background-color: #f9f7f4;
margin: auto;
+ max-width: 100% !important;
}
.ChatInputElements {
diff --git a/src/containers/Chat/ChatMessages/ChatInput/ChatInput.test.tsx b/src/containers/Chat/ChatMessages/ChatInput/ChatInput.test.tsx
index bc9f88ced..b0bef2fa6 100644
--- a/src/containers/Chat/ChatMessages/ChatInput/ChatInput.test.tsx
+++ b/src/containers/Chat/ChatMessages/ChatInput/ChatInput.test.tsx
@@ -1,3 +1,4 @@
+import 'mocks/matchMediaMock';
import { MockedProvider } from '@apollo/client/testing';
import { render, waitFor, fireEvent } from '@testing-library/react';
import { vi } from 'vitest';
@@ -11,6 +12,7 @@ import {
} from 'mocks/Attachment';
import { searchInteractive } from 'mocks/InteractiveMessage';
import '../VoiceRecorder/VoiceRecorder';
+import { LexicalWrapper } from 'common/LexicalWrapper';
const mocks = [
searchInteractive,
@@ -68,7 +70,9 @@ describe('', () => {
const chatInput = (
-
+
+
+
);
@@ -148,7 +152,8 @@ describe('', () => {
const interactiveMessages = getAllByTestId('shortcutButton')[2];
fireEvent.click(interactiveMessages);
await waitFor(() => {
- const listItem = getAllByTestId('templateItem')[0];
+ const listItem = getAllByTestId('templateItem')[1];
+
fireEvent.click(listItem);
});
fireEvent.click(getByTestId('sendButton'));
@@ -170,7 +175,9 @@ describe('', () => {
propsWithBspStatusNone.contactBspStatus = 'NONE';
const { getByText } = render(
-
+
+
+
);
@@ -186,7 +193,9 @@ describe('', () => {
propsWithBspStatusHSM.contactBspStatus = 'HSM';
const { getByText } = render(
-
+
+
+
);
expect(getByText('Templates')).toBeInTheDocument();
@@ -197,7 +206,9 @@ describe('', () => {
propsWithBspStatusSession.contactBspStatus = 'SESSION';
const { getByText } = render(
-
+
+
+
);
expect(getByText('Speed sends')).toBeInTheDocument();
@@ -211,7 +222,9 @@ describe('', () => {
const { getByText } = render(
-
+
+
+
);
expect(getByText('Templates')).toBeInTheDocument();
@@ -223,7 +236,9 @@ describe('', () => {
propsWithMockSend.onSendMessage = sendMessageMock;
const { getByText, getByTestId } = render(
-
+
+
+
);
diff --git a/src/containers/Chat/ChatMessages/ChatInput/ChatInput.tsx b/src/containers/Chat/ChatMessages/ChatInput/ChatInput.tsx
index b98744be5..27307df81 100644
--- a/src/containers/Chat/ChatMessages/ChatInput/ChatInput.tsx
+++ b/src/containers/Chat/ChatMessages/ChatInput/ChatInput.tsx
@@ -1,5 +1,4 @@
import { useState } from 'react';
-import { EditorState, ContentState } from 'draft-js';
import { Container, Button, ClickAwayListener, Fade, IconButton } from '@mui/material';
import { useMutation, useQuery } from '@apollo/client';
import { useTranslation } from 'react-i18next';
@@ -9,10 +8,9 @@ import AttachmentIconSelected from 'assets/images/icons/Attachment/Selected.svg?
import VariableIcon from 'assets/images/icons/Template/Variable.svg?react';
import CrossIcon from 'assets/images/icons/Clear.svg?react';
import SendMessageIcon from 'assets/images/icons/SendMessage.svg?react';
-import { getPlainTextFromEditor, getEditorFromContent } from 'common/RichEditor';
import { is24HourWindowOver, pattern } from 'common/constants';
import SearchBar from 'components/UI/SearchBar/SearchBar';
-import WhatsAppEditor, { updatedValue } from 'components/UI/Form/WhatsAppEditor/WhatsAppEditor';
+import WhatsAppEditor from 'components/UI/Form/WhatsAppEditor/WhatsAppEditor';
import Tooltip from 'components/UI/Tooltip/Tooltip';
import { CREATE_MEDIA_MESSAGE, UPLOAD_MEDIA_BLOB } from 'graphql/mutations/Chat';
import { GET_ATTACHMENT_PERMISSION } from 'graphql/queries/Settings';
@@ -24,6 +22,15 @@ import { AddAttachment } from '../AddAttachment/AddAttachment';
import { VoiceRecorder } from '../VoiceRecorder/VoiceRecorder';
import ChatTemplates from '../ChatTemplates/ChatTemplates';
import styles from './ChatInput.module.css';
+import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
+import {
+ $createParagraphNode,
+ $createTextNode,
+ $getRoot,
+ $getSelection,
+ $isRangeSelection,
+ CLEAR_EDITOR_COMMAND,
+} from 'lexical';
export interface ChatInputProps {
onSendMessage(
@@ -34,7 +41,6 @@ export interface ChatInputProps {
variableParam: any,
interactiveTemplateId?: any
): any;
- handleHeightChange(newHeight: number): void;
contactStatus?: string;
contactBspStatus?: string;
additionalStyle?: any;
@@ -47,11 +53,10 @@ export const ChatInput = ({
contactBspStatus,
contactStatus,
additionalStyle,
- handleHeightChange,
isCollection,
lastMessageTime,
}: ChatInputProps) => {
- const [editorState, setEditorState] = useState(EditorState.createEmpty());
+ const [editorState, setEditorState] = useState('');
const [selectedTab, setSelectedTab] = useState('');
const [open, setOpen] = useState(false);
const [searchVal, setSearchVal] = useState('');
@@ -70,6 +75,8 @@ export const ChatInput = ({
const [showEmojiPicker, setShowEmojiPicker] = useState(false);
const { t } = useTranslation();
+ const [editor] = useLexicalComposerContext();
+
const speedSends = 'Speed sends';
const templates = 'Templates';
const interactiveMsg = 'Interactive msg';
@@ -79,10 +86,16 @@ export const ChatInput = ({
const resetVariable = () => {
setUpdatedEditorState(undefined);
- setEditorState(EditorState.createEmpty());
+ setEditorState('');
setSelectedTemplate(undefined);
setInteractiveMessageContent({});
setVariableParam([]);
+
+ // clear the editor state
+ editor.update(() => {
+ $getRoot().clear();
+ });
+ editor.focus();
};
const { data: permission } = useQuery(GET_ATTACHMENT_PERMISSION);
@@ -182,11 +195,8 @@ export const ChatInput = ({
}
// Resetting the EditorState
- setEditorState(
- EditorState.moveFocusToEnd(
- EditorState.push(editorState, ContentState.createFromText(''), 'remove-range')
- )
- );
+ editor.dispatchCommand(CLEAR_EDITOR_COMMAND, undefined);
+ editor.focus();
};
const emojiStyles = {
@@ -224,7 +234,6 @@ export const ChatInput = ({
const handleSelectText = (obj: any, isInteractiveMsg: boolean = false) => {
resetVariable();
-
// set selected template
let messageBody = obj.body;
@@ -236,7 +245,13 @@ export const ChatInput = ({
setSelectedTemplate(obj);
// Conversion from HTML text to EditorState
- setEditorState(getEditorFromContent(messageBody));
+ setEditorState(messageBody);
+ editor.update(() => {
+ const root = $getRoot();
+ const paragraph = $createParagraphNode();
+ paragraph.append($createTextNode(messageBody || ''));
+ root.append(paragraph);
+ });
// Add attachment if present
if (Object.prototype.hasOwnProperty.call(obj, 'MessageMedia') && obj.MessageMedia) {
@@ -262,7 +277,14 @@ export const ChatInput = ({
};
const updateEditorState = (body: string) => {
- setUpdatedEditorState(body);
+ setUpdatedEditorState(true);
+ editor.update(() => {
+ const root = $getRoot();
+ root.clear();
+ const paragraph = $createParagraphNode();
+ paragraph.append($createTextNode(body));
+ root.append(paragraph);
+ });
};
const variableParams = (params: Array) => {
@@ -370,6 +392,7 @@ export const ChatInput = ({
};
dialog = ;
}
+
return (
- setEditorState(updatedValue(emoji, editorState, true))
- }
+ onEmojiSelect={(emoji: any) => {
+ editor.update(() => {
+ const selection = $getSelection();
+ if ($isRangeSelection(selection)) {
+ selection.insertNodes([$createTextNode(emoji.native)]);
+ }
+ });
+ }}
displayStyle={emojiStyles}
/>
) : null}
@@ -484,18 +510,9 @@ export const ChatInput = ({
color="primary"
disableElevation
onClick={() => {
- if (updatedEditorState) {
- submitMessage(updatedEditorState);
- } else {
- submitMessage(getPlainTextFromEditor(editorState));
- }
+ submitMessage(editorState);
}}
- disabled={
- (!editorState.getCurrentContent().hasText() &&
- !attachmentAdded &&
- !recordedAudio) ||
- uploading
- }
+ disabled={(!editorState && !attachmentAdded && !recordedAudio) || uploading}
>
diff --git a/src/containers/Chat/ChatMessages/ChatMessages.module.css b/src/containers/Chat/ChatMessages/ChatMessages.module.css
index 7e79a149e..5ab7f88f9 100644
--- a/src/containers/Chat/ChatMessages/ChatMessages.module.css
+++ b/src/containers/Chat/ChatMessages/ChatMessages.module.css
@@ -4,6 +4,16 @@
}
}
+.ChatInput {
+ padding-top: 15px;
+ position: absolute;
+ bottom: 15px;
+ left: 0;
+ right: 0;
+ margin: auto;
+ max-width: 100% !important;
+}
+
.ChatMessages {
display: flex;
padding-left: 0px !important;
diff --git a/src/containers/Chat/ChatMessages/ChatMessages.test.tsx b/src/containers/Chat/ChatMessages/ChatMessages.test.tsx
index ef4720e63..ba8f6d0bf 100644
--- a/src/containers/Chat/ChatMessages/ChatMessages.test.tsx
+++ b/src/containers/Chat/ChatMessages/ChatMessages.test.tsx
@@ -1,3 +1,4 @@
+import 'mocks/matchMediaMock';
import { render, screen, act } from '@testing-library/react';
import { ApolloClient, ApolloProvider, InMemoryCache } from '@apollo/client';
import { fireEvent, waitFor } from '@testing-library/dom';
diff --git a/src/containers/Chat/ChatMessages/ChatMessages.tsx b/src/containers/Chat/ChatMessages/ChatMessages.tsx
index 39b4c69f0..d24839ccb 100644
--- a/src/containers/Chat/ChatMessages/ChatMessages.tsx
+++ b/src/containers/Chat/ChatMessages/ChatMessages.tsx
@@ -28,6 +28,7 @@ import {
import { getCachedConverations, updateConversationsCache } from '../../../services/ChatService';
import { addLogs, getDisplayName, isSimulator } from '../../../common/utils';
import { CollectionInformation } from '../../Collection/CollectionInformation/CollectionInformation';
+import { LexicalWrapper } from 'common/LexicalWrapper';
export interface ChatMessagesProps {
contactId?: number | string | null;
@@ -53,7 +54,6 @@ export const ChatMessages = ({ contactId, collectionId, startingHeight }: ChatMe
const [dialog, setDialogbox] = useState();
const [showDropdown, setShowDropdown] = useState(null);
- const [reducedHeight, setReducedHeight] = useState(0);
const [showLoadMore, setShowLoadMore] = useState(true);
const [scrolledToMessage, setScrolledToMessage] = useState(false);
const [showJumpToLatest, setShowJumpToLatest] = useState(false);
@@ -86,7 +86,7 @@ export const ChatMessages = ({ contactId, collectionId, startingHeight }: ChatMe
});
}
}, 1000);
- }, [setShowJumpToLatest, contactId, reducedHeight]);
+ }, [setShowJumpToLatest, contactId]);
const scrollToLatestMessage = () => {
const container: any = document.querySelector('.messageContainer');
@@ -511,10 +511,7 @@ export const ChatMessages = ({ contactId, collectionId, startingHeight }: ChatMe
const showDaySeparator = (currentDate: string, nextDate: string) => {
// if it's last message and its date is greater than current date then show day separator
- if (
- !nextDate &&
- dayjs(currentDate).format(ISO_DATE_FORMAT) < dayjs().format(ISO_DATE_FORMAT)
- ) {
+ if (!nextDate && dayjs(currentDate).format(ISO_DATE_FORMAT) < dayjs().format(ISO_DATE_FORMAT)) {
return true;
}
@@ -595,7 +592,7 @@ export const ChatMessages = ({ contactId, collectionId, startingHeight }: ChatMe
messageListContainer = (
@@ -628,10 +625,6 @@ export const ChatMessages = ({ contactId, collectionId, startingHeight }: ChatMe
);
}
- const handleHeightChange = (newHeight: number) => {
- setReducedHeight(newHeight);
- };
-
const handleChatClearedAction = () => {
const conversationInfoCopy = JSON.parse(JSON.stringify(conversationInfo));
conversationInfoCopy.messages = [];
@@ -652,50 +645,6 @@ export const ChatMessages = ({ contactId, collectionId, startingHeight }: ChatMe
);
}
- let topChatBar;
- let chatInputSection;
-
- if (contactId && conversationInfo.contact) {
- const displayName = getDisplayName(conversationInfo);
- topChatBar = (
- handleChatClearedAction()}
- />
- );
-
- chatInputSection = (
-
- );
- } else if (collectionId && conversationInfo.group) {
- topChatBar = (
-
- );
-
- chatInputSection = (
-
- );
- }
-
const showLatestMessage = () => {
setShowJumpToLatest(false);
@@ -739,6 +688,55 @@ export const ChatMessages = ({ contactId, collectionId, startingHeight }: ChatMe
);
+ let topChatBar;
+ let chatInputSection;
+
+ if (contactId && conversationInfo.contact) {
+ const displayName = getDisplayName(conversationInfo);
+ topChatBar = (
+ handleChatClearedAction()}
+ />
+ );
+
+ chatInputSection = (
+
+ {conversationInfo.messages.length && showJumpToLatest ? jumpToLatest : null}
+
+
+
+
+ );
+ } else if (collectionId && conversationInfo.group) {
+ topChatBar = (
+
+ );
+
+ chatInputSection = (
+
+ {conversationInfo.messages.length && showJumpToLatest ? jumpToLatest : null}
+
+
+
+
+ );
+ }
+
return (
{messageListContainer}
- {conversationInfo.messages.length && showJumpToLatest ? jumpToLatest : null}
{chatInputSection}
);
diff --git a/src/containers/Form/FormLayout.tsx b/src/containers/Form/FormLayout.tsx
index 9cd280ea9..4ad661f31 100644
--- a/src/containers/Form/FormLayout.tsx
+++ b/src/containers/Form/FormLayout.tsx
@@ -11,7 +11,6 @@ import { Dropdown } from 'components/UI/Form/Dropdown/Dropdown';
import { DialogBox } from 'components/UI/DialogBox/DialogBox';
import { Loading } from 'components/UI/Layout/Loading/Loading';
import { setNotification, setErrorMessage } from 'common/notification';
-import { getPlainTextFromEditor } from 'common/RichEditor';
import { SEARCH_QUERY_VARIABLES } from 'common/constants';
import { SEARCH_QUERY } from 'graphql/queries/Search';
import { USER_LANGUAGES } from 'graphql/queries/Organization';
@@ -377,6 +376,7 @@ export const FormLayout = ({
...itemData,
...defaultAttribute,
};
+
payload = languageSupport
? { ...payload, languageId: Number(languageIdValue) }
: { ...payload };
@@ -395,9 +395,6 @@ export const FormLayout = ({
if (field.additionalState) {
additionalState(payload[field.additionalState]);
}
- if (field.convertToWhatsApp && payload[field.name]) {
- payload[field.name] = getPlainTextFromEditor(payload[field.name]);
- }
if (field.skipPayload) {
delete payload[field.name];
}
diff --git a/src/containers/InteractiveMessage/InteractiveMessage.helper.ts b/src/containers/InteractiveMessage/InteractiveMessage.helper.ts
index fea220abb..a0bb19aab 100644
--- a/src/containers/InteractiveMessage/InteractiveMessage.helper.ts
+++ b/src/containers/InteractiveMessage/InteractiveMessage.helper.ts
@@ -1,6 +1,5 @@
import axios from 'axios';
import { LIST, LOCATION_REQUEST, QUICK_REPLY } from 'common/constants';
-import { getPlainTextFromEditor } from 'common/RichEditor';
import { FLOW_EDITOR_API } from 'config';
import { getAuthSession } from 'services/AuthService';
import * as Yup from 'yup';
@@ -38,13 +37,11 @@ export const validator = (templateType: any, t: any) => {
title: Yup.string()
.required(t('Title is required'))
.max(60, t('Title can be at most 60 characters')),
- body: Yup.string()
- .transform((_current, original) => original.getCurrentContent().getPlainText())
- .when('type', {
- is: (val: any) => val && val.id && val.id === 'DOCUMENT',
- then: (schema) => schema.nullable(),
- otherwise: (schema) => schema.required(t('Message content is required.')),
- }),
+ body: Yup.string().when('type', {
+ is: (val: any) => val && val.id && val.id === 'DOCUMENT',
+ then: (schema) => schema.nullable(),
+ otherwise: (schema) => schema.required(t('Message content is required.')),
+ }),
};
if (templateType === LIST) {
@@ -239,7 +236,7 @@ export const getVariableOptions = async (setContactVariables: any) => {
properties.properties
.map((i: any) => contactVariablesprefix.concat(i.key))
.concat(fields)
- .map((val: string) => ({ name: val }))
+ .map((val: string) => val)
.slice(1);
setContactVariables(contacts);
@@ -267,7 +264,7 @@ export const getPayloadByMediaType = (mediaType: string, payload: any) => {
break;
}
- result.text = getPlainTextFromEditor(payload.body);
+ result.text = payload.body;
result.caption = payload.footer;
return result;
diff --git a/src/containers/InteractiveMessage/InteractiveMessage.test.tsx b/src/containers/InteractiveMessage/InteractiveMessage.test.tsx
index 0d44d2d14..de3cfb59c 100644
--- a/src/containers/InteractiveMessage/InteractiveMessage.test.tsx
+++ b/src/containers/InteractiveMessage/InteractiveMessage.test.tsx
@@ -1,3 +1,4 @@
+import 'mocks/matchMediaMock';
import { render, screen, waitFor, fireEvent } from '@testing-library/react';
import { MockedProvider } from '@apollo/client/testing';
import axios from 'axios';
@@ -8,6 +9,16 @@ import { setUserSession } from 'services/AuthService';
import { mocks } from 'mocks/InteractiveMessage';
import { InteractiveMessage } from './InteractiveMessage';
import { FLOW_EDITOR_API } from 'config';
+import { userEvent } from '@testing-library/user-event';
+
+const mockIntersectionObserver = class {
+ constructor() {}
+ observe() {}
+ unobserve() {}
+ disconnect() {}
+};
+
+(window as any).IntersectionObserver = mockIntersectionObserver;
const mockUseLocationValue: any = {
pathname: '/',
@@ -99,21 +110,23 @@ test('it renders empty interactive form', async () => {
await waitFor(() => {
// Get all input elements
- const [title, quickReply1, quickReply2, , attachmentUrl] = screen.getAllByRole('textbox');
+ const [title, lexicalEditor, quickReply1, quickReply2, , attachmentUrl] =
+ screen.getAllByRole('textbox');
expect(title).toBeInTheDocument();
expect(quickReply1).toBeInTheDocument();
expect(quickReply2).toBeInTheDocument();
expect(attachmentUrl).toBeInTheDocument();
fireEvent.change(title, { target: { value: 'new title' } });
- fireEvent.blur(title);
+ userEvent.click(lexicalEditor);
+ userEvent.keyboard('Yes');
fireEvent.change(quickReply1, { target: { value: 'Yes' } });
fireEvent.change(quickReply2, { target: { value: 'No' } });
fireEvent.change(attachmentUrl, { target: { value: 'https://picsum.photos/200/300' } });
fireEvent.blur(attachmentUrl);
});
- // Changing language to marathi
+ // // Changing language to marathi
await waitFor(() => {
const language = screen.getByText('Marathi');
expect(language).toBeInTheDocument();
@@ -135,7 +148,8 @@ test('it renders empty interactive form', async () => {
await waitFor(() => {
// Adding list data
- const [, header, listTitle, listItemTitle, listItemDesc] = screen.getAllByRole('textbox');
+ const [, , header, listTitle, listItemTitle, listItemDesc] = screen.getAllByRole('textbox');
+
expect(header).toBeInTheDocument();
expect(listTitle).toBeInTheDocument();
expect(listItemTitle).toBeInTheDocument();
diff --git a/src/containers/InteractiveMessage/InteractiveMessage.tsx b/src/containers/InteractiveMessage/InteractiveMessage.tsx
index 713c94e71..a2bb6d371 100644
--- a/src/containers/InteractiveMessage/InteractiveMessage.tsx
+++ b/src/containers/InteractiveMessage/InteractiveMessage.tsx
@@ -1,7 +1,6 @@
import { useState, useEffect, useMemo } from 'react';
import * as Yup from 'yup';
import { useTranslation } from 'react-i18next';
-import { EditorState } from 'draft-js';
import { useLocation, useNavigate, useParams } from 'react-router-dom';
import { useLazyQuery, useQuery } from '@apollo/client';
import { setNotification } from 'common/notification';
@@ -24,7 +23,6 @@ import { LanguageBar } from 'components/UI/LanguageBar/LanguageBar';
import { LIST, LOCATION_REQUEST, MEDIA_MESSAGE_TYPES, QUICK_REPLY } from 'common/constants';
import { validateMedia } from 'common/utils';
import Loading from 'components/UI/Layout/Loading/Loading';
-import { getPlainTextFromEditor, getEditorFromContent } from 'common/RichEditor';
import { InteractiveOptions } from './InteractiveOptions/InteractiveOptions';
import styles from './InteractiveMessage.module.css';
import {
@@ -60,7 +58,7 @@ export const InteractiveMessage = () => {
const navigate = useNavigate();
const [title, setTitle] = useState('');
const [footer, setFooter] = useState('');
- const [body, setBody] = useState(EditorState.createEmpty());
+ const [body, setBody] = useState();
const [templateType, setTemplateType] = useState(QUICK_REPLY);
const [templateTypeField, setTemplateTypeField] = useState(templateTypeOptions[0]);
const [templateButtons, setTemplateButtons] = useState>([{ value: '' }]);
@@ -83,6 +81,11 @@ export const InteractiveMessage = () => {
const { t } = useTranslation();
const params = useParams();
+ let isEditing = false;
+ if (params?.id) {
+ isEditing = true;
+ }
+
const isLocationRequestType = templateType === LOCATION_REQUEST;
const { data: tag } = useQuery(GET_TAGS, {
@@ -161,7 +164,7 @@ export const InteractiveMessage = () => {
setTitle(data.title);
setFooter(data.footer);
- setBody(getEditorFromContent(data.body));
+ setBody(data.body);
setTemplateType(typeValue);
setTemplateTypeField(templateTypeOptions.find((option) => option.id === typeValue));
setTimeout(() => setTemplateButtons(data.templateButtons), 100);
@@ -230,7 +233,7 @@ export const InteractiveMessage = () => {
setTitle(titleText);
setFooter(data.footer);
- setBody(getEditorFromContent(data.body));
+ setBody(data.body);
setTemplateType(typeValue);
setTemplateTypeField(templateTypeOptions.find((option) => option.id === typeValue));
setTimeout(() => setTemplateButtons(data.templateButtons), 100);
@@ -449,7 +452,7 @@ export const InteractiveMessage = () => {
setNextLanguage(option);
const { values, errors } = form;
if (values.type?.label === 'TEXT') {
- if (values.title || values.body.getCurrentContent().getPlainText()) {
+ if (values.title || values.body) {
if (errors) {
setNotification(t('Please check the errors'), 'warning');
}
@@ -457,7 +460,7 @@ export const InteractiveMessage = () => {
handleLanguageChange(option);
}
}
- if (values.body.getCurrentContent().getPlainText()) {
+ if (values.body) {
if (Object.keys(errors).length !== 0) {
setNotification(t('Please check the errors'), 'warning');
}
@@ -530,6 +533,7 @@ export const InteractiveMessage = () => {
inputProp: {
suggestions: contactVariables,
},
+ isEditing: isEditing,
},
{
skip: templateType !== QUICK_REPLY,
@@ -615,7 +619,7 @@ export const InteractiveMessage = () => {
}
if (templateTypeVal === LIST) {
- const bodyText = getPlainTextFromEditor(payload.body);
+ const bodyText = payload.body;
const items = getTemplateButtonPayload(templateTypeVal, templateButtonVal);
const globalButtons = [{ type: 'text', title: globalButtonVal }];
@@ -627,7 +631,7 @@ export const InteractiveMessage = () => {
}
if (templateType === LOCATION_REQUEST) {
- const bodyText = getPlainTextFromEditor(payload.body);
+ const bodyText = payload.body;
const locationJson = {
type: 'location_request_message',
body: {
@@ -771,7 +775,7 @@ export const InteractiveMessage = () => {
const validationScheme = Yup.object().shape(validation, [['type', 'attachmentURL']]);
const getPreviewData = () => {
- const bodyText = getPlainTextFromEditor(body);
+ const bodyText = body;
if (!title && !bodyText && !footer) return null;
const payload = {
diff --git a/src/containers/Template/Form/HSM/HSM.test.tsx b/src/containers/Template/Form/HSM/HSM.test.tsx
index 083ea5a70..50d73488d 100644
--- a/src/containers/Template/Form/HSM/HSM.test.tsx
+++ b/src/containers/Template/Form/HSM/HSM.test.tsx
@@ -1,3 +1,4 @@
+import 'mocks/matchMediaMock';
import { render, waitFor, within, fireEvent } from '@testing-library/react';
import { MockedProvider } from '@apollo/client/testing';
import userEvent from '@testing-library/user-event';
diff --git a/src/containers/Template/Form/HSM/HSM.tsx b/src/containers/Template/Form/HSM/HSM.tsx
index c4cd7e7b5..f25d3c0c4 100644
--- a/src/containers/Template/Form/HSM/HSM.tsx
+++ b/src/containers/Template/Form/HSM/HSM.tsx
@@ -1,6 +1,5 @@
import { useState } from 'react';
import { useQuery } from '@apollo/client';
-import { EditorState } from 'draft-js';
import { useTranslation } from 'react-i18next';
import { useParams, useLocation } from 'react-router-dom';
import Loading from 'components/UI/Layout/Loading/Loading';
@@ -28,7 +27,7 @@ export const HSM = () => {
});
const [shortcode, setShortcode] = useState('');
- const [example, setExample] = useState(EditorState.createEmpty());
+ const [example, setExample] = useState();
const [category, setCategory] = useState({ label: '', id: '' });
const { t } = useTranslation();
const params = useParams();
@@ -109,6 +108,7 @@ export const HSM = () => {
getEditorValue: (value: any) => {
setExample(value);
},
+ isEditing: disabled,
},
{
component: AutoComplete,
diff --git a/src/containers/Template/Form/SpeedSend/SpeedSend.test.tsx b/src/containers/Template/Form/SpeedSend/SpeedSend.test.tsx
index b6ea7b29f..a89437286 100644
--- a/src/containers/Template/Form/SpeedSend/SpeedSend.test.tsx
+++ b/src/containers/Template/Form/SpeedSend/SpeedSend.test.tsx
@@ -1,3 +1,4 @@
+import 'mocks/matchMediaMock';
import { render, within, fireEvent, cleanup, waitFor } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import { MockedProvider } from '@apollo/client/testing';
@@ -6,13 +7,21 @@ import { SpeedSendList } from 'containers/Template/List/SpeedSendList/SpeedSendL
import { TEMPLATE_MOCKS } from 'containers/Template/Template.test.helper';
import { setUserSession } from 'services/AuthService';
import { SpeedSend } from './SpeedSend';
-
beforeEach(() => {
cleanup();
});
const mocks = TEMPLATE_MOCKS;
setUserSession(JSON.stringify({ roles: ['Admin'] }));
+const mockIntersectionObserver = class {
+ constructor() {}
+ observe() {}
+ unobserve() {}
+ disconnect() {}
+};
+
+(window as any).IntersectionObserver = mockIntersectionObserver;
+
describe('SpeedSend', () => {
test('cancel button should redirect to SpeedSendlist page', async () => {
const { container, getByText } = render(
diff --git a/src/containers/Template/Form/Template.test.tsx b/src/containers/Template/Form/Template.test.tsx
index 40b95b5ac..eb10ffa07 100644
--- a/src/containers/Template/Form/Template.test.tsx
+++ b/src/containers/Template/Form/Template.test.tsx
@@ -1,3 +1,4 @@
+import 'mocks/matchMediaMock';
import { render, waitFor, cleanup, fireEvent } from '@testing-library/react';
import { MockedProvider } from '@apollo/client/testing';
import { BrowserRouter } from 'react-router-dom';
diff --git a/src/containers/Template/Form/Template.tsx b/src/containers/Template/Form/Template.tsx
index d6de5992f..d81ad191c 100644
--- a/src/containers/Template/Form/Template.tsx
+++ b/src/containers/Template/Form/Template.tsx
@@ -1,7 +1,6 @@
import { useState, useEffect } from 'react';
import * as Yup from 'yup';
import { useLazyQuery, useMutation, useQuery } from '@apollo/client';
-import { EditorState } from 'draft-js';
import Typography from '@mui/material/Typography';
import { useNavigate, useLocation, useParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
@@ -24,7 +23,6 @@ import {
QUICK_REPLY,
VALID_URL_REGEX,
} from 'common/constants';
-import { getPlainTextFromEditor, getEditorFromContent } from 'common/RichEditor';
import Loading from 'components/UI/Layout/Loading/Loading';
import { CreateAutoComplete } from 'components/UI/Form/CreateAutoComplete/CreateAutoComplete';
import { validateMedia } from 'common/utils';
@@ -34,7 +32,6 @@ const regexForShortcode = /^[a-z0-9_]+$/g;
const HSMValidation = {
example: Yup.string()
- .transform((_current, original) => original.getCurrentContent().getPlainText())
.max(1024, 'Maximum 1024 characters are allowed')
.when('body', ([body], schema: any) =>
schema.test({
@@ -178,8 +175,8 @@ const Template = ({
const [tagId, setTagId] = useState(null);
const [label, setLabel] = useState('');
- const [body, setBody] = useState(EditorState.createEmpty());
- const [example, setExample] = useState(EditorState.createEmpty());
+ const [body, setBody] = useState('');
+ const [example, setExample] = useState('');
const [shortcode, setShortcode] = useState('');
const [language, setLanguageId] = useState({});
const [type, setType] = useState(null);
@@ -284,7 +281,7 @@ const Template = ({
setIsActive(isActiveValue);
if (typeof bodyValue === 'string') {
- setBody(getEditorFromContent(bodyValue));
+ setBody(bodyValue);
}
if (exampleValue) {
@@ -301,9 +298,8 @@ const Template = ({
} else {
exampleBody = exampleValue;
}
- const editorStateBody = getEditorFromContent(exampleValue);
- setExample(editorStateBody);
+ setExample(exampleValue);
onExampleChange(exampleBody);
}
@@ -325,7 +321,7 @@ const Template = ({
) {
const content = translationsCopy[currentLanguage];
setLabel(content.label);
- setBody(getEditorFromContent(content.body));
+ setBody(content.body);
}
setTranslations(translationsValue);
}
@@ -359,7 +355,7 @@ const Template = ({
setLabel(labelValue);
if (typeof bodyValue === 'string') {
- setBody(getEditorFromContent(bodyValue));
+ setBody(bodyValue);
}
if (typeValue && typeValue !== 'TEXT') {
@@ -497,7 +493,7 @@ const Template = ({
// Removing buttons when checkbox is checked or unchecked
useEffect(() => {
if (getExample) {
- const { message }: any = getTemplateAndButton(getPlainTextFromEditor(getExample));
+ const { message }: any = getTemplateAndButton(getExample);
onExampleChange(message || '');
}
}, [isAddButtonChecked]);
@@ -509,7 +505,7 @@ const Template = ({
const parsedText = parse.length ? `| ${parse.join(' | ')}` : null;
- const { message }: any = getTemplateAndButton(getPlainTextFromEditor(example));
+ const { message }: any = getTemplateAndButton(example);
const sampleText: any = parsedText && message + parsedText;
@@ -633,7 +629,7 @@ const Template = ({
const onLanguageChange = (option: string, form: any) => {
setNextLanguage(option);
const { values } = form;
- if (values.label || values.body.getCurrentContent().getPlainText()) {
+ if (values.label || values.body) {
return;
}
handleLanguageChange(option);
@@ -689,6 +685,7 @@ const Template = ({
getEditorValue: (value: any) => {
setBody(value);
},
+ isEditing: isEditing,
},
];
@@ -775,15 +772,15 @@ const Template = ({
}, []);
// get template body
- const templateBody = getTemplateAndButton(getPlainTextFromEditor(body));
- const templateExample = getTemplateAndButton(getPlainTextFromEditor(example));
+ const templateBody = getTemplateAndButton(body);
+ const templateExample = getTemplateAndButton(example);
return {
hasButtons: true,
buttons: JSON.stringify(buttons),
buttonType: templateType,
- body: getEditorFromContent(templateBody.message),
- example: getEditorFromContent(templateExample.message),
+ body: templateBody.message,
+ example: templateExample.message,
};
};
@@ -839,7 +836,7 @@ const Template = ({
status: 'approved',
languageId: language,
label: payloadCopy.label,
- body: getPlainTextFromEditor(payloadCopy.body),
+ body: payloadCopy.body,
MessageMedia: messageMedia,
...defaultAttribute,
};
@@ -909,7 +906,6 @@ const Template = ({
language: Yup.object().nullable().required('Language is required.'),
label: Yup.string().required(t('Title is required.')).max(50, t('Title length is too long.')),
body: Yup.string()
- .transform((current, original) => original.getCurrentContent().getPlainText())
.required(t('Message is required.'))
.max(1024, 'Maximum 1024 characters are allowed'),
type: Yup.object()
diff --git a/src/mocks/matchMediaMock.ts b/src/mocks/matchMediaMock.ts
new file mode 100644
index 000000000..eb6eb6524
--- /dev/null
+++ b/src/mocks/matchMediaMock.ts
@@ -0,0 +1,13 @@
+Object.defineProperty(window, 'matchMedia', {
+ writable: true,
+ value: vi.fn().mockImplementation((query) => ({
+ matches: false,
+ media: query,
+ onchange: null,
+ addListener: vi.fn(),
+ removeListener: vi.fn(),
+ addEventListener: vi.fn(),
+ removeEventListener: vi.fn(),
+ dispatchEvent: vi.fn(),
+ })),
+});
diff --git a/src/routes/AuthenticatedRoute/AuthenticatedRoute.test.tsx b/src/routes/AuthenticatedRoute/AuthenticatedRoute.test.tsx
index 6a6aeda99..2ea4021d8 100644
--- a/src/routes/AuthenticatedRoute/AuthenticatedRoute.test.tsx
+++ b/src/routes/AuthenticatedRoute/AuthenticatedRoute.test.tsx
@@ -1,3 +1,4 @@
+import 'mocks/matchMediaMock';
import { Suspense } from 'react';
import { render, waitFor } from '@testing-library/react';
import { BrowserRouter } from 'react-router-dom';
diff --git a/yarn.lock b/yarn.lock
index c7d2a9d05..be3c73660 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -351,28 +351,6 @@
dependencies:
"@date-io/core" "^3.0.0"
-"@draft-js-plugins/editor@^4.1.4":
- version "4.1.4"
- resolved "https://registry.yarnpkg.com/@draft-js-plugins/editor/-/editor-4.1.4.tgz#456e01b032b8ef44f0c77ea777aa3c44172de892"
- integrity sha512-NlE1AIsPPfmdn+JIwwmcAm18FgwJ9/A55+2VXf3US3PmITJVL+y9VORCwLbGh2sb0RXvgFOIbqs8pPAOe8F8WQ==
- dependencies:
- immutable "~3.7.4"
- prop-types "^15.8.1"
-
-"@draft-js-plugins/mention@^5.2.2":
- version "5.2.2"
- resolved "https://registry.yarnpkg.com/@draft-js-plugins/mention/-/mention-5.2.2.tgz#3ed6fe248fc931f28ba494a6887fc156b90b6059"
- integrity sha512-CoympO4FTBHD11mb+lSdD2KvtvwvHeUl+YDUymgtQBncZI4TKNKwZji60JdfNoGFQzDu87QkwKzv3iG8XQmNzA==
- dependencies:
- "@popperjs/core" "^2.11.8"
- "@types/lodash" "^4.14.195"
- clsx "^1.2.1"
- immutable "~3.7.4"
- lodash "^4.17.21"
- lodash-es "^4.17.21"
- prop-types "^15.8.1"
- react-popper "^2.3.0"
-
"@emoji-mart/data@^1.1.2":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@emoji-mart/data/-/data-1.1.2.tgz#777c976f8f143df47cbb23a7077c9ca9fe5fc513"
@@ -757,6 +735,161 @@
"@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14"
+"@lexical/clipboard@0.13.1":
+ version "0.13.1"
+ resolved "https://registry.yarnpkg.com/@lexical/clipboard/-/clipboard-0.13.1.tgz#ca132306129974ea2c9e51d6a8637f8fcffcdb3d"
+ integrity sha512-gMSbVeqb7S+XAi/EMMlwl+FCurLPugN2jAXcp5k5ZaUd7be8B+iupbYdoKkjt4qBhxmvmfe9k46GoC0QOPl/nw==
+ dependencies:
+ "@lexical/html" "0.13.1"
+ "@lexical/list" "0.13.1"
+ "@lexical/selection" "0.13.1"
+ "@lexical/utils" "0.13.1"
+
+"@lexical/code@0.13.1":
+ version "0.13.1"
+ resolved "https://registry.yarnpkg.com/@lexical/code/-/code-0.13.1.tgz#e13688390582a4b63a639daff1f16bcb82aa854d"
+ integrity sha512-QK77r3QgEtJy96ahYXNgpve8EY64BQgBSnPDOuqVrLdl92nPzjqzlsko2OZldlrt7gjXcfl9nqfhZ/CAhStfOg==
+ dependencies:
+ "@lexical/utils" "0.13.1"
+ prismjs "^1.27.0"
+
+"@lexical/dragon@0.13.1":
+ version "0.13.1"
+ resolved "https://registry.yarnpkg.com/@lexical/dragon/-/dragon-0.13.1.tgz#32ba02bff4d8f02a6317d874671ee0b0a2dcdc53"
+ integrity sha512-aNlqfif4//jW7gOxbBgdrbDovU6m3EwQrUw+Y/vqRkY+sWmloyAUeNwCPH1QP3Q5cvfolzOeN5igfBljsFr+1g==
+
+"@lexical/hashtag@0.13.1":
+ version "0.13.1"
+ resolved "https://registry.yarnpkg.com/@lexical/hashtag/-/hashtag-0.13.1.tgz#eb273c199a0115ec0f0191c2449e97f512360f2e"
+ integrity sha512-Dl0dUG4ZXNjYYuAUR0GMGpLGsA+cps2/ln3xEmy28bZR0sKkjXugsu2QOIxZjYIPBewDrXzPcvK8md45cMYoSg==
+ dependencies:
+ "@lexical/utils" "0.13.1"
+
+"@lexical/history@0.13.1":
+ version "0.13.1"
+ resolved "https://registry.yarnpkg.com/@lexical/history/-/history-0.13.1.tgz#3bb54716dc69779d3b35894bd72637a7fc2ed284"
+ integrity sha512-cZXt30MalEEiRaflE9tHeGYnwT1xSDjXLsf9M409DSU9POJyZ1fsULJrG1tWv2uFQOhwal33rve9+MatUlITrg==
+ dependencies:
+ "@lexical/utils" "0.13.1"
+
+"@lexical/html@0.13.1":
+ version "0.13.1"
+ resolved "https://registry.yarnpkg.com/@lexical/html/-/html-0.13.1.tgz#e56035d0c6528ffb932390e0d3d357c82f69253a"
+ integrity sha512-XkZrnCSHIUavtpMol6aG8YsJ5KqC9hMxEhAENf3HTGi3ocysCByyXOyt1EhEYpjJvgDG4wRqt25xGDbLjj1/sA==
+ dependencies:
+ "@lexical/selection" "0.13.1"
+ "@lexical/utils" "0.13.1"
+
+"@lexical/link@0.13.1":
+ version "0.13.1"
+ resolved "https://registry.yarnpkg.com/@lexical/link/-/link-0.13.1.tgz#f1c4c12c828c0251e5d7fb4fb336f2d62380fc57"
+ integrity sha512-7E3B2juL2UoMj2n+CiyFZ7tlpsdViAoIE7MpegXwfe/VQ66wFwk/VxGTa/69ng2EoF7E0kh+SldvGQDrWAWb1g==
+ dependencies:
+ "@lexical/utils" "0.13.1"
+
+"@lexical/list@0.13.1":
+ version "0.13.1"
+ resolved "https://registry.yarnpkg.com/@lexical/list/-/list-0.13.1.tgz#461cb989157bdf4a43eaa8596fdb09df60d114ee"
+ integrity sha512-6U1pmNZcKLuOWiWRML8Raf9zSEuUCMlsOye82niyF6I0rpPgYo5UFghAAbGISDsyqzM1B2L4BgJ6XrCk/dJptg==
+ dependencies:
+ "@lexical/utils" "0.13.1"
+
+"@lexical/mark@0.13.1":
+ version "0.13.1"
+ resolved "https://registry.yarnpkg.com/@lexical/mark/-/mark-0.13.1.tgz#084bb49a8bc1c5c5a4ed5c5d4a20c98ea85ec8b1"
+ integrity sha512-dW27PW8wWDOKFqXTBUuUfV+umU0KfwvXGkPUAxRJrvwUWk5RKaS48LhgbNlQ5BfT84Q8dSiQzvbaa6T40t9a3A==
+ dependencies:
+ "@lexical/utils" "0.13.1"
+
+"@lexical/markdown@0.13.1":
+ version "0.13.1"
+ resolved "https://registry.yarnpkg.com/@lexical/markdown/-/markdown-0.13.1.tgz#1fd2efcacff4ce733682a8161a3f3d78dba37503"
+ integrity sha512-6tbdme2h5Zy/M88loVQVH5G0Nt7VMR9UUkyiSaicyBRDOU2OHacaXEp+KSS/XuF+d7TA+v/SzyDq8HS77cO1wA==
+ dependencies:
+ "@lexical/code" "0.13.1"
+ "@lexical/link" "0.13.1"
+ "@lexical/list" "0.13.1"
+ "@lexical/rich-text" "0.13.1"
+ "@lexical/text" "0.13.1"
+ "@lexical/utils" "0.13.1"
+
+"@lexical/offset@0.13.1":
+ version "0.13.1"
+ resolved "https://registry.yarnpkg.com/@lexical/offset/-/offset-0.13.1.tgz#f37417822aef3dc81580d4abb96e43ba9d547225"
+ integrity sha512-j/RZcztJ7dyTrfA2+C3yXDzWDXV+XmMpD5BYeQCEApaHvlo20PHt1BISk7RcrnQW8PdzGvpKblRWf//c08LS9w==
+
+"@lexical/overflow@0.13.1":
+ version "0.13.1"
+ resolved "https://registry.yarnpkg.com/@lexical/overflow/-/overflow-0.13.1.tgz#42c036dc3ad3eb929fda5aa0a00a725b74f72669"
+ integrity sha512-Uw34j+qG2UJRCIR+bykfFMduFk7Pc4r/kNt8N1rjxGuGXAsreTVch1iOhu7Ev6tJgkURsduKuaJCAi7iHnKl7g==
+
+"@lexical/plain-text@0.13.1":
+ version "0.13.1"
+ resolved "https://registry.yarnpkg.com/@lexical/plain-text/-/plain-text-0.13.1.tgz#e7e713029443c30facce27b34836bf604cf92c0f"
+ integrity sha512-4j5KAsMKUvJ8LhVDSS4zczbYXzdfmgYSAVhmqpSnJtud425Nk0TAfpUBLFoivxZB7KMoT1LGWQZvd47IvJPvtA==
+
+"@lexical/react@^0.13.1":
+ version "0.13.1"
+ resolved "https://registry.yarnpkg.com/@lexical/react/-/react-0.13.1.tgz#6c35bf43e24560d2ca3aa2c6ff607ef37de87bac"
+ integrity sha512-Sy6EL230KAb0RZsZf1dZrRrc3+rvCDQWltcd8C/cqBUYlxsLYCW9s4f3RB2werngD/PtLYbBB48SYXNkIALITA==
+ dependencies:
+ "@lexical/clipboard" "0.13.1"
+ "@lexical/code" "0.13.1"
+ "@lexical/dragon" "0.13.1"
+ "@lexical/hashtag" "0.13.1"
+ "@lexical/history" "0.13.1"
+ "@lexical/link" "0.13.1"
+ "@lexical/list" "0.13.1"
+ "@lexical/mark" "0.13.1"
+ "@lexical/markdown" "0.13.1"
+ "@lexical/overflow" "0.13.1"
+ "@lexical/plain-text" "0.13.1"
+ "@lexical/rich-text" "0.13.1"
+ "@lexical/selection" "0.13.1"
+ "@lexical/table" "0.13.1"
+ "@lexical/text" "0.13.1"
+ "@lexical/utils" "0.13.1"
+ "@lexical/yjs" "0.13.1"
+ react-error-boundary "^3.1.4"
+
+"@lexical/rich-text@0.13.1":
+ version "0.13.1"
+ resolved "https://registry.yarnpkg.com/@lexical/rich-text/-/rich-text-0.13.1.tgz#8251e81a3985a4d76bef027cf6c0dc90c661e4ec"
+ integrity sha512-HliB9Ync06mv9DBg/5j0lIsTJp+exLHlaLJe+n8Zq1QNTzZzu2LsIT/Crquk50In7K/cjtlaQ/d5RB0LkjMHYg==
+
+"@lexical/selection@0.13.1":
+ version "0.13.1"
+ resolved "https://registry.yarnpkg.com/@lexical/selection/-/selection-0.13.1.tgz#466d7cd0ee1b04680bd949112f1f5cb6a6618efa"
+ integrity sha512-Kt9eSwjxPznj7yzIYipu9yYEgmRJhHiq3DNxHRxInYcZJWWNNHum2xKyxwwcN8QYBBzgfPegfM/geqQEJSV1lQ==
+
+"@lexical/table@0.13.1":
+ version "0.13.1"
+ resolved "https://registry.yarnpkg.com/@lexical/table/-/table-0.13.1.tgz#814d3b8a2afb821aff151c92cce831809f9d67a1"
+ integrity sha512-VQzgkfkEmnvn6C64O/kvl0HI3bFoBh3WA/U67ALw+DS11Mb5CKjbt0Gzm/258/reIxNMpshjjicpWMv9Miwauw==
+ dependencies:
+ "@lexical/utils" "0.13.1"
+
+"@lexical/text@0.13.1":
+ version "0.13.1"
+ resolved "https://registry.yarnpkg.com/@lexical/text/-/text-0.13.1.tgz#12104d42da7a707a19853679f3a88e8ed6ce8084"
+ integrity sha512-NYy3TZKt3qzReDwN2Rr5RxyFlg84JjXP2JQGMrXSSN7wYe73ysQIU6PqdVrz4iZkP+w34F3pl55dJ24ei3An9w==
+
+"@lexical/utils@0.13.1":
+ version "0.13.1"
+ resolved "https://registry.yarnpkg.com/@lexical/utils/-/utils-0.13.1.tgz#f2a72f71c859933781294830b38b25b5b33122a9"
+ integrity sha512-AtQQKzYymkbOaQxaBXjRBS8IPxF9zWQnqwHTUTrJqJ4hX71aIQd/thqZbfQETAFJfC8pNBZw5zpxN6yPHk23dQ==
+ dependencies:
+ "@lexical/list" "0.13.1"
+ "@lexical/selection" "0.13.1"
+ "@lexical/table" "0.13.1"
+
+"@lexical/yjs@0.13.1":
+ version "0.13.1"
+ resolved "https://registry.yarnpkg.com/@lexical/yjs/-/yjs-0.13.1.tgz#2a71ae3c4b3cc5c660bbe66d537eb0cbf3c7c1b6"
+ integrity sha512-4GbqQM+PwNTV59AZoNrfTe/0rLjs+cX6Y6yAdZSRPBwr5L3JzYeU1TTcFCVQTtsE7KF8ddVP8sD7w9pi8rOWLA==
+ dependencies:
+ "@lexical/offset" "0.13.1"
+
"@mui/base@5.0.0-beta.33":
version "5.0.0-beta.33"
resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-beta.33.tgz#fbb844e2d840d47dd7a48850a03152aed2381d10"
@@ -1299,11 +1432,6 @@
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==
-"@types/lodash@^4.14.195":
- version "4.14.202"
- resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.202.tgz#f09dbd2fb082d507178b2f2a5c7e74bd72ff98f8"
- integrity sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==
-
"@types/minimatch@^3.0.3":
version "3.0.5"
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40"
@@ -1735,11 +1863,6 @@ arraybuffer.prototype.slice@^1.0.2:
is-array-buffer "^3.0.2"
is-shared-array-buffer "^1.0.2"
-asap@~2.0.3:
- version "2.0.6"
- resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
- integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==
-
assertion-error@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b"
@@ -2055,7 +2178,7 @@ clone@^2.1.2:
resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f"
integrity sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==
-clsx@^1.1.1, clsx@^1.2.1:
+clsx@^1.1.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12"
integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==
@@ -2161,11 +2284,6 @@ convert-source-map@^2.0.0:
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a"
integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==
-core-js@^3.6.4:
- version "3.34.0"
- resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.34.0.tgz#5705e6ad5982678612e96987d05b27c6c7c274a5"
- integrity sha512-aDdvlDder8QmY91H88GzNi9EtQi2TjvQhpCX6B1v/dAZHU1AuLgHvRh54RiOerpEhEW46Tkf+vgAViB/CWC0ag==
-
core-util-is@~1.0.0:
version "1.0.3"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85"
@@ -2197,13 +2315,6 @@ create-require@^1.1.0:
resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333"
integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==
-cross-fetch@^3.0.4:
- version "3.1.8"
- resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.8.tgz#0327eba65fd68a7d119f8fb2bf9334a1a7956f82"
- integrity sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==
- dependencies:
- node-fetch "^2.6.12"
-
cross-spawn@^7.0.3:
version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
@@ -2442,15 +2553,6 @@ dot-case@^3.0.4:
no-case "^3.0.4"
tslib "^2.0.3"
-draft-js@^0.11.7:
- version "0.11.7"
- resolved "https://registry.yarnpkg.com/draft-js/-/draft-js-0.11.7.tgz#be293aaa255c46d8a6647f3860aa4c178484a206"
- integrity sha512-ne7yFfN4sEL82QPQEn80xnADR8/Q6ALVworbC5UOSzOvjffmYfFsr3xSZtxbIirti14R7Y33EZC5rivpLgIbsg==
- dependencies:
- fbjs "^2.0.0"
- immutable "~3.7.4"
- object-assign "^4.1.1"
-
duplexify@^4.1.1:
version "4.1.2"
resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-4.1.2.tgz#18b4f8d28289132fa0b9573c898d9f903f81c7b0"
@@ -2942,25 +3044,6 @@ fastq@^1.13.0, fastq@^1.6.0:
dependencies:
reusify "^1.0.4"
-fbjs-css-vars@^1.0.0:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz#216551136ae02fe255932c3ec8775f18e2c078b8"
- integrity sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==
-
-fbjs@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-2.0.0.tgz#01fb812138d7e31831ed3e374afe27b9169ef442"
- integrity sha512-8XA8ny9ifxrAWlyhAbexXcs3rRMtxWcs3M0lctLfB49jRDHiaxj+Mo0XxbwE7nKZYzgCFoq64FS+WFd4IycPPQ==
- dependencies:
- core-js "^3.6.4"
- cross-fetch "^3.0.4"
- fbjs-css-vars "^1.0.0"
- loose-envify "^1.0.0"
- object-assign "^4.1.0"
- promise "^7.1.1"
- setimmediate "^1.0.5"
- ua-parser-js "^0.7.18"
-
fflate@^0.8.1:
version "0.8.1"
resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.8.1.tgz#1ed92270674d2ad3c73f077cd0acf26486dae6c9"
@@ -3909,6 +3992,16 @@ lead@^4.0.0:
resolved "https://registry.yarnpkg.com/lead/-/lead-4.0.0.tgz#5317a49effb0e7ec3a0c8fb9c1b24fb716aab939"
integrity sha512-DpMa59o5uGUWWjruMp71e6knmwKU3jRBBn1kjuLWN9EeIOxNeSAwvHf03WIl8g/ZMR2oSQC9ej3yeLBwdDc/pg==
+lexical-beautiful-mentions@^0.1.30:
+ version "0.1.30"
+ resolved "https://registry.yarnpkg.com/lexical-beautiful-mentions/-/lexical-beautiful-mentions-0.1.30.tgz#9b541115c690b9b43ed55441ed133d8e928bd253"
+ integrity sha512-2Hi8T8f4+kc/iCnVeglgl9spyEEuoi7gGQgeTQo3tA6wyPsA5HWPSg4yk39YtTE7kkucwzG3j8gqpCuerd3Lsg==
+
+lexical@^0.13.1:
+ version "0.13.1"
+ resolved "https://registry.yarnpkg.com/lexical/-/lexical-0.13.1.tgz#0abffe9bc05a7a9da8a6128ea478bf08c11654db"
+ integrity sha512-jaqRYzVEfBKbX4FwYpd/g+MyOjRaraAel0iQsTrwvx3hyN0bswUZuzb6H6nGlFSjcdrc77wKpyKwoWj4aUd+Bw==
+
lilconfig@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.0.0.tgz#f8067feb033b5b74dab4602a5f5029420be749bc"
@@ -3972,7 +4065,7 @@ logflare-transport-core@^0.3.3:
resolved "https://registry.yarnpkg.com/logflare-transport-core/-/logflare-transport-core-0.3.3.tgz#dade63f6b414b7d01727825ce0ccb830ed6f0f7e"
integrity sha512-n82NsRVWvlaa3jd9QQ8rDroCjCJcIamQOlarLDBou9RsF0QaRv39rduy0ToPmlGQn1OPZBwlsv+R36lXupSmVQ==
-loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0:
+loose-envify@^1.1.0, loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
@@ -4192,7 +4285,7 @@ no-case@^3.0.4:
lower-case "^2.0.2"
tslib "^2.0.3"
-node-fetch@^2.6.1, node-fetch@^2.6.12:
+node-fetch@^2.6.1:
version "2.7.0"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d"
integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==
@@ -4548,6 +4641,11 @@ pretty-format@^29.0.0, pretty-format@^29.7.0:
ansi-styles "^5.0.0"
react-is "^18.0.0"
+prismjs@^1.27.0:
+ version "1.29.0"
+ resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.29.0.tgz#f113555a8fa9b57c35e637bba27509dcf802dd12"
+ integrity sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==
+
process-nextick-args@~2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
@@ -4573,13 +4671,6 @@ promise-map-series@^0.3.0:
resolved "https://registry.yarnpkg.com/promise-map-series/-/promise-map-series-0.3.0.tgz#41873ca3652bb7a042b387d538552da9b576f8a1"
integrity sha512-3npG2NGhTc8BWBolLLf8l/92OxMGaRLbqvIh9wjCHhDXNvk4zsxaTaCpiCunW09qWPrN2zeNSNwRLVBrQQtutA==
-promise@^7.1.1:
- version "7.3.1"
- resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf"
- integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==
- dependencies:
- asap "~2.0.3"
-
prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
version "15.8.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
@@ -4681,6 +4772,13 @@ react-draggable@^4.4.6:
clsx "^1.1.1"
prop-types "^15.8.1"
+react-error-boundary@^3.1.4:
+ version "3.1.4"
+ resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-3.1.4.tgz#255db92b23197108757a888b01e5b729919abde0"
+ integrity sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==
+ dependencies:
+ "@babel/runtime" "^7.12.5"
+
react-fast-compare@^2.0.1:
version "2.0.4"
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9"
@@ -4762,14 +4860,6 @@ react-player@^2.14.1:
prop-types "^15.7.2"
react-fast-compare "^3.0.1"
-react-popper@^2.3.0:
- version "2.3.0"
- resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-2.3.0.tgz#17891c620e1320dce318bad9fede46a5f71c70ba"
- integrity sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==
- dependencies:
- react-fast-compare "^3.0.1"
- warning "^4.0.2"
-
react-refresh@^0.14.0:
version "0.14.0"
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.0.tgz#4e02825378a5f227079554d4284889354e5f553e"
@@ -5182,11 +5272,6 @@ set-function-name@^2.0.0, set-function-name@^2.0.1:
functions-have-names "^1.2.3"
has-property-descriptors "^1.0.0"
-setimmediate@^1.0.5:
- version "1.0.5"
- resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
- integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==
-
shebang-command@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
@@ -5733,11 +5818,6 @@ typescript@^5.0.4, typescript@^5.3.3:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37"
integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==
-ua-parser-js@^0.7.18:
- version "0.7.37"
- resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.37.tgz#e464e66dac2d33a7a1251d7d7a99d6157ec27832"
- integrity sha512-xV8kqRKM+jhMvcHWUKthV9fNebIzrNy//2O9ZwWcfiBFR5f25XVZPLlEajk/sf3Ra15V92isyQqnIEXRDaZWEA==
-
ufo@^1.3.0:
version "1.3.2"
resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.3.2.tgz#c7d719d0628a1c80c006d2240e0d169f6e3c0496"
@@ -6068,13 +6148,6 @@ walk-sync@^2.2.0:
matcher-collection "^2.0.0"
minimatch "^3.0.4"
-warning@^4.0.2:
- version "4.0.3"
- resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3"
- integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==
- dependencies:
- loose-envify "^1.0.0"
-
webidl-conversions@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"