@pie-lib/editable-html 10.0.0-beta.6 → 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 +140 -450
  82. package/src/index.jsx +96 -62
  83. package/src/plugins/characters/index.jsx +18 -14
  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 +41 -67
  87. package/src/plugins/image/index.jsx +43 -108
  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 +91 -66
  91. package/src/plugins/math/index.jsx +71 -84
  92. package/src/plugins/media/index.jsx +118 -147
  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 +7 -10
  96. package/src/plugins/respArea/explicit-constructed-response/index.jsx +2 -3
  97. package/src/plugins/respArea/index.jsx +90 -138
  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 +216 -340
  101. package/src/plugins/table/table-toolbar.jsx +5 -9
  102. package/src/plugins/toolbar/default-toolbar.jsx +31 -51
  103. package/src/plugins/toolbar/editor-and-toolbar.jsx +114 -121
  104. package/src/plugins/toolbar/toolbar.jsx +224 -258
  105. package/src/plugins/utils.js +2 -19
  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,380 +1,22 @@
1
- import React, { useCallback, useMemo, useRef, useState, useEffect } from 'react';
2
- import {
3
- Editor as OldSlateEditor,
4
- findNode,
5
- getEventRange,
6
- getEventTransfer,
7
- } from 'slate-react';
8
- 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';
9
3
 
10
4
  import isEqual from 'lodash/isEqual';
11
- import * as serialization from './new-serialization';
5
+ import * as serialization from './serialization';
12
6
  import PropTypes from 'prop-types';
7
+ import React from 'react';
13
8
  import { Value, Block, Inline } from 'slate';
14
- import { ALL_PLUGINS, DEFAULT_PLUGINS, buildPlugins, withPlugins } from './plugins';
9
+ import { buildPlugins, ALL_PLUGINS, DEFAULT_PLUGINS } from './plugins';
15
10
  import debug from 'debug';
16
11
  import { withStyles } from '@material-ui/core/styles';
17
12
  import classNames from 'classnames';
18
13
  import { color } from '@pie-lib/render-ui';
19
14
  import Plain from 'slate-plain-serializer';
15
+ import { AlertDialog } from '@pie-lib/config-ui';
20
16
 
21
- import { getBase64, htmlToValue, valueToHtml } from './new-serialization';
17
+ import { getBase64 } from './serialization';
22
18
  import InsertImageHandler from './plugins/image/insert-image-handler';
23
19
 
