@pie-lib/editable-html 9.5.13 → 10.0.0-beta.1

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 (144) hide show
  1. package/CHANGELOG.md +0 -302
  2. package/lib/components.js +116 -0
  3. package/lib/components.js.map +1 -0
  4. package/lib/editor.js +418 -103
  5. package/lib/editor.js.map +1 -1
  6. package/lib/index.js +101 -155
  7. package/lib/index.js.map +1 -1
  8. package/lib/new-serialization.js +320 -0
  9. package/lib/new-serialization.js.map +1 -0
  10. package/lib/old-serialization.js +330 -0
  11. package/lib/parse-html.js +1 -1
  12. package/lib/parse-html.js.map +1 -1
  13. package/lib/plugins/characters/custom-popper.js +1 -1
  14. package/lib/plugins/characters/custom-popper.js.map +1 -1
  15. package/lib/plugins/characters/index.js +21 -19
  16. package/lib/plugins/characters/index.js.map +1 -1
  17. package/lib/plugins/characters/utils.js +1 -1
  18. package/lib/plugins/characters/utils.js.map +1 -1
  19. package/lib/plugins/hotKeys/index.js +67 -0
  20. package/lib/plugins/hotKeys/index.js.map +1 -0
  21. package/lib/plugins/image/alt-dialog.js +1 -6
  22. package/lib/plugins/image/alt-dialog.js.map +1 -1
  23. package/lib/plugins/image/component.js +70 -53
  24. package/lib/plugins/image/component.js.map +1 -1
  25. package/lib/plugins/image/image-toolbar.js +7 -9
  26. package/lib/plugins/image/image-toolbar.js.map +1 -1
  27. package/lib/plugins/image/index.js +83 -27
  28. package/lib/plugins/image/index.js.map +1 -1
  29. package/lib/plugins/image/insert-image-handler.js +72 -33
  30. package/lib/plugins/image/insert-image-handler.js.map +1 -1
  31. package/lib/plugins/index.js +23 -41
  32. package/lib/plugins/index.js.map +1 -1
  33. package/lib/plugins/list/index.js +64 -100
  34. package/lib/plugins/list/index.js.map +1 -1
  35. package/lib/plugins/math/index.js +86 -60
  36. package/lib/plugins/math/index.js.map +1 -1
  37. package/lib/plugins/media/index.js +202 -132
  38. package/lib/plugins/media/index.js.map +1 -1
  39. package/lib/plugins/media/media-dialog.js +17 -16
  40. package/lib/plugins/media/media-dialog.js.map +1 -1
  41. package/lib/plugins/media/media-toolbar.js +3 -3
  42. package/lib/plugins/media/media-toolbar.js.map +1 -1
  43. package/lib/plugins/media/media-wrapper.js +21 -58
  44. package/lib/plugins/media/media-wrapper.js.map +1 -1
  45. package/lib/plugins/respArea/drag-in-the-blank/choice.js +3 -3
  46. package/lib/plugins/respArea/drag-in-the-blank/choice.js.map +1 -1
  47. package/lib/plugins/respArea/drag-in-the-blank/index.js +3 -2
  48. package/lib/plugins/respArea/drag-in-the-blank/index.js.map +1 -1
  49. package/lib/plugins/respArea/explicit-constructed-response/index.js +3 -2
  50. package/lib/plugins/respArea/explicit-constructed-response/index.js.map +1 -1
  51. package/lib/plugins/respArea/icons/index.js +13 -15
  52. package/lib/plugins/respArea/icons/index.js.map +1 -1
  53. package/lib/plugins/respArea/index.js +87 -53
  54. package/lib/plugins/respArea/index.js.map +1 -1
  55. package/lib/plugins/respArea/inline-dropdown/index.js +4 -3
  56. package/lib/plugins/respArea/inline-dropdown/index.js.map +1 -1
  57. package/lib/plugins/respArea/utils.js +17 -20
  58. package/lib/plugins/respArea/utils.js.map +1 -1
  59. package/lib/plugins/table/icons/index.js +1 -1
  60. package/lib/plugins/table/icons/index.js.map +1 -1
  61. package/lib/plugins/table/index.js +381 -212
  62. package/lib/plugins/table/index.js.map +1 -1
  63. package/lib/plugins/table/table-toolbar.js +5 -6
  64. package/lib/plugins/table/table-toolbar.js.map +1 -1
  65. package/lib/plugins/toolbar/default-toolbar.js +55 -11
  66. package/lib/plugins/toolbar/default-toolbar.js.map +1 -1
  67. package/lib/plugins/toolbar/done-button.js +1 -1
  68. package/lib/plugins/toolbar/done-button.js.map +1 -1
  69. package/lib/plugins/toolbar/editor-and-toolbar.js +186 -232
  70. package/lib/plugins/toolbar/editor-and-toolbar.js.map +1 -1
  71. package/lib/plugins/toolbar/index.js +1 -2
  72. package/lib/plugins/toolbar/index.js.map +1 -1
  73. package/lib/plugins/toolbar/toolbar-buttons.js +1 -1
  74. package/lib/plugins/toolbar/toolbar-buttons.js.map +1 -1
  75. package/lib/plugins/toolbar/toolbar.js +253 -239
  76. package/lib/plugins/toolbar/toolbar.js.map +1 -1
  77. package/lib/plugins/utils.js +27 -2
  78. package/lib/plugins/utils.js.map +1 -1
  79. package/lib/serialization.js +1 -1
  80. package/lib/serialization.js.map +1 -1
  81. package/lib/slate-editor.js +302 -0
  82. package/lib/test-serializer.js +189 -0
  83. package/lib/test-serializer.js.map +1 -0
  84. package/lib/theme.js +1 -1
  85. package/lib/theme.js.map +1 -1
  86. package/package.json +18 -14
  87. package/playground/image/data.js +20 -20
  88. package/playground/image/index.html +22 -20
  89. package/playground/image/index.jsx +12 -10
  90. package/playground/index.html +25 -23
  91. package/playground/mathquill/index.html +23 -20
  92. package/playground/mathquill/index.jsx +18 -22
  93. package/playground/prod-test/index.html +24 -20
  94. package/playground/prod-test/index.jsx +5 -3
  95. package/playground/schema-override/data.js +10 -10
  96. package/playground/schema-override/image-plugin.jsx +3 -4
  97. package/playground/schema-override/index.html +21 -19
  98. package/playground/schema-override/index.jsx +13 -14
  99. package/playground/serialization/data.js +10 -10
  100. package/playground/serialization/image-plugin.jsx +3 -4
  101. package/playground/serialization/index.html +22 -20
  102. package/playground/table-examples.html +5 -8
  103. package/playground/webpack.config.js +10 -10
  104. package/src/components.js +135 -0
  105. package/src/editor.jsx +478 -141
  106. package/src/index.jsx +71 -95
  107. package/src/new-serialization.jsx +291 -0
  108. package/src/parse-html.js +1 -1
  109. package/src/plugins/characters/custom-popper.js +7 -7
  110. package/src/plugins/characters/index.jsx +33 -34
  111. package/src/plugins/characters/utils.js +81 -81
  112. package/src/plugins/hotKeys/index.js +54 -0
  113. package/src/plugins/image/alt-dialog.jsx +4 -5
  114. package/src/plugins/image/component.jsx +106 -89
  115. package/src/plugins/image/image-toolbar.jsx +27 -19
  116. package/src/plugins/image/index.jsx +75 -43
  117. package/src/plugins/image/insert-image-handler.js +62 -27
  118. package/src/plugins/index.jsx +23 -41
  119. package/src/plugins/list/index.jsx +70 -95
  120. package/src/plugins/math/index.jsx +102 -82
  121. package/src/plugins/media/index.jsx +159 -124
  122. package/src/plugins/media/media-dialog.js +98 -71
  123. package/src/plugins/media/media-toolbar.jsx +8 -8
  124. package/src/plugins/media/media-wrapper.jsx +29 -30
  125. package/src/plugins/respArea/drag-in-the-blank/choice.jsx +21 -19
  126. package/src/plugins/respArea/drag-in-the-blank/index.jsx +14 -11
  127. package/src/plugins/respArea/explicit-constructed-response/index.jsx +7 -6
  128. package/src/plugins/respArea/icons/index.jsx +11 -14
  129. package/src/plugins/respArea/index.jsx +92 -52
  130. package/src/plugins/respArea/inline-dropdown/index.jsx +9 -8
  131. package/src/plugins/respArea/utils.jsx +26 -35
  132. package/src/plugins/table/icons/index.jsx +17 -11
  133. package/src/plugins/table/index.jsx +288 -231
  134. package/src/plugins/table/table-toolbar.jsx +15 -11
  135. package/src/plugins/toolbar/default-toolbar.jsx +65 -19
  136. package/src/plugins/toolbar/done-button.jsx +4 -4
  137. package/src/plugins/toolbar/editor-and-toolbar.jsx +150 -145
  138. package/src/plugins/toolbar/index.jsx +2 -3
  139. package/src/plugins/toolbar/toolbar-buttons.jsx +11 -11
  140. package/src/plugins/toolbar/toolbar.jsx +244 -221
  141. package/src/plugins/utils.js +21 -4
  142. package/src/serialization.jsx +32 -32
  143. package/src/test-serializer.js +139 -0
  144. package/src/test-serializer.js.rej +20 -0
