@pie-lib/editable-html 10.0.0-beta.7 → 10.0.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 (118) hide show
  1. package/CHANGELOG.json +1 -1
  2. package/CHANGELOG.md +81 -0
  3. package/LICENSE.md +5 -0
  4. package/lib/editor.js +410 -543
  5. package/lib/editor.js.map +1 -1
  6. package/lib/index.js +200 -101
  7. package/lib/index.js.map +1 -1
  8. package/lib/parse-html.js +5 -6
  9. package/lib/parse-html.js.map +1 -1
  10. package/lib/plugins/characters/custom-popper.js +12 -2
  11. package/lib/plugins/characters/custom-popper.js.map +1 -1
  12. package/lib/plugins/characters/index.js +71 -19
  13. package/lib/plugins/characters/index.js.map +1 -1
  14. package/lib/plugins/characters/utils.js.map +1 -1
  15. package/lib/plugins/html/icons/index.js +38 -0
  16. package/lib/plugins/html/icons/index.js.map +1 -0
  17. package/lib/plugins/html/index.js +75 -0
  18. package/lib/plugins/html/index.js.map +1 -0
  19. package/lib/plugins/image/alt-dialog.js +26 -0
  20. package/lib/plugins/image/alt-dialog.js.map +1 -1
  21. package/lib/plugins/image/component.js +124 -90
  22. package/lib/plugins/image/component.js.map +1 -1
  23. package/lib/plugins/image/image-toolbar.js +45 -7
  24. package/lib/plugins/image/image-toolbar.js.map +1 -1
  25. package/lib/plugins/image/index.js +91 -113
  26. package/lib/plugins/image/index.js.map +1 -1
  27. package/lib/plugins/image/insert-image-handler.js +54 -72
  28. package/lib/plugins/image/insert-image-handler.js.map +1 -1
  29. package/lib/plugins/index.js +71 -31
  30. package/lib/plugins/index.js.map +1 -1
  31. package/lib/plugins/list/index.js +129 -58
  32. package/lib/plugins/list/index.js.map +1 -1
  33. package/lib/plugins/math/index.js +152 -118
  34. package/lib/plugins/math/index.js.map +1 -1
  35. package/lib/plugins/media/index.js +185 -168
  36. package/lib/plugins/media/index.js.map +1 -1
  37. package/lib/plugins/media/media-dialog.js +197 -110
  38. package/lib/plugins/media/media-dialog.js.map +1 -1
  39. package/lib/plugins/media/media-toolbar.js +24 -4
  40. package/lib/plugins/media/media-toolbar.js.map +1 -1
  41. package/lib/plugins/media/media-wrapper.js +65 -23
  42. package/lib/plugins/media/media-wrapper.js.map +1 -1
  43. package/lib/plugins/respArea/drag-in-the-blank/choice.js +50 -10
  44. package/lib/plugins/respArea/drag-in-the-blank/choice.js.map +1 -1
  45. package/lib/plugins/respArea/drag-in-the-blank/index.js +22 -9
  46. package/lib/plugins/respArea/drag-in-the-blank/index.js.map +1 -1
  47. package/lib/plugins/respArea/explicit-constructed-response/index.js +9 -4
  48. package/lib/plugins/respArea/explicit-constructed-response/index.js.map +1 -1
  49. package/lib/plugins/respArea/icons/index.js +18 -1
  50. package/lib/plugins/respArea/icons/index.js.map +1 -1
  51. package/lib/plugins/respArea/index.js +133 -122
  52. package/lib/plugins/respArea/index.js.map +1 -1
  53. package/lib/plugins/respArea/inline-dropdown/index.js +10 -4
  54. package/lib/plugins/respArea/inline-dropdown/index.js.map +1 -1
  55. package/lib/plugins/respArea/utils.js +33 -15
  56. package/lib/plugins/respArea/utils.js.map +1 -1
  57. package/lib/plugins/table/icons/index.js +7 -0
  58. package/lib/plugins/table/icons/index.js.map +1 -1
  59. package/lib/plugins/table/index.js +279 -390
  60. package/lib/plugins/table/index.js.map +1 -1
  61. package/lib/plugins/table/table-toolbar.js +47 -14
  62. package/lib/plugins/table/table-toolbar.js.map +1 -1
  63. package/lib/plugins/toolbar/default-toolbar.js +63 -51
  64. package/lib/plugins/toolbar/default-toolbar.js.map +1 -1
  65. package/lib/plugins/toolbar/done-button.js +9 -1
  66. package/lib/plugins/toolbar/done-button.js.map +1 -1
  67. package/lib/plugins/toolbar/editor-and-toolbar.js +140 -83
  68. package/lib/plugins/toolbar/editor-and-toolbar.js.map +1 -1
  69. package/lib/plugins/toolbar/index.js +5 -0
  70. package/lib/plugins/toolbar/index.js.map +1 -1
  71. package/lib/plugins/toolbar/toolbar-buttons.js +39 -8
  72. package/lib/plugins/toolbar/toolbar-buttons.js.map +1 -1
  73. package/lib/plugins/toolbar/toolbar.js +261 -225
  74. package/lib/plugins/toolbar/toolbar.js.map +1 -1
  75. package/lib/plugins/utils.js +16 -19
  76. package/lib/plugins/utils.js.map +1 -1
  77. package/lib/serialization.js +70 -11
  78. package/lib/serialization.js.map +1 -1
  79. package/lib/theme.js.map +1 -1
  80. package/package.json +18 -17
  81. package/src/editor.jsx +139 -434
  82. package/src/index.jsx +96 -62
  83. package/src/plugins/characters/index.jsx +17 -12
  84. package/src/plugins/html/icons/index.jsx +19 -0
  85. package/src/plugins/html/index.jsx +68 -0
  86. package/src/plugins/image/component.jsx +38 -60
  87. package/src/plugins/image/index.jsx +42 -95
  88. package/src/plugins/image/insert-image-handler.js +27 -62
  89. package/src/plugins/index.jsx +39 -21
  90. package/src/plugins/list/index.jsx +90 -62
  91. package/src/plugins/math/index.jsx +70 -93
  92. package/src/plugins/media/index.jsx +117 -146
  93. package/src/plugins/media/media-dialog.js +9 -10
  94. package/src/plugins/media/media-wrapper.jsx +27 -29
  95. package/src/plugins/respArea/drag-in-the-blank/index.jsx +4 -5
  96. package/src/plugins/respArea/explicit-constructed-response/index.jsx +1 -2
  97. package/src/plugins/respArea/index.jsx +84 -114
  98. package/src/plugins/respArea/inline-dropdown/index.jsx +2 -3
  99. package/src/plugins/respArea/utils.jsx +28 -23
  100. package/src/plugins/table/index.jsx +214 -334
  101. package/src/plugins/table/table-toolbar.jsx +4 -3
  102. package/src/plugins/toolbar/default-toolbar.jsx +30 -48
  103. package/src/plugins/toolbar/editor-and-toolbar.jsx +114 -114
  104. package/src/plugins/toolbar/toolbar.jsx +224 -254
  105. package/src/plugins/utils.js +0 -16
  106. package/src/serialization.jsx +1 -1
  107. package/lib/components.js +0 -92
  108. package/lib/components.js.map +0 -1
  109. package/lib/new-serialization.js +0 -280
  110. package/lib/new-serialization.js.map +0 -1
  111. package/lib/plugins/hotKeys/index.js +0 -60
  112. package/lib/plugins/hotKeys/index.js.map +0 -1
  113. package/lib/test-serializer.js +0 -138
  114. package/lib/test-serializer.js.map +0 -1
  115. package/src/components.js +0 -135
  116. package/src/new-serialization.jsx +0 -310
  117. package/src/plugins/hotKeys/index.js +0 -54
  118. package/src/test-serializer.js +0 -132