24
- import isHotkey from 'is-hotkey';
25
- import { ReactEditor, useSlateStatic, Editable, useFocused, useSlate, Slate } from 'slate-react';
26
- import { Node as SlateNode, Path, Editor, Transforms, createEditor, Element as SlateElement } from 'slate';
27
-
28
- import { Button, Icon } from './components';
29
- import EditorAndToolbar from './plugins/toolbar/editor-and-toolbar';
30
-
31
- window.Path = Path;
32
- window.SlateNode = SlateNode;
33
- window.ReactEditor = ReactEditor;
34
- window.Editor = Editor;
35
-
36
- const HOTKEYS = {
37
- 'mod+b': 'bold',
38
- 'mod+i': 'italic',
39
- 'mod+u': 'underline',
40
- 'mod+`': 'code'
41
- };
42
-
43
- const LIST_TYPES = ['numbered-list', 'bulleted-list'];
44
- const TEXT_ALIGN_TYPES = ['left', 'center', 'right', 'justify'];
45
-
46
- const initialValue = [
47
- {
48
- type: 'paragraph',
49
- children: [
50
- {
51
- type: 'math',
52
- data: {
53
- latex: '\\frac{1}{2}',
54
- wrapper: 'round_brackets'
55
- },
56
- children: [
57
- {
58
- text: '\\(\\frac{1}{2}\\)'
59
- }
60
- ]
61
- }
62
- ]
63
- }
64
- ];
65
-
66
- const SlateEditor = editorProps => {
67
- const mounted = useRef(false);
68
- const { autoFocus, value, plugins, actionsRef, onEditingDone } = editorProps;
69
- const renderElement = useCallback(props => <Element {...props} plugins={plugins} />, []);
70
- const renderLeaf = useCallback(props => <Leaf {...props} />, []);
71
- const editor = useMemo(() => withPlugins(createEditor(), plugins), []);
72
- const [isFocused, setIsFocused] = useState(false);
73
- const editorRef = useRef(null);
74
-
75
- useEffect(() => {
76
- mounted.current = true;
77
-
78
- return () => {
79
- mounted.current = false;
80
- };
81
- }, []);
82
-
83
- useEffect(() => {
84
- if (editorProps.onEditor) {
85
- editorProps.onEditor(editor);
86
- }
87
-
88
- if (autoFocus) {
89
- Transforms.select(editor, [0, 0]);
90
- ReactEditor.focus(editor);
91
-
92
- if (mounted.current) {
93
- setIsFocused(true);
94
- }
95
- }
96
- }, [editor]);
97
-
98
- const slateValue = useMemo(() => {
99
- // Slate throws an error if the value on the initial render is invalid
100
- // so we directly set the value on the editor in order
101
- // to be able to trigger normalization on the initial value before rendering
102
- editor.children = value;
103
- editor.marks = {};
104
- Editor.normalize(editor, { force: true });
105
- // We return the normalized internal value so that the rendering can take over from here
106
- return editor.children;
107
- }, [editor, value]);
108
-
109
- window.editor = editor;
110
-
111
- const onKeyDown = event => {
112
- if (event.key === 'Enter' && event.shiftKey === true) {
113
- editor.insertText('\n');
114
- event.preventDefault();
115
- event.stopPropagation();
116
- return;
117
- }
118
- for (const hotkey in HOTKEYS) {
119
- if (isHotkey(hotkey, event)) {
120
- event.preventDefault();
121
- const mark = HOTKEYS[hotkey];
122
- toggleMark(editor, mark);
123
- }
124
- }
125
- };
126
- const onFocus = () => setIsFocused(true);
127
- const onBlur = e => {
128
- setTimeout(() => {
129
- if (!editorRef.current || !editorRef.current.contains(document.activeElement)) {
130
- if (editorProps.onBlur) {
131
- editorProps.onBlur(e);
132
- }
133
-
134
- if (mounted.current) {
135
- setIsFocused(false);
136
- }
137
- }
138
- }, 50);
139
- };
140
- const actions = {
141
- focus: (position, node) => {
142
- const [, textPath] = node
143
- ? Editor.leaf(editor, ReactEditor.findPath(editor, node), { edge: 'end' })
144
- : Editor.leaf(editor, [0], { edge: 'end' });
145
-
146
- Transforms.select(editor, textPath);
147
- ReactEditor.focus(editor);
148
- },
149
- finishEditing: () => {
150
- // if (!mounted.current) {
151
- // return;
152
- // }
153
-
154
- if (typeof onEditingDone === 'function') {
155
- onEditingDone(editor);
156
- }
157
- }
158
- };
159
-
160
- if (actionsRef) {
161
- actionsRef(actions);
162
- }
163
-
164
- return (
165
- <Slate editor={editor} initialValue={slateValue}>
166
- <RootRef rootRef={editorRef}>
167
- <EditorAndToolbar
168
- {...editorProps}
169
- editor={editor}
170
- isFocused={isFocused}
171
- onDone={() => {
172
- setIsFocused(false);
173
- document.activeElement.blur();
174
- editorProps.onDone(editor);
175
- }}
176
- >
177
- <Editable
178
- renderElement={renderElement}
179
- renderLeaf={renderLeaf}
180
- placeholder="Enter some rich text…"
181
- spellCheck
182
- onKeyDown={onKeyDown}
183
- onFocus={onFocus}
184
- onBlur={onBlur}
185
- />
186
- </EditorAndToolbar>
187
- </RootRef>
188
- </Slate>
189
- );
190
- };
191
-
192
- const toggleBlock = (editor, format) => {
193
- const isActive = isBlockActive(
194
- editor,
195
- format,
196
- TEXT_ALIGN_TYPES.includes(format) ? 'align' : 'type'
197
- );
198
- const isList = LIST_TYPES.includes(format);
199
-
200
- Transforms.unwrapNodes(editor, {
201
- match: n =>
202
- !Editor.isEditor(n) &&
203
- SlateElement.isElement(n) &&
204
- LIST_TYPES.includes(n.type) &&
205
- !TEXT_ALIGN_TYPES.includes(format),
206
- split: true
207
- });
208
- let newProperties;
209
- if (TEXT_ALIGN_TYPES.includes(format)) {
210
- newProperties = {
211
- align: isActive ? undefined : format
212
- };
213
- } else {
214
- newProperties = {
215
- type: isActive ? 'paragraph' : isList ? 'list_item' : format
216
- };
217
- }
218
- Transforms.setNodes(editor, newProperties);
219
-
220
- if (!isActive && isList) {
221
- const block = { type: format, children: [] };
222
- Transforms.wrapNodes(editor, block);
223
- }
224
- };
225
-
226
- const toggleMark = (editor, format) => {
227
- const isActive = isMarkActive(editor, format);
228
-
229
- if (isActive) {
230
- Editor.removeMark(editor, format);
231
- } else {
232
- Editor.addMark(editor, format, true);
233
- }
234
- };
235
-
236
- const isBlockActive = (editor, format, blockType = 'type') => {
237
- const { selection } = editor;
238
- if (!selection) return false;
239
-
240
- const [match] = Array.from(
241
- Editor.nodes(editor, {
242
- at: Editor.unhangRange(editor, selection),
243
- match: n => !Editor.isEditor(n) && SlateElement.isElement(n) && n[blockType] === format
244
- })
245
- );
246
-
247
- return !!match;
248
- };
249
-
250
- const isMarkActive = (editor, format) => {
251
- const marks = Editor.marks(editor);
252
- return marks ? marks[format] === true : false;
253
- };
254
-
255
- const Element = props => {
256
- const editor = useSlateStatic();
257
- const focused = useFocused();
258
- const { attributes, children, element, plugins } = props;
259
- const style = { textAlign: element.align };
260
-
261
- const nodeProps = { ...attributes, ...props, node: { ...element }, children };
262
- const pluginToRender = plugins.find(
263
- plugin => typeof plugin.supports === 'function' && plugin.supports(element)
264
- );
265
-
266
- if (pluginToRender) {
267
- return pluginToRender.renderNode({ ...nodeProps, editor, focused });
268
- }
269
-
270
- switch (element.type) {
271
- case 'block-quote':
272
- return (
273
- <blockquote style={style} {...attributes}>
274
- {children}
275
- </blockquote>
276
- );
277
- case 'bulleted-list':
278
- return (
279
- <ul style={style} {...attributes}>
280
- {children}
281
- </ul>
282
- );
283
- case 'heading-one':
284
- return (
285
- <h1 style={style} {...attributes}>
286
- {children}
287
- </h1>
288
- );
289
- case 'heading-two':
290
- return (
291
- <h2 style={style} {...attributes}>
292
- {children}
293
- </h2>
294
- );
295
- case 'list-item':
296
- return (
297
- <li style={style} {...attributes}>
298
- {children}
299
- </li>
300
- );
301
- case 'numbered-list':
302
- return (
303
- <ol style={style} {...attributes}>
304
- {children}
305
- </ol>
306
- );
307
- default:
308
- return (
309
- <div
310
- style={{
311
- ...style,
312
- margin: 0
313
- }}
314
- {...attributes}
315
- >
316
- {children}
317
- </div>
318
- );
319
- }
320
- };
321
-
322
- const Leaf = ({ attributes, children, leaf }) => {
323
- if (leaf.bold) {
324
- children = <strong>{children}</strong>;
325
- }
326
-
327
- if (leaf.code) {
328
- children = <code>{children}</code>;
329
- }
330
-
331
- if (leaf.italic) {
332
- children = <em>{children}</em>;
333
- }
334
-
335
- if (leaf.underline) {
336
- children = <u>{children}</u>;
337
- }
338
-
339
- if (leaf.strikethrough) {
340
- children = <del>{children}</del>;
341
- }
342
-
343
- return <span {...attributes}>{children}</span>;
344
- };
345
-
346
- const BlockButton = ({ format, icon }) => {
347
- const editor = useSlate();
348
- return (
349
- <Button
350
- active={isBlockActive(editor, format, TEXT_ALIGN_TYPES.includes(format) ? 'align' : 'type')}
351
- onMouseDown={event => {
352
- event.preventDefault();
353
- toggleBlock(editor, format);
354
- }}
355
- >
356
- <Icon>{icon}</Icon>
357
- </Button>
358
- );
359
- };
360
-
361
- const MarkButton = ({ format, icon }) => {
362
- const editor = useSlate();
363
- return (
364
- <Button
365
- active={isMarkActive(editor, format)}
366
- onMouseDown={event => {
367
- event.preventDefault();
368
- toggleMark(editor, format);
369
- }}
370
- >
371
- <Icon>{icon}</Icon>
372
- </Button>
373
- );
374
- };
375
-
376
- // old-editable
377
-
378
20
  export { ALL_PLUGINS, DEFAULT_PLUGINS, serialization };