package/src/editor.jsx CHANGED
@@ -1,21 +1,327 @@
1
- import { Editor as SlateEditor, findNode, getEventRange, getEventTransfer } from 'slate-react';
2
- import SlateTypes from 'slate-prop-types';
1
+ import React, { useCallback, useMemo, useRef, useState, useEffect } from 'react';
2
+ import {
3
+ Editor as OldSlateEditor,
4
+ findNode,
5
+ getEventRange,
6
+ getEventTransfer,
7
+ useSlateStatic
8
+ } from 'slate-react';
9
+ import RootRef from '@material-ui/core/RootRef';
3
10
 
4
11
  import isEqual from 'lodash/isEqual';
5
- import * as serialization from './serialization';
12
+ import * as serialization from './new-serialization';
6
13
  import PropTypes from 'prop-types';
7
- import React from 'react';
8
14
  import { Value, Block, Inline } from 'slate';
9
- import { buildPlugins, ALL_PLUGINS, DEFAULT_PLUGINS } from './plugins';
15
+ import { ALL_PLUGINS, DEFAULT_PLUGINS, buildPlugins, withPlugins } from './plugins';
10
16
  import debug from 'debug';
11
17
  import { withStyles } from '@material-ui/core/styles';
12
18
  import classNames from 'classnames';
13
19
  import { color } from '@pie-lib/render-ui';
14
20
  import Plain from 'slate-plain-serializer';
15
21
 
16
- import { getBase64 } from './serialization';
22
+ import { getBase64, htmlToValue, valueToHtml } from './new-serialization';
17
23
  import InsertImageHandler from './plugins/image/insert-image-handler';
18
24
 