package/src/editor.jsx CHANGED
@@ -1,369 +1,22 @@
1
- import React, { useCallback, useMemo, useRef, useState, useEffect } from 'react';
2
- import { Editor as OldSlateEditor, findNode, getEventRange, getEventTransfer } from 'slate-react';
3
- import RootRef from '@material-ui/core/RootRef';
1
+ import { Editor as SlateEditor, findNode, getEventRange, getEventTransfer } from 'slate-react';
2
+ import SlateTypes from 'slate-prop-types';
4
3
 
5
4
  import isEqual from 'lodash/isEqual';
6
- import * as serialization from './new-serialization';
5
+ import * as serialization from './serialization';
7
6
  import PropTypes from 'prop-types';
7
+ import React from 'react';
8
8
  import { Value, Block, Inline } from 'slate';
9
- import { ALL_PLUGINS, DEFAULT_PLUGINS, buildPlugins, withPlugins } from './plugins';
9
+ import { buildPlugins, ALL_PLUGINS, DEFAULT_PLUGINS } from './plugins';
10
10
  import debug from 'debug';
11
11
  import { withStyles } from '@material-ui/core/styles';
12
12
  import classNames from 'classnames';
13
13
  import { color } from '@pie-lib/render-ui';
14
14
  import Plain from 'slate-plain-serializer';
15
+ import { AlertDialog } from '@pie-lib/config-ui';
15
16
 