379
21
 
380
22
  const log = debug('editable-html:editor');
@@ -395,31 +37,27 @@ const defaultResponseAreaProps = {
395
37
 
396
38
  const defaultLanguageCharactersProps = [];
397
39
 
398
- const createToolbarOpts = (toolbarOpts, error) => {
40
+ const createToolbarOpts = (toolbarOpts, error, isHtmlMode) => {
399
41
  return {
400
42
  ...defaultToolbarOpts,
401
43
  ...toolbarOpts,
402
44
  error,
45
+ isHtmlMode,
403
46
  };
404
47
  };
405
48
 
406
- export class EditorComponent extends React.Component {
49
+ export class Editor extends React.Component {
407
50
  static propTypes = {
408
51
  autoFocus: PropTypes.bool,
52
+ editorRef: PropTypes.func.isRequired,
409
53
  error: PropTypes.any,
410
54
  onRef: PropTypes.func.isRequired,
411
55
  onChange: PropTypes.func.isRequired,
412
- onEditor: PropTypes.func,
413
56
  onFocus: PropTypes.func,
414
57
  onBlur: PropTypes.func,
415
58
  onKeyDown: PropTypes.func,
416
- value: PropTypes.arrayOf(
417
- PropTypes.shape({
418
- type: PropTypes.string,
419
- children: PropTypes.array,
420
- data: PropTypes.object
421
- })
422
- ),
59
+ focus: PropTypes.func.isRequired,
60
+ value: SlateTypes.value.isRequired,
423
61
  imageSupport: PropTypes.object,
424
62
  mathMlOptions: PropTypes.shape({
425
63
  mmlOutput: PropTypes.bool,
@@ -496,8 +134,15 @@ export class EditorComponent extends React.Component {
496
134
  this.state = {
497
135
  value: props.value,
498
136
  toolbarOpts: createToolbarOpts(props.toolbarOpts, props.error),
137
+ isHtmlMode: false,
138
+ isEdited: false,
139
+ dialog: {
140
+ open: false,
141
+ },
499
142
  };
500
143
 
144
+ this.toggleHtmlMode = this.toggleHtmlMode.bind(this);
145
+
501
146
  this.onResize = () => {
502
147
  props.onChange(this.state.value, true);
503
148
  };
@@ -505,12 +150,48 @@ export class EditorComponent extends React.Component {
505
150
  this.handlePlugins(this.props);
506
151
  }
507
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
+
508
182
  handlePlugins = (props) => {
509
183
  const normalizedResponseAreaProps = {
510
184
  ...defaultResponseAreaProps,
511
185
  ...props.responseAreaProps,
512
186
  };
513
187
 
188
+ const htmlPluginOpts = {
189
+ isHtmlMode: this.state.isHtmlMode,
190
+ isEdited: this.state.isEdited,
191
+ toggleHtmlMode: this.toggleHtmlMode,
192
+ handleAlertDialog: this.handleAlertDialog,
193
+ };
194
+
514
195
  this.plugins = buildPlugins(props.activePlugins, {
515
196
  math: {
516
197
  onClick: this.onMathClick,
@@ -518,6 +199,7 @@ export class EditorComponent extends React.Component {
518
199
  onBlur: this.onPluginBlur,
519
200
  ...props.mathMlOptions,
520
201
  },
202
+ html: htmlPluginOpts,
521
203
  image: {
522
204
  disableImageAlignmentButtons: props.disableImageAlignmentButtons,
523
205
  onDelete:
@@ -551,6 +233,21 @@ export class EditorComponent extends React.Component {
551
233
  disableScrollbar: !!props.disableScrollbar,
552
234
  disableUnderline: props.disableUnderline,
553
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
+ },
554
251
  },
555
252
  table: {
556
253
  onFocus: () => {
@@ -581,6 +278,7 @@ export class EditorComponent extends React.Component {
581
278
  languageCharacters: props.languageCharactersProps,
582
279
  media: {
583
280
  focus: this.focus,
281
+ createChange: () => this.state.value.change(),
584
282
  onChange: this.onChange,
585
283
  uploadSoundSupport: props.uploadSoundSupport,
586
284
  },
@@ -592,6 +290,9 @@ export class EditorComponent extends React.Component {
592
290
  };
593
291
 
594
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
+
595
296
  window.addEventListener('resize', this.onResize);
596
297
 
597
298
  if (this.editor && this.props.autoFocus) {
@@ -599,6 +300,8 @@ export class EditorComponent extends React.Component {
599
300
  if (this.editor) {
600
301
  const editorDOM = document.querySelector(`[data-key="${this.editor.value.document.key}"]`);
601
302
 
303
+ this.editor.focus();
304
+
602
305
  if (editorDOM) {
603
306
  editorDOM.focus();
604
307
  }
@@ -608,8 +311,8 @@ export class EditorComponent extends React.Component {
608
311
  }
609
312
 
610
313
  UNSAFE_componentWillReceiveProps(nextProps) {
611
- const { toolbarOpts } = this.state;
612
- const newToolbarOpts = createToolbarOpts(nextProps.toolbarOpts, nextProps.error);
314
+ const { isHtmlMode, toolbarOpts } = this.state;
315
+ const newToolbarOpts = createToolbarOpts(nextProps.toolbarOpts, nextProps.error, isHtmlMode);
613
316
 
614
317
  if (!isEqual(newToolbarOpts, toolbarOpts)) {
615
318
  this.setState({
@@ -624,7 +327,7 @@ export class EditorComponent extends React.Component {
624
327
  this.handlePlugins(nextProps);
625
328
  }
626
329
 
627
- if (!isEqual(nextProps.value, this.props.value)) {
330
+ if (!nextProps.value.document.equals(this.props.value.document)) {
628
331
  this.setState({
629
332
  focus: false,
630
333
  value: nextProps.value,
@@ -632,9 +335,14 @@ export class EditorComponent extends React.Component {
632
335
  }
633
336
  }
634
337
 
635
- componentDidUpdate() {
338
+ componentDidUpdate(prevProps, prevState) {
636
339
  // The cursor is on a zero width element and when that is placed near void elements, it is not visible
637
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
+
638
346
  const zeroWidthEls = document.querySelectorAll('[data-slate-zero-width="z"]');
639
347
 
640
348
  Array.from(zeroWidthEls).forEach((el) => {
@@ -670,23 +378,15 @@ export class EditorComponent extends React.Component {
670
378
  };
671
379
 
672
380
  onMathClick = (node) => {
381
+ this.editor.change((c) => c.collapseToStartOf(node));
673
382
  this.setState({ selectedNode: node });
674
383
  };
675
384
 
676
- onEditingDone = editor => {
385
+ onEditingDone = () => {
677
386
  log('[onEditingDone]');
678
- // this.setState({ stashedValue: null, focusedNode: null });
387
+ this.setState({ stashedValue: null, focusedNode: null });
679
388
  log('[onEditingDone] value: ', this.state.value);
680
- this.props.onChange(editor, true);
681
- };
682
-
683
- onDone = editor => {
684
- const { nonEmpty } = this.props;
685
-
686
- log('[onDone]');
687
- this.setState({ toolbarInFocus: false, focusedNode: null });
688
-
689
- this.onEditingDone(editor);
389
+ this.props.onChange(this.state.value, true);
690
390
  };
691
391
 
692
392
  /**
@@ -705,6 +405,10 @@ export class EditorComponent extends React.Component {
705
405
 
706
406
  this.setState({ toolbarInFocus: false, focusedNode: null });
707
407
 
408
+ if (this.editor) {
409
+ this.editor.blur();
410
+ }
411
+
708
412
  if (doneOn === 'blur') {
709
413
  if (nonEmpty && this.state.value.startText?.text?.length === 0) {
710
414
  this.resetValue(true).then(() => {
@@ -719,11 +423,10 @@ export class EditorComponent extends React.Component {
719
423
  };
720
424
 
721
425
  onBlur = (event) => {
722
- return this.props.onBlur(event);
723
426
  log('[onBlur]');
724
427
  const target = event.relatedTarget;
725
428
 
726
- const node = ReactEditor.toSlateNode(editor, target);
429
+ const node = target ? findNode(target, this.state.value) : null;
727
430
 
728
431
  log('[onBlur] node: ', node);
729
432
 
@@ -846,17 +549,23 @@ export class EditorComponent extends React.Component {
846
549
  }
847
550
  };
848
551
 
849
- onChange = (editor, done) => {
552
+ onChange = (change, done) => {
850
553
  log('[onChange]');
554
+
555
+ const { value } = change;
851
556
  const { charactersLimit } = this.props;
852
- const allText = Editor.string(editor, []);
853
557
 
854
- if (allText > charactersLimit) {
558
+ if (value && value.document && value.document.text && value.document.text.length > charactersLimit) {
855
559
  return;
856
560
  }
857
561
 
858
- const html = valueToHtml(editor);
859
- 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
+ }
860
569
 
861
570
  this.setState({ value }, () => {
862
571
  log('[onChange], call done()');
@@ -925,6 +634,25 @@ export class EditorComponent extends React.Component {
925
634
  return undefined;
926
635
  };
927
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
+
928
656
  focus = (pos, node) => {
929
657
  const position = pos || 'end';
930
658
 
@@ -1025,7 +753,6 @@ export class EditorComponent extends React.Component {
1025
753
 
1026
754
  render() {
1027
755
  const {
1028
- autoFocus,
1029
756
  disabled,
1030
757
  spellCheck,
1031
758
  highlightShape,
@@ -1036,7 +763,7 @@ export class EditorComponent extends React.Component {
1036
763
  onKeyDown,
1037
764
  } = this.props;
1038
765
 
1039
- const { value, focusedNode, toolbarOpts } = this.state;
766
+ const { value, focusedNode, toolbarOpts, dialog } = this.state;
1040
767
 
1041
768
  log('[render] value: ', value);
1042
769
  const sizeStyle = this.buildSizeStyle();
@@ -1046,64 +773,19 @@ export class EditorComponent extends React.Component {
1046
773
  [classes.toolbarOnTop]: toolbarOpts.alwaysVisible && toolbarOpts.position === 'top',
1047
774
  },
1048
775
  className,
1049
- classes.slateEditor,
1050
776
  );
1051
777
 
1052
778
  return (
1053
779
  <div ref={(ref) => (this.wrapperRef = ref)} style={{ width: sizeStyle.width }} className={names}>
1054
780
  <SlateEditor
1055
781
  plugins={this.plugins}
1056
- toolbarRef={(r) => {
782
+ innerRef={(r) => {
1057
783
  if (r) {
1058
- this.toolbarRef = r;
784
+ this.slateEditor = r;
1059
785
  }
1060
786
  }}
1061
- autoFocus={autoFocus}
1062
- actionsRef={this.props.onRef}
1063
- onEditor={this.props.onEditor}
1064
- value={value}
1065
- focus={this.focus}
1066
- onKeyDown={onKeyDown}
1067
- onChange={this.onChange}
1068
- getFocusedValue={this.getFocusedValue}
1069
- onBlur={this.onBlur}
1070
- onDrop={(event, editor) => this.onDropPaste(event, editor, true)}
1071
- onPaste={(event, editor) => this.onDropPaste(event, editor)}
1072
- onFocus={this.onFocus}
1073
- onEditingDone={this.onEditingDone}
1074
- onDone={this.onDone}
1075
- focusedNode={focusedNode}
1076
- normalize={this.normalize}
1077
- readOnly={disabled}
1078
- spellCheck={spellCheck}
1079
- className={classNames(
1080
- {
1081
- [classes.noPadding]: toolbarOpts && toolbarOpts.noBorder
1082
- },
1083
- classes.slateEditor
1084
- )}
1085
- style={{
1086
- minHeight: sizeStyle.minHeight,
1087
- height: sizeStyle.height,
1088
- maxHeight: sizeStyle.maxHeight
1089
- }}
1090
- pluginProps={pluginProps}
1091
- toolbarOpts={toolbarOpts}
1092
- placeholder={placeholder}
1093
- renderPlaceholder={this.renderPlaceholder}
1094
- />
1095
- </div>
1096
- );
1097
-
1098
- return (
1099
- <div
1100
- ref={ref => (this.wrapperRef = ref)}
1101
- style={{ width: sizeStyle.width }}
1102
- className={names}
1103
- >
1104
- <OldSlateEditor
1105
- plugins={this.plugins}
1106
- toolbarRef={r => {
787
+ ref={(r) => (this.editor = r && this.props.editorRef(r))}
788
+ toolbarRef={(r) => {
1107
789
  if (r) {
1108
790
  this.toolbarRef = r;
1109
791
  }
@@ -1138,6 +820,14 @@ export class EditorComponent extends React.Component {
1138
820
  toolbarOpts={toolbarOpts}
1139
821
  placeholder={placeholder}
1140
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}
1141
831
  />
1142
832
  </div>
1143
833
  );
@@ -1182,4 +872,4 @@ const styles = {
1182
872
  },
1183
873
  };
1184
874
 
1185
- export default withStyles(styles)(EditorComponent);
875
+ export default withStyles(styles)(Editor);