@crystallize/design-system 1.7.0 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/index.css +53 -332
  3. package/dist/index.d.ts +15 -8
  4. package/dist/index.js +1723 -4554
  5. package/dist/index.mjs +1643 -2339
  6. package/package.json +1 -1
  7. package/src/action-menu/action-item-separator.tsx +14 -0
  8. package/src/action-menu/action-item.tsx +2 -2
  9. package/src/action-menu/action-menu.css +8 -0
  10. package/src/action-menu/action-menu.tsx +2 -1
  11. package/src/dropdown-menu/dropdown-menu-root.tsx +3 -1
  12. package/src/dropdown-menu/index.ts +5 -2
  13. package/src/iconography/subscription-contracts.tsx +4 -4
  14. package/src/iconography/subscription-plans.tsx +5 -5
  15. package/src/rich-text-editor/model/crystallize-to-lexical.ts +12 -1
  16. package/src/rich-text-editor/model/lexical-to-crystallize.ts +38 -38
  17. package/src/rich-text-editor/nodes/BaseNodes.ts +0 -7
  18. package/src/rich-text-editor/nodes/TableCellNodes.ts +0 -7
  19. package/src/rich-text-editor/plugins/ActionsPlugin/index.tsx +1 -1
  20. package/src/rich-text-editor/plugins/CodeActionMenuPlugin/components/CopyButton/index.tsx +3 -2
  21. package/src/rich-text-editor/plugins/CodeActionMenuPlugin/components/PrettierButton/index.tsx +0 -1
  22. package/src/rich-text-editor/plugins/FloatingTextFormatToolbarPlugin/index.css +17 -17
  23. package/src/rich-text-editor/plugins/FloatingTextFormatToolbarPlugin/index.tsx +1 -1
  24. package/src/rich-text-editor/plugins/MaxLengthPlugin/index.tsx +2 -7
  25. package/src/rich-text-editor/plugins/TableActionMenuPlugin/index.tsx +80 -149
  26. package/src/rich-text-editor/plugins/ToolbarPlugin/index.tsx +2 -2
  27. package/src/rich-text-editor/plugins/ToolbarPlugin/insert-table.tsx +55 -0
  28. package/src/rich-text-editor/rich-text-editor.css +10 -322
  29. package/src/rich-text-editor/rich-text-editor.stories.tsx +35 -5
  30. package/src/rich-text-editor/rich-text-editor.tsx +6 -39
  31. package/src/rich-text-editor/tests/rich-text-editor-code.test.tsx +10 -6
  32. package/src/rich-text-editor/tests/rich-text-editor-model-conversions.test.tsx +19 -7
  33. package/src/rich-text-editor/themes/CrystallizeRTEditorTheme.css +3 -11
  34. package/dist/TableComponent-I2YOOYOU.css +0 -281
  35. package/dist/TableComponent-QINOO453.mjs +0 -1377
  36. package/dist/chevron-down-3FRWSIKS.svg +0 -1
  37. package/dist/chunk-VUXQZRSP.mjs +0 -737
  38. package/dist/markdown-4BGQNLLT.svg +0 -1
  39. package/src/rich-text-editor/nodes/KeywordNode.ts +0 -73
  40. package/src/rich-text-editor/nodes/TableComponent.tsx +0 -1547
  41. package/src/rich-text-editor/nodes/TableNode.tsx +0 -398
  42. package/src/rich-text-editor/plugins/ComponentPickerPlugin/index.tsx +0 -320
  43. package/src/rich-text-editor/plugins/DragDropPastePlugin/index.ts +0 -40
  44. package/src/rich-text-editor/plugins/MarkdownShortcutPlugin/index.tsx +0 -16
  45. package/src/rich-text-editor/plugins/MarkdownTransformers/index.ts +0 -195
  46. package/src/rich-text-editor/plugins/SpeechToTextPlugin/index.ts +0 -113
  47. package/src/rich-text-editor/plugins/TableCellResizer/index.css +0 -12
  48. package/src/rich-text-editor/plugins/TableCellResizer/index.tsx +0 -386
  49. package/src/rich-text-editor/plugins/TablePlugin.tsx +0 -190
  50. package/src/rich-text-editor/plugins/TreeViewPlugin/index.tsx +0 -25
  51. package/src/rich-text-editor/plugins/TypingPerfPlugin/index.ts +0 -117