16
- import { getBase64, htmlToValue, valueToHtml } from './new-serialization';
17
+ import { getBase64 } from './serialization';
17
18
  import InsertImageHandler from './plugins/image/insert-image-handler';
18
19
 
19
- import isHotkey from 'is-hotkey';
20
- import { ReactEditor, useSlateStatic, Editable, useFocused, useSlate, Slate } from 'slate-react';
21
- import { Node as SlateNode, Path, Editor, Transforms, createEditor, Element as SlateElement } from 'slate';
22
-
23
- import { Button, Icon } from './components';
24
- import EditorAndToolbar from './plugins/toolbar/editor-and-toolbar';
25
-
26
- window.Path = Path;
27
- window.SlateNode = SlateNode;
28
- window.ReactEditor = ReactEditor;
29
- window.Editor = Editor;
30
-
31
- const HOTKEYS = {
32
- 'mod+b': 'bold',
33
- 'mod+i': 'italic',
34
- 'mod+u': 'underline',
35
- 'mod+`': 'code',
36
- };
37
-
38
- const LIST_TYPES = ['numbered-list', 'bulleted-list'];
39
- const TEXT_ALIGN_TYPES = ['left', 'center', 'right', 'justify'];
40
-
41
- const initialValue = [
42
- {
43
- type: 'paragraph',
44
- children: [
45
- {
46
- type: 'math',
47
- data: {
48
- latex: '\\frac{1}{2}',
49
- wrapper: 'round_brackets',
50
- },
51
- children: [
52
- {
53
- text: '\\(\\frac{1}{2}\\)',
54
- },
55
- ],
56
- },
57
- ],
58
- },
59
- ];
60
-
61
- const SlateEditor = (editorProps) => {
62
- const mounted = useRef(false);
63
- const { autoFocus, value, plugins, actionsRef, onEditingDone } = editorProps;
64
- const renderElement = useCallback((props) => <Element {...props} plugins={plugins} />, []);
65
- const renderLeaf = useCallback((props) => <Leaf {...props} />, []);
66
- const editor = useMemo(() => withPlugins(createEditor(), plugins), []);
67
- const [isFocused, setIsFocused] = useState(false);
68
- const editorRef = useRef(null);
69
-
70
- useEffect(() => {
71
- mounted.current = true;
72
-
73
- return () => {
74
- mounted.current = false;
75
- };
76
- }, []);
77
-
78
- useEffect(() => {
79
- if (editorProps.onEditor) {
80
- editorProps.onEditor(editor);
81
- }
82
-
83
- if (autoFocus) {
84
- Transforms.select(editor, [0, 0]);
85
- ReactEditor.focus(editor);
86
-
87
- if (mounted.current) {
88
- setIsFocused(true);
89
- }
90
- }
91
- }, [editor]);
92
-
93
- const slateValue = useMemo(() => {
94
- // Slate throws an error if the value on the initial render is invalid
95
- // so we directly set the value on the editor in order
96
- // to be able to trigger normalization on the initial value before rendering
97
- editor.children = value;
98
- editor.marks = {};
99
- Editor.normalize(editor, { force: true });
100
- // We return the normalized internal value so that the rendering can take over from here
101
- return editor.children;
102
- }, [editor, value]);
103
-
104
- window.editor = editor;
105
-
106
- const onKeyDown = (event) => {
107
- if (event.key === 'Enter' && event.shiftKey === true) {
108
- editor.insertText('\n');
109
- event.preventDefault();
110
- event.stopPropagation();
111
- return;
112
- }
113
- for (const hotkey in HOTKEYS) {
114
- if (isHotkey(hotkey, event)) {
115
- event.preventDefault();
116
- const mark = HOTKEYS[hotkey];
117
- toggleMark(editor, mark);
118
- }
119
- }
120
- };
121
- const onFocus = () => setIsFocused(true);
122
- const onBlur = (e) => {
123
- setTimeout(() => {
124
- if (!editorRef.current || !editorRef.current.contains(document.activeElement)) {
125
- if (editorProps.onBlur) {
126
- editorProps.onBlur(e);
127
- }
128
-
129
- if (mounted.current) {
130
- setIsFocused(false);
131
- }
132
- }
133
- }, 50);
134
- };
135
- const actions = {
136
- focus: (position, node) => {
137
- const [, textPath] = node
138
- ? Editor.leaf(editor, ReactEditor.findPath(editor, node), { edge: 'end' })
139
- : Editor.leaf(editor, [0], { edge: 'end' });
140
-
141
- Transforms.select(editor, textPath);
142
- ReactEditor.focus(editor);
143
- },
144
- finishEditing: () => {
145
- // if (!mounted.current) {
146
- // return;
147
- // }
148
-
149
- if (typeof onEditingDone === 'function') {
150
- onEditingDone(editor);
151
- }
152
- },
153
- };
154
-
155
- if (actionsRef) {
156
- actionsRef(actions);
157
- }
158
-
159
- return (
160
- <Slate editor={editor} initialValue={slateValue}>
161
- <RootRef rootRef={editorRef}>
162
- <EditorAndToolbar
163
- {...editorProps}
164
- editor={editor}
165
- isFocused={isFocused}
166
- onDone={() => {
167
- setIsFocused(false);
168
- document.activeElement.blur();
169
- editorProps.onDone(editor);
170
- }}
171
- >
172
- <Editable
173
- renderElement={renderElement}
174
- renderLeaf={renderLeaf}
175
- placeholder="Enter some rich text…"
176
- spellCheck
177
- onKeyDown={onKeyDown}
178
- onFocus={onFocus}
179
- onBlur={onBlur}
180
- />
181
- </EditorAndToolbar>
182
- </RootRef>
183
- </Slate>
184
- );
185
- };
186
-
187
- const toggleBlock = (editor, format) => {
188
- const isActive = isBlockActive(editor, format, TEXT_ALIGN_TYPES.includes(format) ? 'align' : 'type');
189
- const isList = LIST_TYPES.includes(format);
190
-
191
- Transforms.unwrapNodes(editor, {
192
- match: (n) =>
193
- !Editor.isEditor(n) &&
194
- SlateElement.isElement(n) &&
195
- LIST_TYPES.includes(n.type) &&
196
- !TEXT_ALIGN_TYPES.includes(format),
197
- split: true,
198
- });
199
- let newProperties;
200
- if (TEXT_ALIGN_TYPES.includes(format)) {
201
- newProperties = {
202
- align: isActive ? undefined : format,
203
- };
204
- } else {
205
- newProperties = {
206
- type: isActive ? 'paragraph' : isList ? 'list_item' : format,
207
- };
208
- }
209
- Transforms.setNodes(editor, newProperties);
210
-
211
- if (!isActive && isList) {
212
- const block = { type: format, children: [] };
213
- Transforms.wrapNodes(editor, block);
214
- }
215
- };
216
-
217
- const toggleMark = (editor, format) => {
218
- const isActive = isMarkActive(editor, format);
219
-
220
- if (isActive) {
221
- Editor.removeMark(editor, format);
222
- } else {
223
- Editor.addMark(editor, format, true);
224
- }
225
- };
226
-
227
- const isBlockActive = (editor, format, blockType = 'type') => {
228
- const { selection } = editor;
229
- if (!selection) return false;
230
-
231
- const [match] = Array.from(
232
- Editor.nodes(editor, {
233
- at: Editor.unhangRange(editor, selection),
234
- match: (n) => !Editor.isEditor(n) && SlateElement.isElement(n) && n[blockType] === format,
235
- }),
236
- );
237
-
238
- return !!match;
239
- };
240
-
241
- const isMarkActive = (editor, format) => {
242
- const marks = Editor.marks(editor);
243
- return marks ? marks[format] === true : false;
244
- };
245
-
246
- const Element = (props) => {
247
- const editor = useSlateStatic();
248
- const focused = useFocused();
249
- const { attributes, children, element, plugins } = props;
250
- const style = { textAlign: element.align };
251
-
252
- const nodeProps = { ...attributes, ...props, node: { ...element }, children };
253
- const pluginToRender = plugins.find((plugin) => typeof plugin.supports === 'function' && plugin.supports(element));
254
-
255
- if (pluginToRender) {
256
- return pluginToRender.renderNode({ ...nodeProps, editor, focused });
257
- }
258
-
259
- switch (element.type) {
260
- case 'block-quote':
261
- return (
262
- <blockquote style={style} {...attributes}>
263
- {children}
264
- </blockquote>
265
- );
266
- case 'bulleted-list':
267
- return (
268
- <ul style={style} {...attributes}>
269
- {children}
270
- </ul>
271
- );
272
- case 'heading-one':
273
- return (
274
- <h1 style={style} {...attributes}>
275
- {children}
276
- </h1>
277
- );
278
- case 'heading-two':
279
- return (
280
- <h2 style={style} {...attributes}>
281
- {children}
282
- </h2>
283
- );
284
- case 'list-item':
285
- return (
286
- <li style={style} {...attributes}>
287
- {children}
288
- </li>
289
- );
290
- case 'numbered-list':
291
- return (
292
- <ol style={style} {...attributes}>
293
- {children}
294
- </ol>
295
- );
296
- default:
297
- return (
298
- <div
299
- style={{
300
- ...style,
301
- margin: 0,
302
- }}
303
- {...attributes}
304
- >
305
- {children}
306
- </div>
307
- );
308
- }
309
- };
310
-
311
- const Leaf = ({ attributes, children, leaf }) => {
312
- if (leaf.bold) {
313
- children = <strong>{children}</strong>;
314
- }
315
-
316
- if (leaf.code) {
317
- children = <code>{children}</code>;
318
- }
319
-
320
- if (leaf.italic) {
321
- children = <em>{children}</em>;
322
- }
323
-
324
- if (leaf.underline) {
325
- children = <u>{children}</u>;
326
- }
327
-
328
- if (leaf.strikethrough) {
329
- children = <del>{children}</del>;
330
- }
331
-
332
- return <span {...attributes}>{children}</span>;
333
- };
334
-
335
- const BlockButton = ({ format, icon }) => {
336
- const editor = useSlate();
337
- return (
338
- <Button
339
- active={isBlockActive(editor, format, TEXT_ALIGN_TYPES.includes(format) ? 'align' : 'type')}
340
- onMouseDown={(event) => {
341
- event.preventDefault();
342
- toggleBlock(editor, format);
343
- }}
344
- >
345
- <Icon>{icon}</Icon>
346
- </Button>
347
- );
348
- };
349
-
350
- const MarkButton = ({ format, icon }) => {
351
- const editor = useSlate();
352
- return (
353
- <Button
354
- active={isMarkActive(editor, format)}
355
- onMouseDown={(event) => {
356
- event.preventDefault();
357
- toggleMark(editor, format);
358
- }}
359
- >
360
- <Icon>{icon}</Icon>
361
- </Button>
362
- );
363
- };
364
-
365
- // old-editable
366
-
367
20
  export { ALL_PLUGINS, DEFAULT_PLUGINS, serialization };