25
+ import isHotkey from 'is-hotkey';
26
+ import { Editable, useFocused, withReact, useSlate, Slate } from 'slate-react';
27
+ import { Editor, Transforms, createEditor, Element as SlateElement } from 'slate';
28
+ import { withHistory } from 'slate-history';
29
+ import { MathPreview } from '@pie-lib/math-toolbar';
30
+
31
+ import { Button, Icon, Toolbar } from './components';
32
+ import EditorAndToolbar from './plugins/toolbar/editor-and-toolbar';
33
+
34
+ const HOTKEYS = {
35
+ 'mod+b': 'bold',
36
+ 'mod+i': 'italic',
37
+ 'mod+u': 'underline',
38
+ 'mod+`': 'code'
39
+ };
40
+
41
+ const LIST_TYPES = ['numbered-list', 'bulleted-list'];
42
+ const TEXT_ALIGN_TYPES = ['left', 'center', 'right', 'justify'];
43
+
44
+ const initialValue = [
45
+ {
46
+ type: 'paragraph',
47
+ children: [
48
+ {
49
+ type: 'math',
50
+ data: {
51
+ latex: '\\frac{1}{2}',
52
+ wrapper: 'round_brackets'
53
+ },
54
+ children: [
55
+ {
56
+ text: '\\(\\frac{1}{2}\\)'
57
+ }
58
+ ]
59
+ }
60
+ ]
61
+ }
62
+ ];
63
+
64
+ const SlateEditor = editorProps => {
65
+ const { value, plugins } = editorProps;
66
+ const renderElement = useCallback(props => <Element {...props} plugins={plugins} />, []);
67
+ const renderLeaf = useCallback(props => <Leaf {...props} />, []);
68
+ const editor = useMemo(() => withPlugins(createEditor(), plugins), []);
69
+ const [isFocused, setIsFocused] = useState(false);
70
+ const editorRef = useRef(null);
71
+
72
+ useEffect(() => {
73
+ if (editorProps.onEditor) {
74
+ editorProps.onEditor(editor);
75
+ }
76
+ }, [editor]);
77
+
78
+ const slateValue = useMemo(() => {
79
+ // Slate throws an error if the value on the initial render is invalid
80
+ // so we directly set the value on the editor in order
81
+ // to be able to trigger normalization on the initial value before rendering
82
+ editor.children = value;
83
+ Editor.normalize(editor, { force: true });
84
+ // We return the normalized internal value so that the rendering can take over from here
85
+ return editor.children;
86
+ }, [editor, value]);
87
+
88
+ const onKeyDown = event => {
89
+ if (event.key === 'Enter' && event.shiftKey === true) {
90
+ editor.insertText('\n');
91
+ event.preventDefault();
92
+ event.stopPropagation();
93
+ return;
94
+ }
95
+ for (const hotkey in HOTKEYS) {
96
+ if (isHotkey(hotkey, event)) {
97
+ event.preventDefault();
98
+ const mark = HOTKEYS[hotkey];
99
+ toggleMark(editor, mark);
100
+ }
101
+ }
102
+ };
103
+ const onFocus = () => setIsFocused(true);
104
+ const onBlur = () => {
105
+ setTimeout(() => {
106
+ if (!editorRef.current || !editorRef.current.contains(document.activeElement)) {
107
+ setIsFocused(false);
108
+ }
109
+ }, 50);
110
+ };
111
+
112
+ return (
113
+ <Slate editor={editor} value={slateValue}>
114
+ <RootRef rootRef={editorRef}>
115
+ <EditorAndToolbar
116
+ {...editorProps}
117
+ editor={editor}
118
+ isFocused={isFocused}
119
+ onDone={() => {
120
+ setIsFocused(false);
121
+ editorProps.onDone(editor);
122
+ }}
123
+ >
124
+ <Editable
125
+ renderElement={renderElement}
126
+ renderLeaf={renderLeaf}
127
+ placeholder="Enter some rich text…"
128
+ spellCheck
129
+ onKeyDown={onKeyDown}
130
+ onFocus={onFocus}
131
+ onBlur={onBlur}
132
+ />
133
+ </EditorAndToolbar>
134
+ </RootRef>
135
+ </Slate>
136
+ );
137
+ };
138
+
139
+ const toggleBlock = (editor, format) => {
140
+ const isActive = isBlockActive(
141
+ editor,
142
+ format,
143
+ TEXT_ALIGN_TYPES.includes(format) ? 'align' : 'type'
144
+ );
145
+ const isList = LIST_TYPES.includes(format);
146
+
147
+ Transforms.unwrapNodes(editor, {
148
+ match: n =>
149
+ !Editor.isEditor(n) &&
150
+ SlateElement.isElement(n) &&
151
+ LIST_TYPES.includes(n.type) &&
152
+ !TEXT_ALIGN_TYPES.includes(format),
153
+ split: true
154
+ });
155
+ let newProperties;
156
+ if (TEXT_ALIGN_TYPES.includes(format)) {
157
+ newProperties = {
158
+ align: isActive ? undefined : format
159
+ };
160
+ } else {
161
+ newProperties = {
162
+ type: isActive ? 'paragraph' : isList ? 'list_item' : format
163
+ };
164
+ }
165
+ Transforms.setNodes(editor, newProperties);
166
+
167
+ if (!isActive && isList) {
168
+ const block = { type: format, children: [] };
169
+ Transforms.wrapNodes(editor, block);
170
+ }
171
+ };
172
+
173
+ const toggleMark = (editor, format) => {
174
+ const isActive = isMarkActive(editor, format);
175
+
176
+ if (isActive) {
177
+ Editor.removeMark(editor, format);
178
+ } else {
179
+ Editor.addMark(editor, format, true);
180
+ }
181
+ };
182
+
183
+ const isBlockActive = (editor, format, blockType = 'type') => {
184
+ const { selection } = editor;
185
+ if (!selection) return false;
186
+
187
+ const [match] = Array.from(
188
+ Editor.nodes(editor, {
189
+ at: Editor.unhangRange(editor, selection),
190
+ match: n => !Editor.isEditor(n) && SlateElement.isElement(n) && n[blockType] === format
191
+ })
192
+ );
193
+
194
+ return !!match;
195
+ };
196
+
197
+ const isMarkActive = (editor, format) => {
198
+ const marks = Editor.marks(editor);
199
+ return marks ? marks[format] === true : false;
200
+ };
201
+
202
+ const Element = props => {
203
+ const editor = useSlateStatic();
204
+ const focused = useFocused();
205
+ const { attributes, children, element, plugins } = props;
206
+ const style = { textAlign: element.align };
207
+
208
+ const nodeProps = { ...attributes, ...props, node: { ...element }, children };
209
+ const pluginToRender = plugins.find(
210
+ plugin => typeof plugin.supports === 'function' && plugin.supports(element)
211
+ );
212
+
213
+ if (pluginToRender) {
214
+ return pluginToRender.renderNode({ ...nodeProps, editor, focused });
215
+ }
216
+
217
+ switch (element.type) {
218
+ case 'block-quote':
219
+ return (
220
+ <blockquote style={style} {...attributes}>
221
+ {children}
222
+ </blockquote>
223
+ );
224
+ case 'bulleted-list':
225
+ return (
226
+ <ul style={style} {...attributes}>
227
+ {children}
228
+ </ul>
229
+ );
230
+ case 'heading-one':
231
+ return (
232
+ <h1 style={style} {...attributes}>
233
+ {children}
234
+ </h1>
235
+ );
236
+ case 'heading-two':
237
+ return (
238
+ <h2 style={style} {...attributes}>
239
+ {children}
240
+ </h2>
241
+ );
242
+ case 'list-item':
243
+ return (
244
+ <li style={style} {...attributes}>
245
+ {children}
246
+ </li>
247
+ );
248
+ case 'numbered-list':
249
+ return (
250
+ <ol style={style} {...attributes}>
251
+ {children}
252
+ </ol>
253
+ );
254
+ default:
255
+ return (
256
+ <div
257
+ style={{
258
+ ...style,
259
+ margin: 0
260
+ }}
261
+ {...attributes}
262
+ >
263
+ {children}
264
+ </div>
265
+ );
266
+ }
267
+ };
268
+
269
+ const Leaf = ({ attributes, children, leaf }) => {
270
+ if (leaf.bold) {
271
+ children = <strong>{children}</strong>;
272
+ }
273
+
274
+ if (leaf.code) {
275
+ children = <code>{children}</code>;
276
+ }
277
+
278
+ if (leaf.italic) {
279
+ children = <em>{children}</em>;
280
+ }
281
+
282
+ if (leaf.underline) {
283
+ children = <u>{children}</u>;
284
+ }
285
+
286
+ if (leaf.strikethrough) {
287
+ children = <del>{children}</del>;
288
+ }
289
+
290
+ return <span {...attributes}>{children}</span>;
291
+ };
292
+
293
+ const BlockButton = ({ format, icon }) => {
294
+ const editor = useSlate();
295
+ return (
296
+ <Button
297
+ active={isBlockActive(editor, format, TEXT_ALIGN_TYPES.includes(format) ? 'align' : 'type')}
298
+ onMouseDown={event => {
299
+ event.preventDefault();
300
+ toggleBlock(editor, format);
301
+ }}
302
+ >
303
+ <Icon>{icon}</Icon>
304
+ </Button>
305
+ );
306
+ };
307
+
308
+ const MarkButton = ({ format, icon }) => {
309
+ const editor = useSlate();
310
+ return (
311
+ <Button
312
+ active={isMarkActive(editor, format)}
313
+ onMouseDown={event => {
314
+ event.preventDefault();
315
+ toggleMark(editor, format);
316
+ }}
317
+ >
318
+ <Icon>{icon}</Icon>
319
+ </Button>
320
+ );
321
+ };
322
+
323
+ // old-editable
324
+
19
325
  export { ALL_PLUGINS, DEFAULT_PLUGINS, serialization };