@@ -1,320 +0,0 @@
1
- /**
2
- * Copyright (c) Meta Platforms, Inc. and affiliates.
3
- *
4
- * This source code is licensed under the MIT license found in the
5
- * LICENSE file in the root directory of this source tree.
6
- *
7
- */
8
-
9
- import { useCallback, useMemo, useState } from 'react';
10
- import * as React from 'react';
11
- import { $createParagraphNode, $getSelection, $isRangeSelection, FORMAT_ELEMENT_COMMAND, TextNode } from 'lexical';
12
- import * as ReactDOM from 'react-dom';
13
- import { $createCodeNode } from '@lexical/code';
14
- import { INSERT_CHECK_LIST_COMMAND, INSERT_ORDERED_LIST_COMMAND, INSERT_UNORDERED_LIST_COMMAND } from '@lexical/list';
15
- import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
16
- import { INSERT_HORIZONTAL_RULE_COMMAND } from '@lexical/react/LexicalHorizontalRuleNode';
17
- import {
18
- LexicalTypeaheadMenuPlugin,
19
- TypeaheadOption,
20
- useBasicTypeaheadTriggerMatch,
21
- } from '@lexical/react/LexicalTypeaheadMenuPlugin';
22
- import { $createHeadingNode, $createQuoteNode } from '@lexical/rich-text';
23
- import { $setBlocksType } from '@lexical/selection';
24
- import { INSERT_TABLE_COMMAND } from '@lexical/table';
25
-
26
- // import { InsertNewTableDialog } from '../TablePlugin';
27
-
28
- class ComponentPickerOption extends TypeaheadOption {
29
- // What shows up in the editor
30
- title: string;
31
- // Icon for display
32
- icon?: JSX.Element;
33
- // For extra searching.
34
- keywords: Array<string>;
35
- // TBD
36
- keyboardShortcut?: string;
37
- // What happens when you select this option?
38
- onSelect: (queryString: string) => void;
39
-
40
- constructor(
41
- title: string,
42
- options: {
43
- icon?: JSX.Element;
44
- keywords?: Array<string>;
45
- keyboardShortcut?: string;
46
- onSelect: (queryString: string) => void;
47
- },
48
- ) {
49
- super(title);
50
- this.title = title;
51
- this.keywords = options.keywords || [];
52
- this.icon = options.icon;
53
- this.keyboardShortcut = options.keyboardShortcut;
54
- this.onSelect = options.onSelect.bind(this);
55
- }
56
- }
57
-
58
- function ComponentPickerMenuItem({
59
- index,
60
- isSelected,
61
- onClick,
62
- onMouseEnter,
63
- option,
64
- }: {
65
- index: number;
66
- isSelected: boolean;
67
- onClick: () => void;
68
- onMouseEnter: () => void;
69
- option: ComponentPickerOption;
70
- }) {
71
- let className = 'item';
72
- if (isSelected) {
73
- className += ' selected';
74
- }
75
- return (
76
- <li
77
- key={option.key}
78
- tabIndex={-1}
79
- className={className}
80
- ref={option.setRefElement}
81
- role="option"
82
- aria-selected={isSelected}
83
- id={'typeahead-item-' + index}
84
- onMouseEnter={onMouseEnter}
85
- onClick={onClick}
86
- >
87
- {option.icon}
88
- <span className="text">{option.title}</span>
89
- </li>
90
- );
91
- }
92
-
93
- export default function ComponentPickerMenuPlugin(): JSX.Element {
94
- const [editor] = useLexicalComposerContext();
95
- const [queryString, setQueryString] = useState<string | null>(null);
96
-
97
- const checkForTriggerMatch = useBasicTypeaheadTriggerMatch('/', {
98
- minLength: 0,
99
- });
100
-
101
- const getDynamicOptions = useCallback(() => {
102
- const options: Array<ComponentPickerOption> = [];
103
-
104
- if (queryString == null) {
105
- return options;
106
- }
107
-
108
- const fullTableRegex = new RegExp(/^([1-9]|10)x([1-9]|10)$/);
109
- const partialTableRegex = new RegExp(/^([1-9]|10)x?$/);
110
-
111
- const fullTableMatch = fullTableRegex.exec(queryString);
112
- const partialTableMatch = partialTableRegex.exec(queryString);
113
-
114
- if (fullTableMatch) {
115
- const [rows, columns] = fullTableMatch[0].split('x').map((n: string) => parseInt(n, 10));
116
-
117
- options.push(
118
- new ComponentPickerOption(`${rows}x${columns} Table`, {
119
- icon: <i className="icon table" />,
120
- keywords: ['table'],
121
- onSelect: () =>
122
- // @ts-ignore Correct types, but since they're dynamic TS doesn't like it.
123
- editor.dispatchCommand(INSERT_TABLE_COMMAND, { columns, rows }),
124
- }),
125
- );
126
- } else if (partialTableMatch) {
127
- const rows = parseInt(partialTableMatch[0], 10);
128
-
129
- options.push(
130
- ...Array.from({ length: 5 }, (_, i) => i + 1).map(
131
- columns =>
132
- new ComponentPickerOption(`${rows}x${columns} Table`, {
133
- icon: <i className="icon table" />,
134
- keywords: ['table'],
135
- onSelect: () =>
136
- // @ts-ignore Correct types, but since they're dynamic TS doesn't like it.
137
- editor.dispatchCommand(INSERT_TABLE_COMMAND, { columns, rows }),
138
- }),
139
- ),
140
- );
141
- }
142
-
143
- return options;
144
- }, [editor, queryString]);
145
-
146
- const options = useMemo(() => {
147
- const baseOptions = [
148
- new ComponentPickerOption('Paragraph', {
149
- icon: <i className="icon paragraph" />,
150
- keywords: ['normal', 'paragraph', 'p', 'text'],
151
- onSelect: () =>
152
- editor.update(() => {
153
- const selection = $getSelection();
154
- if ($isRangeSelection(selection)) {
155
- $setBlocksType(selection, () => $createParagraphNode());
156
- }
157
- }),
158
- }),
159
- ...Array.from({ length: 3 }, (_, i) => i + 1).map(
160
- n =>
161
- new ComponentPickerOption(`Heading ${n}`, {
162
- icon: <i className={`icon h${n}`} />,
163
- keywords: ['heading', 'header', `h${n}`],
164
- onSelect: () =>
165
- editor.update(() => {
166
- const selection = $getSelection();
167
- if ($isRangeSelection(selection)) {
168
- $setBlocksType(selection, () =>
169
- // @ts-ignore Correct types, but since they're dynamic TS doesn't like it.
170
- $createHeadingNode(`h${n}`),
171
- );
172
- }
173
- }),
174
- }),
175
- ),
176
- // new ComponentPickerOption('Table', {
177
- // icon: <i className="icon table" />,
178
- // keywords: ['table', 'grid', 'spreadsheet', 'rows', 'columns'],
179
- // onSelect: () =>
180
- // showModal('Insert Table', onClose => <InsertTableDialog activeEditor={editor} onClose={onClose} />),
181
- // }),
182
- // new ComponentPickerOption('Table (Experimental)', {
183
- // icon: <i className="icon table" />,
184
- // keywords: ['table', 'grid', 'spreadsheet', 'rows', 'columns'],
185
- // onSelect: () =>
186
- // showModal('Insert Table', onClose => <InsertNewTableDialog activeEditor={editor} onClose={onClose} />),
187
- // }),
188
- new ComponentPickerOption('Numbered List', {
189
- icon: <i className="icon number" />,
190
- keywords: ['numbered list', 'ordered list', 'ol'],
191
- onSelect: () => editor.dispatchCommand(INSERT_ORDERED_LIST_COMMAND, undefined),
192
- }),
193
- new ComponentPickerOption('Bulleted List', {
194
- icon: <i className="icon bullet" />,
195
- keywords: ['bulleted list', 'unordered list', 'ul'],
196
- onSelect: () => editor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND, undefined),
197
- }),
198
- new ComponentPickerOption('Check List', {
199
- icon: <i className="icon check" />,
200
- keywords: ['check list', 'todo list'],
201
- onSelect: () => editor.dispatchCommand(INSERT_CHECK_LIST_COMMAND, undefined),
202
- }),
203
- new ComponentPickerOption('Quote', {
204
- icon: <i className="icon quote" />,
205
- keywords: ['block quote'],
206
- onSelect: () =>
207
- editor.update(() => {
208
- const selection = $getSelection();
209
- if ($isRangeSelection(selection)) {
210
- $setBlocksType(selection, () => $createQuoteNode());
211
- }
212
- }),
213
- }),
214
- new ComponentPickerOption('Code', {
215
- icon: <i className="icon code" />,
216
- keywords: ['javascript', 'python', 'js', 'codeblock'],
217
- onSelect: () =>
218
- editor.update(() => {
219
- const selection = $getSelection();
220
-
221
- if ($isRangeSelection(selection)) {
222
- if (selection.isCollapsed()) {
223
- $setBlocksType(selection, () => $createCodeNode());
224
- } else {
225
- // Will this ever happen?
226
- const textContent = selection.getTextContent();
227
- const codeNode = $createCodeNode();
228
- selection.insertNodes([codeNode]);
229
- selection.insertRawText(textContent);
230
- }
231
- }
232
- }),
233
- }),
234
- new ComponentPickerOption('Divider', {
235
- icon: <i className="icon horizontal-rule" />,
236
- keywords: ['horizontal rule', 'divider', 'hr'],
237
- onSelect: () => editor.dispatchCommand(INSERT_HORIZONTAL_RULE_COMMAND, undefined),
238
- }),
239
-
240
- ...['left', 'center', 'right', 'justify'].map(
241
- alignment =>
242
- new ComponentPickerOption(`Align ${alignment}`, {
243
- icon: <i className={`icon ${alignment}-align`} />,
244
- keywords: ['align', 'justify', alignment],
245
- onSelect: () =>
246
- // @ts-ignore Correct types, but since they're dynamic TS doesn't like it.
247
- editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, alignment),
248
- }),
249
- ),
250
- ];
251
-
252
- const dynamicOptions = getDynamicOptions();
253
-
254
- return queryString
255
- ? [
256
- ...dynamicOptions,
257
- ...baseOptions.filter(option => {
258
- return new RegExp(queryString, 'gi').exec(option.title) || option.keywords != null
259
- ? option.keywords.some(keyword => new RegExp(queryString, 'gi').exec(keyword))
260
- : false;
261
- }),
262
- ]
263
- : baseOptions;
264
- }, [editor, getDynamicOptions, queryString]);
265
-
266
- const onSelectOption = useCallback(
267
- (
268
- selectedOption: ComponentPickerOption,
269
- nodeToRemove: TextNode | null,
270
- closeMenu: () => void,
271
- matchingString: string,
272
- ) => {
273
- editor.update(() => {
274
- if (nodeToRemove) {
275
- nodeToRemove.remove();
276
- }
277
- selectedOption.onSelect(matchingString);
278
- closeMenu();
279
- });
280
- },
281
- [editor],
282
- );
283
-
284
- return (
285
- <>
286
- <LexicalTypeaheadMenuPlugin<ComponentPickerOption>
287
- onQueryChange={setQueryString}
288
- onSelectOption={onSelectOption}
289
- triggerFn={checkForTriggerMatch}
290
- options={options}
291
- menuRenderFn={(anchorElementRef, { selectedIndex, selectOptionAndCleanUp, setHighlightedIndex }) =>
292
- anchorElementRef.current && options.length
293
- ? ReactDOM.createPortal(
294
- <div className="typeahead-popover component-picker-menu">
295
- <ul>
296
- {options.map((option, i: number) => (
297
- <ComponentPickerMenuItem
298
- index={i}
299
- isSelected={selectedIndex === i}
300
- onClick={() => {
301
- setHighlightedIndex(i);
302
- selectOptionAndCleanUp(option);
303
- }}
304
- onMouseEnter={() => {
305
- setHighlightedIndex(i);
306
- }}
307
- key={option.key}
308
- option={option}
309
- />
310
- ))}
311
- </ul>
312
- </div>,
313
- anchorElementRef.current,
314
- )
315
- : null
316
- }
317
- />
318
- </>
319
- );
320
- }
@@ -1,40 +0,0 @@
1
- /**
2
- * Copyright (c) Meta Platforms, Inc. and affiliates.
3
- *
4
- * This source code is licensed under the MIT license found in the
5
- * LICENSE file in the root directory of this source tree.
6
- *
7
- */
8
-
9
- import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
10
- import { DRAG_DROP_PASTE } from '@lexical/rich-text';
11
- import { isMimeType, mediaFileReader } from '@lexical/utils';
12
- import { COMMAND_PRIORITY_LOW } from 'lexical';
13
- import { useEffect } from 'react';
14
-
15
- const ACCEPTABLE_IMAGE_TYPES = ['image/', 'image/heic', 'image/heif', 'image/gif', 'image/webp'];
16
-
17
- export default function DragDropPaste(): null {
18
- const [editor] = useLexicalComposerContext();
19
- useEffect(() => {
20
- return editor.registerCommand(
21
- DRAG_DROP_PASTE,
22
- files => {
23
- (async () => {
24
- const filesResult = await mediaFileReader(
25
- files,
26
- [ACCEPTABLE_IMAGE_TYPES].flatMap(x => x),
27
- );
28
- for (const { file, result } of filesResult) {
29
- if (isMimeType(file, ACCEPTABLE_IMAGE_TYPES)) {
30
- console.log({ file, name: file.name });
31
- }
32
- }
33
- })();
34
- return true;
35
- },
36
- COMMAND_PRIORITY_LOW,
37
- );
38
- }, [editor]);
39
- return null;
40
- }
@@ -1,16 +0,0 @@
1
- /**
2
- * Copyright (c) Meta Platforms, Inc. and affiliates.
3
- *
4
- * This source code is licensed under the MIT license found in the
5
- * LICENSE file in the root directory of this source tree.
6
- *
7
- */
8
-
9
- import * as React from 'react';
10
- import { MarkdownShortcutPlugin } from '@lexical/react/LexicalMarkdownShortcutPlugin';
11
-
12
- import { PLAYGROUND_TRANSFORMERS } from '../MarkdownTransformers';
13
-
14
- export default function MarkdownPlugin(): JSX.Element {
15
- return <MarkdownShortcutPlugin transformers={PLAYGROUND_TRANSFORMERS} />;
16
- }
@@ -1,195 +0,0 @@
1
- /**
2
- * Copyright (c) Meta Platforms, Inc. and affiliates.
3
- *
4
- * This source code is licensed under the MIT license found in the
5
- * LICENSE file in the root directory of this source tree.
6
- *
7
- */
8
-
9
- import {
10
- $createParagraphNode,
11
- $createTextNode,
12
- $isElementNode,
13
- $isParagraphNode,
14
- $isTextNode,
15
- type ElementNode,
16
- type LexicalNode,
17
- } from 'lexical';
18
- import {
19
- CHECK_LIST,
20
- ELEMENT_TRANSFORMERS,
21
- TEXT_FORMAT_TRANSFORMERS,
22
- TEXT_MATCH_TRANSFORMERS,
23
- type ElementTransformer,
24
- type Transformer,
25
- } from '@lexical/markdown';
26
- import {
27
- $createHorizontalRuleNode,
28
- $isHorizontalRuleNode,
29
- HorizontalRuleNode,
30
- } from '@lexical/react/LexicalHorizontalRuleNode';
31
- import {
32
- $createTableCellNode,
33
- $createTableNode,
34
- $createTableRowNode,
35
- $isTableNode,
36
- $isTableRowNode,
37
- TableCellHeaderStates,
38
- TableCellNode,
39
- TableNode,
40
- TableRowNode,
41
- } from '@lexical/table';
42
-
43
- export const HR: ElementTransformer = {
44
- dependencies: [HorizontalRuleNode],
45
- export: (node: LexicalNode) => {
46
- return $isHorizontalRuleNode(node) ? '***' : null;
47
- },
48
- regExp: /^(---|\*\*\*|___)\s?$/,
49
- replace: (parentNode, _1, _2, isImport) => {
50
- const line = $createHorizontalRuleNode();
51
-
52
- // TODO: Get rid of isImport flag
53
- if (isImport || parentNode.getNextSibling() != null) {
54
- parentNode.replace(line);
55
- } else {
56
- parentNode.insertBefore(line);
57
- }
58
-
59
- line.selectNext();
60
- },
61
- type: 'element',
62
- };
63
-
64
- // Very primitive table setup
65
- const TABLE_ROW_REG_EXP = /^(?:\|)(.+)(?:\|)\s?$/;
66
-
67
- export const TABLE: ElementTransformer = {
68
- // TODO: refactor transformer for new TableNode
69
- dependencies: [TableNode, TableRowNode, TableCellNode],
70
- export: (node: LexicalNode, exportChildren: (elementNode: ElementNode) => string) => {
71
- if (!$isTableNode(node)) {
72
- return null;
73
- }
74
-
75
- const output = [];
76
-
77
- for (const row of node.getChildren()) {
78
- const rowOutput = [];
79
-
80
- if ($isTableRowNode(row)) {
81
- for (const cell of row.getChildren()) {
82
- // It's TableCellNode (hence ElementNode) so it's just to make flow happy
83
- if ($isElementNode(cell)) {
84
- rowOutput.push(exportChildren(cell));
85
- }
86
- }
87
- }
88
-
89
- output.push(`| ${rowOutput.join(' | ')} |`);
90
- }
91
-
92
- return output.join('\n');
93
- },
94
- regExp: TABLE_ROW_REG_EXP,
95
- replace: (parentNode, _1, match) => {
96
- const matchCells = mapToTableCells(match[0]);
97
-
98
- if (matchCells == null) {
99
- return;
100
- }
101
-
102
- const rows = [matchCells];
103
- let sibling = parentNode.getPreviousSibling();
104
- let maxCells = matchCells.length;
105
-
106
- while (sibling) {
107
- if (!$isParagraphNode(sibling)) {
108
- break;
109
- }
110
-
111
- if (sibling.getChildrenSize() !== 1) {
112
- break;
113
- }
114
-
115
- const firstChild = sibling.getFirstChild();
116
-
117
- if (!$isTextNode(firstChild)) {
118
- break;
119
- }
120
-
121
- const cells = mapToTableCells(firstChild.getTextContent());
122
-
123
- if (cells == null) {
124
- break;
125
- }
126
-
127
- maxCells = Math.max(maxCells, cells.length);
128
- rows.unshift(cells);
129
- const previousSibling = sibling.getPreviousSibling();
130
- sibling.remove();
131
- sibling = previousSibling;
132
- }
133
-
134
- const table = $createTableNode();
135
-
136
- for (const cells of rows) {
137
- const tableRow = $createTableRowNode();
138
- table.append(tableRow);
139
-
140
- for (let i = 0; i < maxCells; i++) {
141
- tableRow.append(i < cells.length ? cells[i] : createTableCell(null));
142
- }
143
- }
144
-
145
- const previousSibling = parentNode.getPreviousSibling();
146
- if ($isTableNode(previousSibling) && getTableColumnsSize(previousSibling) === maxCells) {
147
- previousSibling.append(...table.getChildren());
148
- parentNode.remove();
149
- } else {
150
- parentNode.replace(table);
151
- }
152
-
153
- table.selectEnd();
154
- },
155
- type: 'element',
156
- };
157
-
158
- function getTableColumnsSize(table: TableNode) {
159
- const row = table.getFirstChild();
160
- return $isTableRowNode(row) ? row.getChildrenSize() : 0;
161
- }
162
-
163
- const createTableCell = (textContent: string | null | undefined): TableCellNode => {
164
- const cell = $createTableCellNode(TableCellHeaderStates.NO_STATUS);
165
- const paragraph = $createParagraphNode();
166
-
167
- if (textContent != null) {
168
- paragraph.append($createTextNode(textContent.trim()));
169
- }
170
-
171
- cell.append(paragraph);
172
- return cell;
173
- };
174
-
175
- const mapToTableCells = (textContent: string): Array<TableCellNode> | null => {
176
- // TODO:
177
- // For now plain text, single node. Can be expanded to more complex content
178
- // including formatted text
179
- const match = textContent.match(TABLE_ROW_REG_EXP);
180
-
181
- if (!match || !match[1]) {
182
- return null;
183
- }
184
-
185
- return match[1].split('|').map(text => createTableCell(text));
186
- };
187
-
188
- export const PLAYGROUND_TRANSFORMERS: Array<Transformer> = [
189
- TABLE,
190
- HR,
191
- CHECK_LIST,
192
- ...ELEMENT_TRANSFORMERS,
193
- ...TEXT_FORMAT_TRANSFORMERS,
194
- ...TEXT_MATCH_TRANSFORMERS,
195
- ];
@@ -1,113 +0,0 @@
1
- /**
2
- * Copyright (c) Meta Platforms, Inc. and affiliates.
3
- *
4
- * This source code is licensed under the MIT license found in the
5
- * LICENSE file in the root directory of this source tree.
6
- *
7
- */
8
-
9
- import type { LexicalCommand, LexicalEditor, RangeSelection } from 'lexical';
10
-
11
- import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
12
- import {
13
- $getSelection,
14
- $isRangeSelection,
15
- COMMAND_PRIORITY_EDITOR,
16
- createCommand,
17
- REDO_COMMAND,
18
- UNDO_COMMAND,
19
- } from 'lexical';
20
- import { useEffect, useRef, useState } from 'react';
21
-
22
- import useReport from '../../hooks/useReport';
23
-
24
- export const SPEECH_TO_TEXT_COMMAND: LexicalCommand<boolean> = createCommand('SPEECH_TO_TEXT_COMMAND');
25
-
26
- const VOICE_COMMANDS: Readonly<Record<string, (arg0: { editor: LexicalEditor; selection: RangeSelection }) => void>> = {
27
- '\n': ({ selection }) => {
28
- selection.insertParagraph();
29
- },
30
- redo: ({ editor }) => {
31
- editor.dispatchCommand(REDO_COMMAND, undefined);
32
- },
33
- undo: ({ editor }) => {
34
- editor.dispatchCommand(UNDO_COMMAND, undefined);
35
- },
36
- };
37
-
38
- export const SUPPORT_SPEECH_RECOGNITION: boolean =
39
- typeof window !== 'undefined' && ('SpeechRecognition' in window || 'webkitSpeechRecognition' in window);
40
-
41
- function SpeechToTextPlugin(): null {
42
- const [editor] = useLexicalComposerContext();
43
- const [isEnabled, setIsEnabled] = useState<boolean>(false);
44
- const SpeechRecognition =
45
- // @ts-ignore
46
- window.SpeechRecognition || window.webkitSpeechRecognition;
47
- const recognition = useRef<typeof SpeechRecognition | null>(null);
48
- const report = useReport();
49
-
50
- useEffect(() => {
51
- if (isEnabled && recognition.current === null) {
52
- recognition.current = new SpeechRecognition();
53
- recognition.current.continuous = true;
54
- recognition.current.interimResults = true;
55
- recognition.current.addEventListener('result', (event: typeof SpeechRecognition) => {
56
- const resultItem = event.results.item(event.resultIndex);
57
- const { transcript } = resultItem.item(0);
58
- report(transcript);
59
-
60
- if (!resultItem.isFinal) {
61
- return;
62
- }
63
-
64
- editor.update(() => {
65
- const selection = $getSelection();
66
-
67
- if ($isRangeSelection(selection)) {
68
- const command = VOICE_COMMANDS[transcript.toLowerCase().trim()];
69
-
70
- if (command) {
71
- command({
72
- editor,
73
- selection,
74
- });
75
- } else if (transcript.match(/\s*\n\s*/)) {
76
- selection.insertParagraph();
77
- } else {
78
- selection.insertText(transcript);
79
- }
80
- }
81
- });
82
- });
83
- }
84
-
85
- if (recognition.current) {
86
- if (isEnabled) {
87
- recognition.current.start();
88
- } else {
89
- recognition.current.stop();
90
- }
91
- }
92
-
93
- return () => {
94
- if (recognition.current !== null) {
95
- recognition.current.stop();
96
- }
97
- };
98
- }, [SpeechRecognition, editor, isEnabled, report]);
99
- useEffect(() => {
100
- return editor.registerCommand(
101
- SPEECH_TO_TEXT_COMMAND,
102
- (_isEnabled: boolean) => {
103
- setIsEnabled(_isEnabled);
104
- return true;
105
- },
106
- COMMAND_PRIORITY_EDITOR,
107
- );
108
- }, [editor]);
109
-
110
- return null;
111
- }
112
-
113
- export default (SUPPORT_SPEECH_RECOGNITION ? SpeechToTextPlugin : () => null) as () => null;
@@ -1,12 +0,0 @@
1
- /**
2
- * Copyright (c) Meta Platforms, Inc. and affiliates.
3
- *
4
- * This source code is licensed under the MIT license found in the
5
- * LICENSE file in the root directory of this source tree.
6
- *
7
- *
8
- */
9
-
10
- .TableCellResizer__resizer {
11
- position: absolute;
12
- }