Use slate.js as a rich text editor for chat messages

This commit is contained in:
Gabe Kangas 2022-05-05 13:52:10 -07:00
parent f96bde4f71
commit 66a55401a8
No known key found for this signature in database
GPG key ID: 9A56337728BC81EA
3 changed files with 280 additions and 31 deletions

View file

@ -1,27 +1,101 @@
import { SmileOutlined } from '@ant-design/icons'; import { SmileOutlined } from '@ant-design/icons';
import { Button, Input } from 'antd'; import { Button, Input } from 'antd';
import React, { useRef, useState } from 'react'; import React, { useState, useMemo } from 'react';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import ContentEditable from 'react-contenteditable'; import { Transforms, createEditor, Node, BaseEditor, Text } from 'slate';
import { Slate, Editable, withReact, ReactEditor } from 'slate-react';
import WebsocketService from '../../../services/websocket-service'; import WebsocketService from '../../../services/websocket-service';
import { websocketServiceAtom } from '../../stores/ClientConfigStore'; import { websocketServiceAtom } from '../../stores/ClientConfigStore';
import { getCaretPosition, convertToText, convertOnPaste } from '../chat'; import { getCaretPosition, convertToText, convertOnPaste } from '../chat';
import { MessageType } from '../../../interfaces/socket-events'; import { MessageType } from '../../../interfaces/socket-events';
import s from './ChatTextField.module.scss'; import s from './ChatTextField.module.scss';
type CustomElement = { type: 'paragraph'; children: CustomText[] };
type CustomText = { text: string };
declare module 'slate' {
interface CustomTypes {
Editor: BaseEditor & ReactEditor;
Element: CustomElement;
Text: CustomText;
}
}
interface Props { interface Props {
value?: string; value?: string;
} }
const Image = ({ element }) => (
<img
src={element.url}
alt="emoji"
style={{ display: 'inline', position: 'relative', width: '30px', bottom: '10px' }}
/>
);
const insertImage = (editor, url) => {
const text = { text: '' };
const image: ImageElement = { type: 'image', url, children: [text] };
Transforms.insertNodes(editor, image);
};
const withImages = editor => {
const { isVoid } = editor;
editor.isVoid = element => (element.type === 'image' ? true : isVoid(element));
editor.isInline = element => element.type === 'image';
return editor;
};
export type EmptyText = {
text: string;
};
type ImageElement = {
type: 'image';
url: string;
children: EmptyText[];
};
const Element = props => {
const { attributes, children, element } = props;
switch (element.type) {
case 'image':
return <Image {...props} />;
default:
return <p {...attributes}>{children}</p>;
}
};
const serialize = node => {
if (Text.isText(node)) {
let string = node.text; // escapeHtml(node.text);
if (node.bold) {
string = `<strong>${string}</strong>`;
}
return string;
}
const children = node.children.map(n => serialize(n)).join('');
switch (node.type) {
case 'paragraph':
return `<p>${children}</p>`;
case 'image':
return `<img src="${node.url}" alt="emoji" />`;
default:
return children;
}
};
export default function ChatTextField(props: Props) { export default function ChatTextField(props: Props) {
const { value: originalValue } = props; const { value: originalValue } = props;
const [value, setValue] = useState(originalValue);
const [showEmojis, setShowEmojis] = useState(false); const [showEmojis, setShowEmojis] = useState(false);
const websocketService = useRecoilValue<WebsocketService>(websocketServiceAtom); const websocketService = useRecoilValue<WebsocketService>(websocketServiceAtom);
const [editor] = useState(() => withImages(withReact(createEditor())));
const text = useRef(value);
// large is 40px
const size = 'small'; const size = 'small';
const sendMessage = () => { const sendMessage = () => {
@ -30,41 +104,46 @@ export default function ChatTextField(props: Props) {
return; return;
} }
const message = convertToText(value); const message = serialize(editor);
websocketService.send({ type: MessageType.CHAT, body: message }); websocketService.send({ type: MessageType.CHAT, body: message });
setValue('');
// Clear the editor.
Transforms.select(editor, [0, editor.children.length - 1]);
Transforms.delete(editor);
}; };
const handleChange = evt => { const handleChange = e => {};
text.current = evt.target.value;
setValue(evt.target.value);
};
const handleKeyDown = event => { const onKeyDown = e => {
const key = event && event.key; if (e.key === 'Enter') {
e.preventDefault();
if (key === 'Enter') { sendMessage();
} }
}; };
const initialValue = [
{
type: 'paragraph',
children: [{ text: originalValue }],
},
];
return ( return (
<div> <div>
<Input.Group compact style={{ display: 'flex', width: '100%', position: 'absolute' }}> <Slate editor={editor} value={initialValue} onChange={handleChange}>
<ContentEditable <Editable
style={{ width: '60%', maxHeight: '50px', padding: '5px' }} onKeyDown={onKeyDown}
html={text.current} renderElement={props => <Element {...props} />}
onChange={handleChange} placeholder="Chat message goes here..."
onKeyDown={e => {
handleKeyDown(e);
}}
/> />
<Button type="default" ghost title="Emoji" onClick={() => setShowEmojis(!showEmojis)}> </Slate>
<SmileOutlined style={{ color: 'rgba(0,0,0,.45)' }} /> <Button type="default" ghost title="Emoji" onClick={() => setShowEmojis(!showEmojis)}>
</Button> <SmileOutlined style={{ color: 'rgba(0,0,0,.45)' }} />
<Button size={size} type="primary" onClick={sendMessage}> </Button>
Submit <Button size={size} type="primary" onClick={sendMessage}>
</Button> Submit
</Input.Group> </Button>
</div> </div>
); );
} }