20
326
 
21
327
  const log = debug('editable-html:editor');
@@ -25,41 +331,44 @@ const defaultToolbarOpts = {
25
331
  alignment: 'left',
26
332
  alwaysVisible: false,
27
333
  showDone: true,
28
- doneOn: 'blur',
334
+ doneOn: 'blur'
29
335
  };
30
336
 
31
337
  const defaultResponseAreaProps = {
32
338
  options: {},
33
339
  respAreaToolbar: () => {},
34
- onHandleAreaChange: () => {},
340
+ onHandleAreaChange: () => {}
35
341
  };
36
342
 
37
343
  const defaultLanguageCharactersProps = [];
38
344
 
39
- const createToolbarOpts = (toolbarOpts, error) => {
345
+ const createToolbarOpts = toolbarOpts => {
40
346
  return {
41
347
  ...defaultToolbarOpts,
42
- ...toolbarOpts,
43
- error,
348
+ ...toolbarOpts
44
349
  };
45
350
  };
46
351
 
47
- export class Editor extends React.Component {
352
+ export class EditorComponent extends React.Component {
48
353
  static propTypes = {
49
354
  autoFocus: PropTypes.bool,
50
- editorRef: PropTypes.func.isRequired,
51
355
  onRef: PropTypes.func.isRequired,
52
356
  onChange: PropTypes.func.isRequired,
357
+ onEditor: PropTypes.func,
53
358
  onFocus: PropTypes.func,
54
359
  onBlur: PropTypes.func,
55
360
  onKeyDown: PropTypes.func,
56
- focus: PropTypes.func.isRequired,
57
- value: SlateTypes.value.isRequired,
361
+ value: PropTypes.arrayOf(
362
+ PropTypes.shape({
363
+ type: PropTypes.string,
364
+ children: PropTypes.array,
365
+ data: PropTypes.object
366
+ })
367
+ ),
58
368
  imageSupport: PropTypes.object,
59
- disableImageAlignmentButtons: PropTypes.bool,
60
369
  uploadSoundSupport: PropTypes.shape({
61
370
  add: PropTypes.func,
62
- delete: PropTypes.func,
371
+ delete: PropTypes.func
63
372
  }),
64
373
  charactersLimit: PropTypes.number,
65
374
  width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
@@ -77,33 +386,40 @@ export class Editor extends React.Component {
77
386
  pluginProps: PropTypes.any,
78
387
  placeholder: PropTypes.string,
79
388
  responseAreaProps: PropTypes.shape({
80
- type: PropTypes.oneOf(['explicit-constructed-response', 'inline-dropdown', 'drag-in-the-blank']),
389
+ type: PropTypes.oneOf([
390
+ 'explicit-constructed-response',
391
+ 'inline-dropdown',
392
+ 'drag-in-the-blank'
393
+ ]),
81
394
  options: PropTypes.object,
82
395
  respAreaToolbar: PropTypes.func,
83
- onHandleAreaChange: PropTypes.func,
396
+ onHandleAreaChange: PropTypes.func
84
397
  }),
85
398
  languageCharactersProps: PropTypes.arrayOf(
86
399
  PropTypes.shape({
87
400
  language: PropTypes.string,
88
401
  characterIcon: PropTypes.string,
89
- characters: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.string)),
90
- }),
402
+ characters: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.string))
403
+ })
91
404
  ),
92
405
  toolbarOpts: PropTypes.shape({
93
406
  position: PropTypes.oneOf(['bottom', 'top']),
94
407
  alignment: PropTypes.oneOf(['left', 'right']),
95
408
  alwaysVisible: PropTypes.bool,
96
409
  showDone: PropTypes.bool,
97
- doneOn: PropTypes.string,
410
+ doneOn: PropTypes.string
98
411
  }),