368
21
 
369
22
  const log = debug('editable-html:editor');
@@ -384,31 +37,27 @@ const defaultResponseAreaProps = {
384
37
 
385
38
  const defaultLanguageCharactersProps = [];
386
39
 
387
- const createToolbarOpts = (toolbarOpts, error) => {
40
+ const createToolbarOpts = (toolbarOpts, error, isHtmlMode) => {
388
41
  return {
389
42
  ...defaultToolbarOpts,
390
43
  ...toolbarOpts,
391
44
  error,
45
+ isHtmlMode,
392
46
  };
393
47
  };
394
48
 
395
- export class EditorComponent extends React.Component {
49
+ export class Editor extends React.Component {
396
50
  static propTypes = {
397
51
  autoFocus: PropTypes.bool,
52
+ editorRef: PropTypes.func.isRequired,
398
53
  error: PropTypes.any,
399
54
  onRef: PropTypes.func.isRequired,
400
55
  onChange: PropTypes.func.isRequired,
401
- onEditor: PropTypes.func,
402
56
  onFocus: PropTypes.func,
403
57
  onBlur: PropTypes.func,
404
58
  onKeyDown: PropTypes.func,
405
- value: PropTypes.arrayOf(
406
- PropTypes.shape({
407
- type: PropTypes.string,
408
- children: PropTypes.array,
409
- data: PropTypes.object,
410
- }),
411
- ),
59
+ focus: PropTypes.func.isRequired,
60
+ value: SlateTypes.value.isRequired,
412
61
  imageSupport: PropTypes.object,
413
62
  mathMlOptions: PropTypes.shape({
414
63
  mmlOutput: PropTypes.bool,
@@ -485,8 +134,15 @@ export class EditorComponent extends React.Component {
485
134
  this.state = {
486
135
  value: props.value,
487
136
  toolbarOpts: createToolbarOpts(props.toolbarOpts, props.error),
137
+ isHtmlMode: false,
138
+ isEdited: false,
139
+ dialog: {
140
+ open: false,
141
+ },
488
142
  };
489
143
 
144
+ this.toggleHtmlMode = this.toggleHtmlMode.bind(this);
145
+
490
146
  this.onResize = () => {
491
147
  props.onChange(this.state.value, true);
492
148
  };
@@ -494,12 +150,48 @@ export class EditorComponent extends React.Component {
494
150
  this.handlePlugins(this.props);
495
151
  }
496
152
 
153
+ handleAlertDialog = (open, extraDialogProps, callback) => {
154
+ this.setState(
155
+ {
156
+ dialog: {
157
+ open,
158
+ ...extraDialogProps,
159
+ },
160
+ isEdited: false,
161
+ },
162
+ callback,
163
+ );
164
+ };
165
+
166
+ toggleHtmlMode = () => {
167
+ this.setState(
168
+ (prevState) => ({
169
+ isHtmlMode: !prevState.isHtmlMode,
170
+ }),
171
+ () => {
172
+ const { error } = this.props;
173
+ const { toolbarOpts } = this.state;
174
+ const newToolbarOpts = createToolbarOpts(toolbarOpts, error, this.state.isHtmlMode);
175
+ this.setState({
176
+ toolbarOpts: newToolbarOpts,
177
+ });
178
+ },
179
+ );
180
+ };
181
+
497
182
  handlePlugins = (props) => {
498
183
  const normalizedResponseAreaProps = {
499
184
  ...defaultResponseAreaProps,
500
185
  ...props.responseAreaProps,
501
186
  };
502
187
 
188
+ const htmlPluginOpts = {
189
+ isHtmlMode: this.state.isHtmlMode,
190
+ isEdited: this.state.isEdited,
191
+ toggleHtmlMode: this.toggleHtmlMode,
192
+ handleAlertDialog: this.handleAlertDialog,
193
+ };
194
+
503
195
  this.plugins = buildPlugins(props.activePlugins, {
504
196
  math: {
505
197
  onClick: this.onMathClick,
@@ -507,6 +199,7 @@ export class EditorComponent extends React.Component {
507
199
  onBlur: this.onPluginBlur,
508
200
  ...props.mathMlOptions,
509
201
  },
202
+ html: htmlPluginOpts,
510
203
  image: {
511
204
  disableImageAlignmentButtons: props.disableImageAlignmentButtons,
512
205
  onDelete:
@@ -540,6 +233,21 @@ export class EditorComponent extends React.Component {
540
233
  disableScrollbar: !!props.disableScrollbar,
541
234
  disableUnderline: props.disableUnderline,
542
235
  autoWidth: props.autoWidthToolbar,
236
+ onDone: () => {
237
+ const { nonEmpty } = props;
238
+
239
+ log('[onDone]');
240
+ this.setState({ toolbarInFocus: false, focusedNode: null });
241
+ this.editor.blur();
242
+
243
+ if (nonEmpty && this.state.value.startText?.text?.length === 0) {
244
+ this.resetValue(true).then(() => {
245
+ this.onEditingDone();
246
+ });
247
+ } else {
248
+ this.onEditingDone();
249
+ }
250
+ },
543
251
  },
544
252
  table: {
545
253
  onFocus: () => {
@@ -570,6 +278,7 @@ export class EditorComponent extends React.Component {
570
278
  languageCharacters: props.languageCharactersProps,
571
279
  media: {
572
280
  focus: this.focus,
281
+ createChange: () => this.state.value.change(),
573
282
  onChange: this.onChange,
574
283
  uploadSoundSupport: props.uploadSoundSupport,
575
284
  },
@@ -581,6 +290,9 @@ export class EditorComponent extends React.Component {
581
290
  };
582
291
 
583
292
  componentDidMount() {
293
+ // onRef is needed to get the ref of the component because we export it using withStyles
294
+ this.props.onRef(this);
295
+
584
296
  window.addEventListener('resize', this.onResize);
585
297
 
586
298
  if (this.editor && this.props.autoFocus) {
@@ -588,6 +300,8 @@ export class EditorComponent extends React.Component {
588
300
  if (this.editor) {
589
301
  const editorDOM = document.querySelector(`[data-key="${this.editor.value.document.key}"]`);
590
302
 
303
+ this.editor.focus();
304
+
591
305
  if (editorDOM) {
592
306
  editorDOM.focus();
593
307
  }
@@ -597,8 +311,8 @@ export class EditorComponent extends React.Component {
597
311
  }
598
312
 
599
313
  UNSAFE_componentWillReceiveProps(nextProps) {
600
- const { toolbarOpts } = this.state;
601
- const newToolbarOpts = createToolbarOpts(nextProps.toolbarOpts, nextProps.error);
314
+ const { isHtmlMode, toolbarOpts } = this.state;
315
+ const newToolbarOpts = createToolbarOpts(nextProps.toolbarOpts, nextProps.error, isHtmlMode);
602
316
 
603
317
  if (!isEqual(newToolbarOpts, toolbarOpts)) {
604
318
  this.setState({
@@ -613,7 +327,7 @@ export class EditorComponent extends React.Component {
613
327
  this.handlePlugins(nextProps);
614
328
  }
615
329
 
616
- if (!isEqual(nextProps.value, this.props.value)) {
330
+ if (!nextProps.value.document.equals(this.props.value.document)) {
617
331
  this.setState({
618
332
  focus: false,
619
333
  value: nextProps.value,
@@ -621,9 +335,14 @@ export class EditorComponent extends React.Component {
621
335
  }
622
336
  }
623
337
 
624
- componentDidUpdate() {
338
+ componentDidUpdate(prevProps, prevState) {
625
339
  // The cursor is on a zero width element and when that is placed near void elements, it is not visible
626
340
  // so we increase the width to at least 2px in order for the user to see it
341
+ if (this.state.isHtmlMode !== prevState.isHtmlMode || prevState.isEdited !== this.state.isEdited) {
342
+ this.handlePlugins(this.props);
343
+ this.onEditingDone();
344
+ }
345
+
627
346
  const zeroWidthEls = document.querySelectorAll('[data-slate-zero-width="z"]');
628
347
 
629
348
  Array.from(zeroWidthEls).forEach((el) => {
@@ -659,23 +378,15 @@ export class EditorComponent extends React.Component {
659
378
  };
660
379
 
661
380
  onMathClick = (node) => {
381
+ this.editor.change((c) => c.collapseToStartOf(node));
662
382
  this.setState({ selectedNode: node });
663
383
  };
664
384
 
665
- onEditingDone = (editor) => {
385
+ onEditingDone = () => {
666
386
  log('[onEditingDone]');
667
- // this.setState({ stashedValue: null, focusedNode: null });
387
+ this.setState({ stashedValue: null, focusedNode: null });
668
388
  log('[onEditingDone] value: ', this.state.value);
669
- this.props.onChange(editor, true);
670
- };
671
-
672
- onDone = (editor) => {
673
- const { nonEmpty } = this.props;
674
-
675
- log('[onDone]');
676
- this.setState({ toolbarInFocus: false, focusedNode: null });
677
-
678
- this.onEditingDone(editor);
389
+ this.props.onChange(this.state.value, true);
679
390
  };
680
391
 
681
392
  /**
@@ -694,6 +405,10 @@ export class EditorComponent extends React.Component {
694
405
 
695
406
  this.setState({ toolbarInFocus: false, focusedNode: null });
696
407
 
408
+ if (this.editor) {
409
+ this.editor.blur();
410
+ }
411
+
697
412
  if (doneOn === 'blur') {
698
413
  if (nonEmpty && this.state.value.startText?.text?.length === 0) {
699
414
  this.resetValue(true).then(() => {
@@ -708,11 +423,10 @@ export class EditorComponent extends React.Component {
708
423
  };
709
424
 
710
425
  onBlur = (event) => {
711
- return this.props.onBlur(event);
712
426
  log('[onBlur]');
713
427
  const target = event.relatedTarget;
714
428
 
715
- const node = ReactEditor.toSlateNode(editor, target);
429
+ const node = target ? findNode(target, this.state.value) : null;
716
430
 
717
431
  log('[onBlur] node: ', node);
718
432
 
@@ -835,17 +549,23 @@ export class EditorComponent extends React.Component {
835
549
  }
836
550
  };
837
551
 
838
- onChange = (editor, done) => {
552
+ onChange = (change, done) => {
839
553
  log('[onChange]');
554
+
555
+ const { value } = change;
840
556
  const { charactersLimit } = this.props;
841
- const allText = Editor.string(editor, []);
842
557
 
843
- if (allText > charactersLimit) {
558
+ if (value && value.document && value.document.text && value.document.text.length > charactersLimit) {
844
559
  return;
845
560
  }
846
561
 
847
- const html = valueToHtml(editor);
848
- const value = htmlToValue(html);
562
+ if (!this.state.isHtmlMode) {
563
+ this.setState({ isEdited: false });
564
+ }
565
+
566
+ if (this.state.isHtmlMode && !isEqual(this.state.value.document.text, value.document.text)) {
567
+ this.setState({ isEdited: true });
568
+ }
849
569
 
850
570
  this.setState({ value }, () => {
851
571
  log('[onChange], call done()');
@@ -914,6 +634,25 @@ export class EditorComponent extends React.Component {
914
634
  return undefined;
915
635
  };
916
636
 
637
+ changeData = (key, data) => {
638
+ log('[changeData]. .. ', key, data);
639
+
640
+ /**
641
+ * HACK ALERT: We should be calling setState here and storing the change data:
642
+ *
643
+ * <code>this.setState({changeData: { key, data}})</code>
644
+ * However this is causing issues with the Mathquill instance. The 'input' event stops firing on the element and no
645
+ * more changes get through. The issues seem to be related to the promises in onBlur/onFocus. But removing these
646
+ * brings it's own problems. A major clean up is planned for this component so I've decided to temporarily settle
647
+ * on this hack rather than spend more time on this.
648
+ */
649
+
650
+ // Uncomment this line to see the bug described above.
651
+ // this.setState({changeData: {key, data}})
652
+
653
+ this.__TEMPORARY_CHANGE_DATA = { key, data };
654
+ };
655
+
917
656
  focus = (pos, node) => {
918
657
  const position = pos || 'end';
919
658
 
@@ -1014,7 +753,6 @@ export class EditorComponent extends React.Component {
1014
753
 
1015
754
  render() {
1016
755
  const {
1017
- autoFocus,
1018
756
  disabled,
1019
757
  spellCheck,
1020
758
  highlightShape,
@@ -1025,7 +763,7 @@ export class EditorComponent extends React.Component {
1025
763
  onKeyDown,
1026
764
  } = this.props;
1027
765
 
1028
- const { value, focusedNode, toolbarOpts } = this.state;
766
+ const { value, focusedNode, toolbarOpts, dialog } = this.state;
1029
767
 
1030
768
  log('[render] value: ', value);
1031
769
  const sizeStyle = this.buildSizeStyle();
@@ -1035,59 +773,18 @@ export class EditorComponent extends React.Component {
1035
773
  [classes.toolbarOnTop]: toolbarOpts.alwaysVisible && toolbarOpts.position === 'top',
1036
774
  },
1037
775
  className,
1038
- classes.slateEditor,
1039
776
  );
1040
777
 
1041
778
  return (
1042
779
  <div ref={(ref) => (this.wrapperRef = ref)} style={{ width: sizeStyle.width }} className={names}>
1043
780
  <SlateEditor
1044
781
  plugins={this.plugins}
1045
- toolbarRef={(r) => {
782
+ innerRef={(r) => {
1046
783
  if (r) {
1047
- this.toolbarRef = r;
784
+ this.slateEditor = r;
1048
785
  }
1049
786
  }}
1050
- autoFocus={autoFocus}
1051
- actionsRef={this.props.onRef}
1052
- onEditor={this.props.onEditor}
1053
- value={value}
1054
- focus={this.focus}
1055
- onKeyDown={onKeyDown}
1056
- onChange={this.onChange}
1057
- getFocusedValue={this.getFocusedValue}
1058
- onBlur={this.onBlur}
1059
- onDrop={(event, editor) => this.onDropPaste(event, editor, true)}
1060
- onPaste={(event, editor) => this.onDropPaste(event, editor)}
1061
- onFocus={this.onFocus}
1062
- onEditingDone={this.onEditingDone}
1063
- onDone={this.onDone}
1064
- focusedNode={focusedNode}
1065
- normalize={this.normalize}
1066
- readOnly={disabled}
1067
- spellCheck={spellCheck}
1068
- className={classNames(
1069
- {
1070
- [classes.noPadding]: toolbarOpts && toolbarOpts.noBorder,
1071
- },
1072
- classes.slateEditor,
1073
- )}
1074
- style={{
1075
- minHeight: sizeStyle.minHeight,
1076
- height: sizeStyle.height,
1077
- maxHeight: sizeStyle.maxHeight,
1078
- }}
1079
- pluginProps={pluginProps}
1080
- toolbarOpts={toolbarOpts}
1081
- placeholder={placeholder}
1082
- renderPlaceholder={this.renderPlaceholder}
1083
- />
1084
- </div>
1085
- );
1086
-
1087
- return (
1088
- <div ref={(ref) => (this.wrapperRef = ref)} style={{ width: sizeStyle.width }} className={names}>
1089
- <OldSlateEditor
1090
- plugins={this.plugins}
787
+ ref={(r) => (this.editor = r && this.props.editorRef(r))}
1091
788
  toolbarRef={(r) => {
1092
789
  if (r) {
1093
790
  this.toolbarRef = r;
@@ -1123,6 +820,14 @@ export class EditorComponent extends React.Component {
1123
820
  toolbarOpts={toolbarOpts}
1124
821
  placeholder={placeholder}
1125
822
  renderPlaceholder={this.renderPlaceholder}
823
+ onDataChange={this.changeData}
824
+ />
825
+ <AlertDialog
826
+ open={dialog.open}
827
+ title={dialog.title}
828
+ text={dialog.text}
829
+ onClose={dialog.onClose}
830
+ onConfirm={dialog.onConfirm}
1126
831
  />
1127
832
  </div>
1128
833
  );
@@ -1167,4 +872,4 @@ const styles = {
1167
872
  },
1168
873
  };
1169
874
 
1170
- export default withStyles(styles)(EditorComponent);
875
+ export default withStyles(styles)(Editor);