168
web/package-lock.json generated
View file

@ -33,6 +33,8 @@
"react-markdown-editor-lite": "1.3.2", "react-markdown-editor-lite": "1.3.2",
"react-virtuoso": "^2.10.2", "react-virtuoso": "^2.10.2",
"recoil": "^0.7.2", "recoil": "^0.7.2",
"slate": "^0.78.0",
"slate-react": "^0.79.0",
"ua-parser-js": "1.0.2", "ua-parser-js": "1.0.2",
"video.js": "^7.18.1" "video.js": "^7.18.1"
}, },
@ -10813,6 +10815,11 @@
"resolved": "https://registry.npmjs.org/@types/is-function/-/is-function-1.0.1.tgz", "resolved": "https://registry.npmjs.org/@types/is-function/-/is-function-1.0.1.tgz",
"integrity": "sha512-A79HEEiwXTFtfY+Bcbo58M2GRYzCr9itHWzbzHVFNEYCcoU/MMGwYYf721gBrnhpj1s6RGVVha/IgNFnR0Iw/Q==" "integrity": "sha512-A79HEEiwXTFtfY+Bcbo58M2GRYzCr9itHWzbzHVFNEYCcoU/MMGwYYf721gBrnhpj1s6RGVVha/IgNFnR0Iw/Q=="
}, },
"node_modules/@types/is-hotkey": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/@types/is-hotkey/-/is-hotkey-0.1.7.tgz",
"integrity": "sha512-yB5C7zcOM7idwYZZ1wKQ3pTfjA9BbvFqRWvKB46GFddxnJtHwi/b9y84ykQtxQPg5qhdpg4Q/kWU3EGoCTmLzQ=="
},
"node_modules/@types/istanbul-lib-coverage": { "node_modules/@types/istanbul-lib-coverage": {
"version": "2.0.4", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz",
@ -10859,6 +10866,11 @@
"integrity": "sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==", "integrity": "sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==",
"dev": true "dev": true
}, },
"node_modules/@types/lodash": {
"version": "4.14.182",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz",
"integrity": "sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q=="
},
"node_modules/@types/markdown-it": { "node_modules/@types/markdown-it": {
"version": "12.2.3", "version": "12.2.3",
"resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz",
@ -15461,6 +15473,18 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/direction": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/direction/-/direction-1.0.4.tgz",
"integrity": "sha512-GYqKi1aH7PJXxdhTeZBFrg8vUBeKXi+cNprXsC1kpJcbcVnV9wBsrOu1cQEdG0WeQwlfHiy3XvnKfIrJ2R0NzQ==",
"bin": {
"direction": "cli.js"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/doctrine": { "node_modules/doctrine": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
@ -19065,6 +19089,15 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/immer": {
"version": "9.0.12",
"resolved": "https://registry.npmjs.org/immer/-/immer-9.0.12.tgz",
"integrity": "sha512-lk7UNmSbAukB5B6dh9fnh5D0bJTOFKxVg2cyJWTYrWRfhLrLMBquONcUs3aFq507hNoIZEDDh8lb8UtOizSMhA==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/immer"
}
},
"node_modules/immutable": { "node_modules/immutable": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.0.0.tgz", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.0.0.tgz",
@ -19597,6 +19630,11 @@
"url": "https://github.com/sponsors/wooorm" "url": "https://github.com/sponsors/wooorm"
} }
}, },
"node_modules/is-hotkey": {
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/is-hotkey/-/is-hotkey-0.1.8.tgz",
"integrity": "sha512-qs3NZ1INIS+H+yeo7cD9pDfwYV/jqRh1JG9S9zYrNudkoUQg7OL7ziXqRKu+InFjUIDoP2o6HIkLYMh1pcWgyQ=="
},
"node_modules/is-installed-globally": { "node_modules/is-installed-globally": {
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz",
@ -28434,6 +28472,52 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/slate": {
"version": "0.78.0",
"resolved": "https://registry.npmjs.org/slate/-/slate-0.78.0.tgz",
"integrity": "sha512-VwQ0RafT3JPf9SFrXI02Dh3S4Iz9en7d1nn50C/LJjjqjfgv+a2ORbgWMdYjhycPYldaxJwcI3OpP9D1g4SXEg==",
"dependencies": {
"immer": "^9.0.6",
"is-plain-object": "^5.0.0",
"tiny-warning": "^1.0.3"
}
},
"node_modules/slate-react": {
"version": "0.79.0",
"resolved": "https://registry.npmjs.org/slate-react/-/slate-react-0.79.0.tgz",
"integrity": "sha512-uPzYArGwHKq4QvpzRvP/DVvsDgB2Zw6x9Som/hJWaydu18r0Vp2vmgHL0Dbc4EU6pUNV6o83XbBJLLEwISzBHQ==",
"dependencies": {
"@types/is-hotkey": "^0.1.1",
"@types/lodash": "^4.14.149",
"direction": "^1.0.3",
"is-hotkey": "^0.1.6",
"is-plain-object": "^5.0.0",
"lodash": "^4.17.4",
"scroll-into-view-if-needed": "^2.2.20",
"tiny-invariant": "1.0.6"
},
"peerDependencies": {
"react": ">=16.8.0",
"react-dom": ">=16.8.0",
"slate": ">=0.65.3"
}
},
"node_modules/slate-react/node_modules/is-plain-object": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/slate/node_modules/is-plain-object": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/snapdragon": { "node_modules/snapdragon": {
"version": "0.8.2", "version": "0.8.2",
"resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
@ -29808,6 +29892,16 @@
"node": ">=0.6.0" "node": ">=0.6.0"
} }
}, },
"node_modules/tiny-invariant": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.0.6.tgz",
"integrity": "sha512-FOyLWWVjG+aC0UqG76V53yAWdXfH8bO6FNmyZOuUrzDzK8DI3/JRY25UD7+g49JWM1LXwymsKERB+DzI0dTEQA=="
},
"node_modules/tiny-warning": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
"integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
},
"node_modules/tlds": { "node_modules/tlds": {
"version": "1.231.0", "version": "1.231.0",
"resolved": "https://registry.npmjs.org/tlds/-/tlds-1.231.0.tgz", "resolved": "https://registry.npmjs.org/tlds/-/tlds-1.231.0.tgz",
@ -39698,6 +39792,11 @@
"resolved": "https://registry.npmjs.org/@types/is-function/-/is-function-1.0.1.tgz", "resolved": "https://registry.npmjs.org/@types/is-function/-/is-function-1.0.1.tgz",
"integrity": "sha512-A79HEEiwXTFtfY+Bcbo58M2GRYzCr9itHWzbzHVFNEYCcoU/MMGwYYf721gBrnhpj1s6RGVVha/IgNFnR0Iw/Q==" "integrity": "sha512-A79HEEiwXTFtfY+Bcbo58M2GRYzCr9itHWzbzHVFNEYCcoU/MMGwYYf721gBrnhpj1s6RGVVha/IgNFnR0Iw/Q=="
}, },
"@types/is-hotkey": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/@types/is-hotkey/-/is-hotkey-0.1.7.tgz",
"integrity": "sha512-yB5C7zcOM7idwYZZ1wKQ3pTfjA9BbvFqRWvKB46GFddxnJtHwi/b9y84ykQtxQPg5qhdpg4Q/kWU3EGoCTmLzQ=="
},
"@types/istanbul-lib-coverage": { "@types/istanbul-lib-coverage": {
"version": "2.0.4", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz",
@ -39744,6 +39843,11 @@
"integrity": "sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==", "integrity": "sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==",
"dev": true "dev": true
}, },
"@types/lodash": {
"version": "4.14.182",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz",
"integrity": "sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q=="
},
"@types/markdown-it": { "@types/markdown-it": {
"version": "12.2.3", "version": "12.2.3",
"resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz",
@ -43358,6 +43462,11 @@
"path-type": "^4.0.0" "path-type": "^4.0.0"
} }
}, },
"direction": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/direction/-/direction-1.0.4.tgz",
"integrity": "sha512-GYqKi1aH7PJXxdhTeZBFrg8vUBeKXi+cNprXsC1kpJcbcVnV9wBsrOu1cQEdG0WeQwlfHiy3XvnKfIrJ2R0NzQ=="
},
"doctrine": { "doctrine": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
@ -46160,6 +46269,11 @@
"integrity": "sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=", "integrity": "sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=",
"optional": true "optional": true
}, },
"immer": {
"version": "9.0.12",
"resolved": "https://registry.npmjs.org/immer/-/immer-9.0.12.tgz",
"integrity": "sha512-lk7UNmSbAukB5B6dh9fnh5D0bJTOFKxVg2cyJWTYrWRfhLrLMBquONcUs3aFq507hNoIZEDDh8lb8UtOizSMhA=="
},
"immutable": { "immutable": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.0.0.tgz", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.0.0.tgz",
@ -46529,6 +46643,11 @@
"resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz",
"integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==" "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw=="
}, },
"is-hotkey": {
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/is-hotkey/-/is-hotkey-0.1.8.tgz",
"integrity": "sha512-qs3NZ1INIS+H+yeo7cD9pDfwYV/jqRh1JG9S9zYrNudkoUQg7OL7ziXqRKu+InFjUIDoP2o6HIkLYMh1pcWgyQ=="
},
"is-installed-globally": { "is-installed-globally": {
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz",
@ -53282,6 +53401,45 @@
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="
}, },
"slate": {
"version": "0.78.0",
"resolved": "https://registry.npmjs.org/slate/-/slate-0.78.0.tgz",
"integrity": "sha512-VwQ0RafT3JPf9SFrXI02Dh3S4Iz9en7d1nn50C/LJjjqjfgv+a2ORbgWMdYjhycPYldaxJwcI3OpP9D1g4SXEg==",
"requires": {
"immer": "^9.0.6",
"is-plain-object": "^5.0.0",
"tiny-warning": "^1.0.3"
},
"dependencies": {
"is-plain-object": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q=="
}
}
},
"slate-react": {
"version": "0.79.0",
"resolved": "https://registry.npmjs.org/slate-react/-/slate-react-0.79.0.tgz",
"integrity": "sha512-uPzYArGwHKq4QvpzRvP/DVvsDgB2Zw6x9Som/hJWaydu18r0Vp2vmgHL0Dbc4EU6pUNV6o83XbBJLLEwISzBHQ==",
"requires": {
"@types/is-hotkey": "^0.1.1",
"@types/lodash": "^4.14.149",
"direction": "^1.0.3",
"is-hotkey": "^0.1.6",
"is-plain-object": "^5.0.0",
"lodash": "^4.17.4",
"scroll-into-view-if-needed": "^2.2.20",
"tiny-invariant": "1.0.6"
},
"dependencies": {
"is-plain-object": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q=="
}
}
},
"snapdragon": { "snapdragon": {
"version": "0.8.2", "version": "0.8.2",
"resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
@ -54368,6 +54526,16 @@
"setimmediate": "^1.0.4" "setimmediate": "^1.0.4"
} }
}, },
"tiny-invariant": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.0.6.tgz",
"integrity": "sha512-FOyLWWVjG+aC0UqG76V53yAWdXfH8bO6FNmyZOuUrzDzK8DI3/JRY25UD7+g49JWM1LXwymsKERB+DzI0dTEQA=="
},
"tiny-warning": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
"integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
},
"tlds": { "tlds": {
"version": "1.231.0", "version": "1.231.0",
"resolved": "https://registry.npmjs.org/tlds/-/tlds-1.231.0.tgz", "resolved": "https://registry.npmjs.org/tlds/-/tlds-1.231.0.tgz",

View file

@ -36,6 +36,8 @@
"react-markdown-editor-lite": "1.3.2", "react-markdown-editor-lite": "1.3.2",
"react-virtuoso": "^2.10.2", "react-virtuoso": "^2.10.2",
"recoil": "^0.7.2", "recoil": "^0.7.2",
"slate": "^0.78.0",
"slate-react": "^0.79.0",
"ua-parser-js": "1.0.2", "ua-parser-js": "1.0.2",
"video.js": "^7.18.1" "video.js": "^7.18.1"
}, },