99
- activePlugins: PropTypes.arrayOf((values) => {
100
- const allValid = values.every((v) => ALL_PLUGINS.includes(v));
412
+ activePlugins: PropTypes.arrayOf(values => {
413
+ const allValid = values.every(v => ALL_PLUGINS.includes(v));
101
414
 
102
- return !allValid && new Error(`Invalid values: ${values}, values must be one of [${ALL_PLUGINS.join(',')}]`);
415
+ return (
416
+ !allValid &&
417
+ new Error(`Invalid values: ${values}, values must be one of [${ALL_PLUGINS.join(',')}]`)
418
+ );
103
419
  }),
104
420
  className: PropTypes.string,
105
421
  maxImageWidth: PropTypes.number,
106
- maxImageHeight: PropTypes.number,
422
+ maxImageHeight: PropTypes.number
107
423
  };
108
424
 
109
425
  static defaultProps = {
@@ -113,14 +429,14 @@ export class Editor extends React.Component {
113
429
  onKeyDown: () => {},
114
430
  toolbarOpts: defaultToolbarOpts,
115
431
  responseAreaProps: defaultResponseAreaProps,
116
- languageCharactersProps: defaultLanguageCharactersProps,
432
+ languageCharactersProps: defaultLanguageCharactersProps
117
433
  };
118
434
 
119
435
  constructor(props) {
120
436
  super(props);
121
437
  this.state = {
122
438
  value: props.value,
123
- toolbarOpts: createToolbarOpts(props.toolbarOpts, props.error),
439
+ toolbarOpts: createToolbarOpts(props.toolbarOpts)
124
440
  };
125
441
 
126
442
  this.onResize = () => {
@@ -130,31 +446,30 @@ export class Editor extends React.Component {
130
446
  this.handlePlugins(this.props);
131
447
  }
132
448
 
133
- handlePlugins = (props) => {
449
+ handlePlugins = props => {
134
450
  const normalizedResponseAreaProps = {
135
451
  ...defaultResponseAreaProps,
136
- ...props.responseAreaProps,
452
+ ...props.responseAreaProps
137
453
  };
138
454
 
139
455
  this.plugins = buildPlugins(props.activePlugins, {
140
456
  math: {
141
457
  onClick: this.onMathClick,
142
458
  onFocus: this.onPluginFocus,
143
- onBlur: this.onPluginBlur,
459
+ onBlur: this.onPluginBlur
144
460
  },
145
461
  image: {
146
- disableImageAlignmentButtons: props.disableImageAlignmentButtons,
147
462
  onDelete:
148
463
  props.imageSupport &&
149
464
  props.imageSupport.delete &&
150
465
  ((src, done) => {
151
- props.imageSupport.delete(src, (e) => {
466
+ props.imageSupport.delete(src, e => {
152
467
  done(e, this.state.value);
153
468
  });
154
469
  }),
155
470
  insertImageRequested:
156
471
  props.imageSupport &&
157
- ((getHandler) => {
472
+ (getHandler => {
158
473
  /**
159
474
  * The handler is the object through which the outer context
160
475
  * communicates file upload events like: fileChosen, cancel, progress
@@ -165,7 +480,7 @@ export class Editor extends React.Component {
165
480
  onFocus: this.onPluginFocus,
166
481
  onBlur: this.onPluginBlur,
167
482
  maxImageWidth: this.props.maxImageWidth,
168
- maxImageHeight: this.props.maxImageHeight,
483
+ maxImageHeight: this.props.maxImageHeight
169
484
  },
170
485
  toolbar: {
171
486
  /**
@@ -174,22 +489,7 @@ export class Editor extends React.Component {
174
489
  */
175
490
  disableScrollbar: !!props.disableScrollbar,
176
491
  disableUnderline: props.disableUnderline,
177
- autoWidth: props.autoWidthToolbar,
178
- onDone: () => {
179
- const { nonEmpty } = props;
180
-
181
- log('[onDone]');
182
- this.setState({ toolbarInFocus: false, focusedNode: null });
183
- this.editor.blur();
184
-
185
- if (nonEmpty && this.state.value.startText?.text?.length === 0) {
186
- this.resetValue(true).then(() => {
187
- this.onEditingDone();
188
- });
189
- } else {
190
- this.onEditingDone();
191
- }
192
- },
492
+ autoWidth: props.autoWidthToolbar
193
493
  },
194
494
  table: {
195
495
  onFocus: () => {
@@ -199,7 +499,7 @@ export class Editor extends React.Component {
199
499
  onBlur: () => {
200
500
  log('[table:onBlur]...');
201
501
  this.onPluginBlur();
202
- },
502
+ }
203
503
  },
204
504
  responseArea: {
205
505
  type: normalizedResponseAreaProps.type,
@@ -215,15 +515,14 @@ export class Editor extends React.Component {
215
515
  onBlur: () => {
216
516
  log('[table:onBlur]...');
217
517
  this.onPluginBlur();
218
- },
518
+ }
219
519
  },
220
520
  languageCharacters: props.languageCharactersProps,
221
521
  media: {
222
522
  focus: this.focus,
223
- createChange: () => this.state.value.change(),
224
523
  onChange: this.onChange,
225
- uploadSoundSupport: props.uploadSoundSupport,
226
- },
524
+ uploadSoundSupport: props.uploadSoundSupport
525
+ }
227
526
  });
228
527
  };
229
528
 
@@ -236,9 +535,9 @@ export class Editor extends React.Component {
236
535
  if (this.editor && this.props.autoFocus) {
237
536
  Promise.resolve().then(() => {
238
537
  if (this.editor) {
239
- const editorDOM = document.querySelector(`[data-key="${this.editor.value.document.key}"]`);
240
-
241
- this.editor.focus();
538
+ const editorDOM = document.querySelector(
539
+ `[data-key="${this.editor.value.document.key}"]`
540
+ );
242
541
 
243
542
  if (editorDOM) {
244
543
  editorDOM.focus();
@@ -250,11 +549,11 @@ export class Editor extends React.Component {
250
549
 
251
550
  componentWillReceiveProps(nextProps) {
252
551
  const { toolbarOpts } = this.state;
253
- const newToolbarOpts = createToolbarOpts(nextProps.toolbarOpts, nextProps.error);
552
+ const newToolbarOpts = createToolbarOpts(nextProps.toolbarOpts);
254
553
 
255
554
  if (!isEqual(newToolbarOpts, toolbarOpts)) {
256
555
  this.setState({
257
- toolbarOpts: newToolbarOpts,
556
+ toolbarOpts: newToolbarOpts
258
557
  });
259
558
  }
260
559
 
@@ -268,13 +567,13 @@ export class Editor extends React.Component {
268
567
  // so we increase the width to at least 2px in order for the user to see it
269
568
  const zeroWidthEls = document.querySelectorAll('[data-slate-zero-width="z"]');
270
569
 
271
- Array.from(zeroWidthEls).forEach((el) => {
570
+ Array.from(zeroWidthEls).forEach(el => {
272
571
  el.style.minWidth = '2px';
273
572
  el.style.display = 'inline-block';
274
573
  });
275
574
  }
276
575
 
277
- onPluginBlur = (e) => {
576
+ onPluginBlur = e => {
278
577
  log('[onPluginBlur]', e && e.relatedTarget);
279
578
  const target = e && e.relatedTarget;
280
579
 
@@ -285,7 +584,7 @@ export class Editor extends React.Component {
285
584
  });
286
585
  };
287
586
 
288
- onPluginFocus = (e) => {
587
+ onPluginFocus = e => {
289
588
  log('[onPluginFocus]', e && e.target);
290
589
  const target = e && e.target;
291
590
  if (target) {
@@ -300,16 +599,24 @@ export class Editor extends React.Component {
300
599
  this.stashValue();
301
600
  };
302
601
 
303
- onMathClick = (node) => {
304
- this.editor.change((c) => c.collapseToStartOf(node));
602
+ onMathClick = node => {
305
603
  this.setState({ selectedNode: node });
306
604
  };
307
605
 
308
- onEditingDone = () => {
606
+ onEditingDone = editor => {
309
607
  log('[onEditingDone]');
310
608
  this.setState({ stashedValue: null, focusedNode: null });
311
609
  log('[onEditingDone] value: ', this.state.value);
312
- this.props.onChange(this.state.value, true);
610
+ this.props.onChange(editor, true);
611
+ };
612
+
613
+ onDone = editor => {
614
+ const { nonEmpty } = this.props;
615
+
616
+ log('[onDone]');
617
+ this.setState({ toolbarInFocus: false, focusedNode: null });
618
+
619
+ this.onEditingDone(editor);
313
620
  };
314
621
 
315
622
  /**
@@ -320,18 +627,14 @@ export class Editor extends React.Component {
320
627
  }
321
628
 
322
629
  // Allowing time for onChange to take effect if it is called
323
- handleBlur = (resolve) => {
630
+ handleBlur = resolve => {
324
631
  const { nonEmpty } = this.props;
325
632
  const {
326
- toolbarOpts: { doneOn },
633
+ toolbarOpts: { doneOn }
327
634
  } = this.state;
328
635
 
329
636
  this.setState({ toolbarInFocus: false, focusedNode: null });
330
637
 
331
- if (this.editor) {
332
- this.editor.blur();
333
- }
334
-
335
638
  if (doneOn === 'blur') {
336
639
  if (nonEmpty && this.state.value.startText?.text?.length === 0) {
337
640
  this.resetValue(true).then(() => {
@@ -345,7 +648,7 @@ export class Editor extends React.Component {
345
648
  }
346
649
  };
347
650
 
348
- onBlur = (event) => {
651
+ onBlur = event => {
349
652
  log('[onBlur]');
350
653
  const target = event.relatedTarget;
351
654
 
@@ -353,16 +656,16 @@ export class Editor extends React.Component {
353
656
 
354
657
  log('[onBlur] node: ', node);
355
658
 
356
- return new Promise((resolve) => {
659
+ return new Promise(resolve => {
357
660
  this.setState(
358
661
  { preBlurValue: this.state.value, focusedNode: !node ? null : node },
359
- this.handleBlur.bind(this, resolve),
662
+ this.handleBlur.bind(this, resolve)
360
663
  );
361
664
  this.props.onBlur(event);
362
665
  });
363
666
  };
364
667
 
365
- handleDomBlur = (e) => {
668
+ handleDomBlur = e => {
366
669
  const editorDOM = document.querySelector(`[data-key="${this.state.value.document.key}"]`);
367
670
 
368
671
  setTimeout(() => {
@@ -372,10 +675,13 @@ export class Editor extends React.Component {
372
675
  return;
373
676
  }
374
677
 
375
- const editorElement = !editorDOM || document.activeElement.closest(`[class*="${editorDOM.className}"]`);
678
+ const editorElement =
679
+ !editorDOM || document.activeElement.closest(`[class*="${editorDOM.className}"]`);
376
680
  const toolbarElement =
377
- !this.toolbarRef || document.activeElement.closest(`[class*="${this.toolbarRef.className}"]`);
378
- const isInCurrentComponent = this.wrapperRef.contains(editorElement) || this.wrapperRef.contains(toolbarElement);
681
+ !this.toolbarRef ||
682
+ document.activeElement.closest(`[class*="${this.toolbarRef.className}"]`);
683
+ const isInCurrentComponent =
684
+ this.wrapperRef.contains(editorElement) || this.wrapperRef.contains(toolbarElement);
379
685
 
380
686
  if (!isInCurrentComponent) {
381
687
  editorDOM.removeEventListener('blur', this.handleDomBlur);
@@ -395,7 +701,7 @@ export class Editor extends React.Component {
395
701
  * Note: The use of promises has been causing issues with MathQuill
396
702
  * */
397
703
  onFocus = () =>
398
- new Promise((resolve) => {
704
+ new Promise(resolve => {
399
705
  const editorDOM = document.querySelector(`[data-key="${this.state.value.document.key}"]`);
400
706
 
401
707
  log('[onFocus]', document.activeElement);
@@ -443,7 +749,7 @@ export class Editor extends React.Component {
443
749
  /**
444
750
  * Reset the value if the user didn't click done.
445
751
  */
446
- resetValue = (force) => {
752
+ resetValue = force => {
447
753
  const { value, focusedNode } = this.state;
448
754
 
449
755
  const stopReset = this.plugins.reduce((s, p) => {
@@ -459,7 +765,7 @@ export class Editor extends React.Component {
459
765
  const newValue = Value.fromJSON(this.state.stashedValue.toJSON());
460
766
 
461
767
  log('newValue: ', newValue.document);
462
- return new Promise((resolve) => {
768
+ return new Promise(resolve => {
463
769
  setTimeout(() => {
464
770
  this.setState({ value: newValue, stashedValue: null }, () => {
465
771
  log('value now: ', this.state.value.document.toJSON());
@@ -472,16 +778,18 @@ export class Editor extends React.Component {
472
778
  }
473
779
  };
474
780
 
475
- onChange = (change, done) => {
781
+ onChange = (editor, done) => {
476
782
  log('[onChange]');
477
-
478
- const { value } = change;
479
783
  const { charactersLimit } = this.props;
784
+ const allText = Editor.string(editor, []);
480
785
 
481
- if (value && value.document && value.document.text && value.document.text.length > charactersLimit) {
786
+ if (allText > charactersLimit) {
482
787
  return;
483
788
  }
484
789
 
790
+ const html = valueToHtml(editor);
791
+ const value = htmlToValue(html);
792
+
485
793
  this.setState({ value }, () => {
486
794
  log('[onChange], call done()');
487
795
 
@@ -500,15 +808,15 @@ export class Editor extends React.Component {
500
808
  };
501
809
 
502
810
  UNSAFE_componentWillReceiveProps(props) {
503
- if (!props.value.document.equals(this.props.value.document)) {
811
+ if (!isEqual(props.value, this.props.value)) {
504
812
  this.setState({
505
813
  focus: false,
506
- value: props.value,
814
+ value: props.value
507
815
  });
508
816
  }
509
817
  }
510
818
 
511
- valueToSize = (v) => {
819
+ valueToSize = v => {
512
820
  if (!v) {
513
821
  return;
514
822
  }
@@ -537,11 +845,11 @@ export class Editor extends React.Component {
537
845
  width: this.valueToSize(width),
538
846
  height: this.valueToSize(height),
539
847
  minHeight: this.valueToSize(minHeight),
540
- maxHeight: this.valueToSize(maxHeight),
848
+ maxHeight: this.valueToSize(maxHeight)
541
849
  };
542
850
  }
543
851
 
544
- validateNode = (node) => {
852
+ validateNode = node => {
545
853
  if (node.object !== 'block') return;
546
854
 
547
855
  const last = node.nodes.last();
@@ -558,25 +866,6 @@ export class Editor extends React.Component {
558
866
  return undefined;
559
867
  };
560
868
 
561
- changeData = (key, data) => {
562
- log('[changeData]. .. ', key, data);
563
-
564
- /**
565
- * HACK ALERT: We should be calling setState here and storing the change data:
566
- *
567
- * <code>this.setState({changeData: { key, data}})</code>
568
- * However this is causing issues with the Mathquill instance. The 'input' event stops firing on the element and no
569
- * more changes get through. The issues seem to be related to the promises in onBlur/onFocus. But removing these
570
- * brings it's own problems. A major clean up is planned for this component so I've decided to temporarily settle
571
- * on this hack rather than spend more time on this.
572
- */
573
-
574
- // Uncomment this line to see the bug described above.
575
- // this.setState({changeData: {key, data}})
576
-
577
- this.__TEMPORARY_CHANGE_DATA = { key, data };
578
- };
579
-
580
869
  focus = (pos, node) => {
581
870
  const position = pos || 'end';
582
871
 
@@ -592,7 +881,10 @@ export class Editor extends React.Component {
592
881
  const fragment = transfer.fragment;
593
882
  const text = transfer.text;
594
883
 
595
- if (file && (file.type === 'image/jpeg' || file.type === 'image/jpg' || file.type === 'image/png')) {
884
+ if (
885
+ file &&
886
+ (file.type === 'image/jpeg' || file.type === 'image/jpg' || file.type === 'image/png')
887
+ ) {
596
888
  if (!this.props.imageSupport) {
597
889
  return;
598
890
  }
@@ -604,8 +896,8 @@ export class Editor extends React.Component {
604
896
  isVoid: true,
605
897
  data: {
606
898
  loading: false,
607
- src,
608
- },
899
+ src
900
+ }
609
901
  });
610
902
 
611
903
  if (dropContext) {
@@ -631,7 +923,7 @@ export class Editor extends React.Component {
631
923
  return;
632
924
  }
633
925
  const {
634
- value: { document, selection, startBlock },
926
+ value: { document, selection, startBlock }
635
927
  } = change;
636
928
 
637
929
  if (startBlock.isVoid) {
@@ -642,13 +934,13 @@ export class Editor extends React.Component {
642
934
  const defaultMarks = document.getInsertMarksAtRange(selection);
643
935
  const frag = Plain.deserialize(text, {
644
936
  defaultBlock,
645
- defaultMarks,
937
+ defaultMarks
646
938
  }).document;
647
939
  change.insertFragment(frag);
648
940
  }
649
941
  };
650
942
 
651
- renderPlaceholder = (props) => {
943
+ renderPlaceholder = props => {
652
944
  const { editor } = props;
653
945
  const { document } = editor.value;
654
946
 
@@ -666,7 +958,7 @@ export class Editor extends React.Component {
666
958
  whiteSpace: 'nowrap',
667
959
  opacity: '0.33',
668
960
  pointerEvents: 'none',
669
- userSelect: 'none',
961
+ userSelect: 'none'
670
962
  }}
671
963
  >
672
964
  {editor.props.placeholder}
@@ -683,7 +975,7 @@ export class Editor extends React.Component {
683
975
  className,
684
976
  placeholder,
685
977
  pluginProps,
686
- onKeyDown,
978
+ onKeyDown
687
979
  } = this.props;
688
980
 
689
981
  const { value, focusedNode, toolbarOpts } = this.state;
@@ -693,22 +985,69 @@ export class Editor extends React.Component {
693
985
  const names = classNames(
694
986
  {
695
987
  [classes.withBg]: highlightShape,
696
- [classes.toolbarOnTop]: toolbarOpts.alwaysVisible && toolbarOpts.position === 'top',
988
+ [classes.toolbarOnTop]: toolbarOpts.alwaysVisible && toolbarOpts.position === 'top'
697
989
  },
698
990
  className,
991
+ classes.slateEditor
699
992
  );
700
993
 
701
994
  return (
702
- <div ref={(ref) => (this.wrapperRef = ref)} style={{ width: sizeStyle.width }} className={names}>
995
+ <div
996
+ ref={ref => (this.wrapperRef = ref)}
997
+ style={{ width: sizeStyle.width }}
998
+ className={names}
999
+ >
703
1000
  <SlateEditor
704
1001
  plugins={this.plugins}
705
- innerRef={(r) => {
1002
+ toolbarRef={r => {
706
1003
  if (r) {
707
- this.slateEditor = r;
1004
+ this.toolbarRef = r;
708
1005
  }
709
1006
  }}
710
- ref={(r) => (this.editor = r && this.props.editorRef(r))}
711
- toolbarRef={(r) => {
1007
+ onEditor={this.props.onEditor}
1008
+ value={value}
1009
+ focus={this.focus}
1010
+ onKeyDown={onKeyDown}
1011
+ onChange={this.onChange}
1012
+ getFocusedValue={this.getFocusedValue}
1013
+ onBlur={this.onBlur}
1014
+ onDrop={(event, editor) => this.onDropPaste(event, editor, true)}
1015
+ onPaste={(event, editor) => this.onDropPaste(event, editor)}
1016
+ onFocus={this.onFocus}
1017
+ onEditingDone={this.onEditingDone}
1018
+ onDone={this.onDone}
1019
+ focusedNode={focusedNode}
1020
+ normalize={this.normalize}
1021
+ readOnly={disabled}
1022
+ spellCheck={spellCheck}
1023
+ className={classNames(
1024
+ {
1025
+ [classes.noPadding]: toolbarOpts && toolbarOpts.noBorder
1026
+ },
1027
+ classes.slateEditor
1028
+ )}
1029
+ style={{
1030
+ minHeight: sizeStyle.minHeight,
1031
+ height: sizeStyle.height,
1032
+ maxHeight: sizeStyle.maxHeight
1033
+ }}
1034
+ pluginProps={pluginProps}
1035
+ toolbarOpts={toolbarOpts}
1036
+ placeholder={placeholder}
1037
+ renderPlaceholder={this.renderPlaceholder}
1038
+ />
1039
+ </div>
1040
+ );
1041
+
1042
+ return (
1043
+ <div
1044
+ ref={ref => (this.wrapperRef = ref)}
1045
+ style={{ width: sizeStyle.width }}
1046
+ className={names}
1047
+ >
1048
+ <OldSlateEditor
1049
+ plugins={this.plugins}
1050
+ toolbarRef={r => {
712
1051
  if (r) {
713
1052
  this.toolbarRef = r;
714
1053
  }
@@ -727,23 +1066,21 @@ export class Editor extends React.Component {
727
1066
  normalize={this.normalize}
728
1067
  readOnly={disabled}
729
1068
  spellCheck={spellCheck}
730
- autoCorrect={spellCheck}
731
1069
  className={classNames(
732
1070
  {
733
- [classes.noPadding]: toolbarOpts && toolbarOpts.noBorder,
1071
+ [classes.noPadding]: toolbarOpts && toolbarOpts.noBorder
734
1072
  },
735
- classes.slateEditor,
1073
+ classes.slateEditor
736
1074
  )}
737
1075
  style={{
738
1076
  minHeight: sizeStyle.minHeight,
739
1077
  height: sizeStyle.height,
740
- maxHeight: sizeStyle.maxHeight,
1078
+ maxHeight: sizeStyle.maxHeight
741
1079
  }}
742
1080
  pluginProps={pluginProps}
743
1081
  toolbarOpts={toolbarOpts}
744
1082
  placeholder={placeholder}
745
1083
  renderPlaceholder={this.renderPlaceholder}
746
- onDataChange={this.changeData}
747
1084
  />
748
1085
  </div>
749
1086
  );
@@ -753,7 +1090,7 @@ export class Editor extends React.Component {
753
1090
  // TODO color - hardcoded gray background and keypad colors will need to change too
754
1091
  const styles = {
755
1092
  withBg: {
756
- backgroundColor: 'rgba(0,0,0,0.06)',
1093
+ backgroundColor: 'rgba(0,0,0,0.06)'
757
1094
  },
758
1095
  slateEditor: {
759
1096
  fontFamily: 'Roboto, sans-serif',
@@ -763,10 +1100,10 @@ const styles = {
763
1100
  width: '100%',
764
1101
  borderCollapse: 'collapse',
765
1102
  color: color.text(),
766
- backgroundColor: color.background(),
1103
+ backgroundColor: color.background()
767
1104
  },
768
1105
  '& table:not([border="1"]) tr': {
769
- borderTop: '1px solid #dfe2e5',
1106
+ borderTop: '1px solid #dfe2e5'
770
1107
  // TODO perhaps secondary color for background, for now disable
771
1108
  // '&:nth-child(2n)': {
772
1109
  // backgroundColor: '#f6f8fa'
@@ -774,18 +1111,18 @@ const styles = {
774
1111
  },
775
1112
  '& td, th': {
776
1113
  padding: '.6em 1em',
777
- textAlign: 'center',
1114
+ textAlign: 'center'
778
1115
  },
779
1116
  '& table:not([border="1"]) td, th': {
780
- border: '1px solid #dfe2e5',
781
- },
1117
+ border: '1px solid #dfe2e5'
1118
+ }
782
1119
  },
783
1120
  toolbarOnTop: {
784
- marginTop: '45px',
1121
+ marginTop: '45px'
785
1122
  },
786
1123
  noPadding: {
787
- padding: '0 !important',
788
- },
1124
+ padding: '0 !important'
1125
+ }
789
1126
  };
790
1127
 
791
- export default withStyles(styles)(Editor);
1128
+ export default withStyles(styles)(EditorComponent);