@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
@@ -1,15 +1,14 @@
1
- import React from 'react';
2
- import { Editor, Inline, Transforms } from 'slate';
3
- import { ReactEditor } from 'slate-react';
4
- import { jsx } from 'slate-hyperscript';
5
1
  import Functions from '@material-ui/icons/Functions';
2
+ import { Inline } from 'slate';
6
3
  import { MathPreview, MathToolbar } from '@pie-lib/math-toolbar';
7
4
  import { wrapMath, unWrapMath, mmlToLatex, renderMath } from '@pie-lib/math-rendering';
5
+ import React from 'react';
8
6
  import debug from 'debug';
7
+ import SlatePropTypes from 'slate-prop-types';
9
8
  import PropTypes from 'prop-types';
10
- import isEqual from 'lodash/isEqual';
11
9
 
12
- import { BLOCK_TAGS } from '../../new-serialization';
10
+ import { BLOCK_TAGS } from '../../serialization';
11
+ import isEqual from 'lodash/isEqual';
13
12
  const log = debug('@pie-lib:editable-html:plugins:math');
14
13
 
15
14
  const TEXT_NODE = 3;
@@ -26,35 +25,43 @@ function generateAdditionalKeys(keyData = []) {
26
25
  // eslint-disable-next-line react/display-name
27
26
  export const CustomToolbarComp = React.memo(
28
27
  (props) => {
29
- const { node, nodePath, onFocus, onBlur, onClick, editor } = props;
28
+ const { node, value, onFocus, onBlur, onClick } = props;
30
29
  const { pluginProps } = props || {};
31
30
  const { math } = pluginProps || {};
32
31
  const { keypadMode, customKeys, controlledKeypadMode = true } = math || {};
33
32
 
34
33
  const onDone = (latex) => {
35
34
  const update = {
36
- ...node.data,
35
+ ...node.data.toObject(),
37
36
  latex,
38
37
  };
39
- editor.apply({
40
- type: 'set_node',
41
- path: nodePath,
42
- properties: {
43
- data: node.data,
44
- },
45
- newProperties: { data: update },
46
- });
47
- ReactEditor.focus(editor);
48
- Transforms.move(editor, { distance: 1, unit: 'offset' });
38
+ const change = value.change().setNodeByKey(node.key, { data: update });
39
+
40
+ const nextText = value.document.getNextText(node.key);
41
+
42
+ change.moveFocusTo(nextText.key, 0).moveAnchorTo(nextText.key, 0);
43
+
44
+ props.onToolbarDone(change, false);
49
45
  };
50
46
 
51
- const latex = node.data.latex;
47
+ const onChange = (latex) => {
48
+ const update = {
49
+ ...node.data.toObject(),
50
+ latex,
51
+ };
52
+ const change = value.change().setNodeByKey(node.key, { data: update });
53
+ log('call onToolbarChange:', change);
54
+ props.onDataChange(node.key, update);
55
+ };
56
+
57
+ const latex = node.data.get('latex');
52
58
 
53
59
  return (
54
60
  <MathToolbar
55
61
  autoFocus
56
62
  additionalKeys={generateAdditionalKeys(customKeys)}
57
63
  latex={latex}
64
+ onChange={onChange}
58
65
  onDone={onDone}
59
66
  onBlur={onBlur}
60
67
  onFocus={onFocus}
@@ -73,33 +80,21 @@ export const CustomToolbarComp = React.memo(
73
80
  const keypadModeChanged = keypadMode !== keypadModeNext;
74
81
  const controlledKeypadModeChanged = controlledKeypadMode !== controlledKeypadModeNext;
75
82
 
76
- const equal = isEqual(node, nodeNext);
83
+ const equal = node.equals(nodeNext);
77
84
  return equal && !keypadModeChanged && !controlledKeypadModeChanged;
78
85
  },
79
86
  );
80
87
 
81
88
  CustomToolbarComp.propTypes = {
82
- editor: PropTypes.object,
83
- node: PropTypes.shape({
84
- type: PropTypes.string,
85
- children: PropTypes.array,
86
- data: PropTypes.object,
87
- }).isRequired,
88
- value: PropTypes.arrayOf(
89
- PropTypes.shape({
90
- type: PropTypes.string,
91
- children: PropTypes.array,
92
- data: PropTypes.object,
93
- }),
94
- ).isRequired,
89
+ node: SlatePropTypes.node.isRequired,
90
+ value: SlatePropTypes.value,
95
91
  onToolbarDone: PropTypes.func,
92
+ onDataChange: PropTypes.func,
96
93
  onFocus: PropTypes.func,
97
94
  onClick: PropTypes.func,
98
95
  onBlur: PropTypes.func,
99
96
  };
100
97
 
101
- const mathTypes = ['math', 'mathml'];
102
-
103
98
  export default function MathPlugin(opts) {
104
99
  MathPlugin.mathMlOptions = {
105
100
  mmlOutput: opts.mmlOutput,
@@ -110,12 +105,13 @@ export default function MathPlugin(opts) {
110
105
  name: 'math',
111
106
  toolbar: {
112
107
  icon: <Functions />,
113
- onClick: (editor) => {
108
+ onClick: (value, onChange) => {
114
109
  log('[insertMath]');
115
110
  const math = inlineMath();
116
-
117
- editor.insertNode(math);
111
+ const change = value.change().insertInline(math);
112
+ onChange(change);
118
113
  },
114
+ supports: (node) => node && node.object === 'inline' && node.type === 'math',
119
115
  /**
120
116
  * Return a react component function
121
117
  * @param node {Slate.Node}
@@ -125,20 +121,6 @@ export default function MathPlugin(opts) {
125
121
  */
126
122
  CustomToolbarComp,
127
123
  },
128
- rules: (editor) => {
129
- const { isVoid, isInline } = editor;
130
-
131
- editor.isVoid = (element) => {
132
- return mathTypes.includes(element.type) ? true : isVoid(element);
133
- };
134
-
135
- editor.isInline = (element) => {
136
- return mathTypes.includes(element.type) ? true : isInline(element);
137
- };
138
-
139
- return editor;
140
- },
141
- supports: (node) => mathTypes.includes(node.type),
142
124
  schema: {
143
125
  document: { match: [{ type: 'math' }] },
144
126
  },
@@ -162,16 +144,9 @@ export default function MathPlugin(opts) {
162
144
  * Here for rendering mathml content
163
145
  */
164
146
  if (props.node.type === 'mathml') {
165
- const {
166
- data: { html },
167
- } = props.node;
168
-
169
- return (
170
- <span>
171
- <span {...props.attributes} contentEditable={false} dangerouslySetInnerHTML={{ __html: html }} />
172
- {props.children}
173
- </span>
174
- );
147
+ const html = props.node.data.get('html');
148
+
149
+ return <span {...props.attributes} dangerouslySetInnerHTML={{ __html: html }} />;
175
150
  }
176
151
  },
177
152
  };
@@ -185,16 +160,18 @@ MathPlugin.mathMlOptions = {};
185
160
 
186
161
  MathPlugin.propTypes = {
187
162
  attributes: PropTypes.object,
188
- node: PropTypes.node,
163
+ node: SlatePropTypes.node,
189
164
  };
190
165
 
191
- export const inlineMath = () => ({
192
- type: 'math',
193
- data: {
194
- latex: '',
195
- },
196
- children: [{ text: '' }],
197
- });
166
+ export const inlineMath = () =>
167
+ Inline.create({
168
+ object: 'inline',
169
+ type: 'math',
170
+ isVoid: true,
171
+ data: {
172
+ latex: '',
173
+ },
174
+ });
198
175
 
199
176
  const htmlDecode = (input) => {
200
177
  const doc = new DOMParser().parseFromString(input, 'text/html');
@@ -237,7 +214,7 @@ const lessThanHandling = (input) => {
237
214
  };
238
215
 
239
216
  export const serialization = {
240
- deserialize(el, children) {
217
+ deserialize(el) {
241
218
  const tagName = getTagName(el);
242
219
  /**
243
220
  * This is used for when there's a wrapper over the mathml element.
@@ -260,21 +237,26 @@ export const serialization = {
260
237
  const latex = htmlDecode(htmlToUse);
261
238
  const { unwrapped, wrapType } = unWrapMath(latex);
262
239
 
263
- return jsx('element', {
240
+ return {
241
+ object: 'inline',
264
242
  type: 'math',
243
+ isVoid: true,
244
+ nodes: [],
265
245
  data: {
266
246
  latex: unwrapped,
267
247
  wrapper: wrapType,
268
248
  },
269
- });
249
+ };
270
250
  }
271
251
 
272
- return jsx('element', {
252
+ return {
253
+ object: 'inline',
254
+ isVoid: true,
273
255
  type: 'mathml',
274
256
  data: {
275
257
  html: newHtml,
276
258
  },
277
- });
259
+ };
278
260
  }
279
261
 
280
262
  if (el.nodeType === TEXT_NODE) {
@@ -291,28 +273,31 @@ export const serialization = {
291
273
  const latex = htmlDecode(el.innerHTML);
292
274
  const { unwrapped, wrapType } = unWrapMath(latex);
293
275
  log('[deserialize]: noBrackets: ', unwrapped, wrapType);
294
-
295
- return jsx('element', {
276
+ return {
277
+ object: 'inline',
296
278
  type: 'math',
279
+ isVoid: true,
280
+ nodes: [],
297
281
  data: {
298
282
  latex: unwrapped,
299
283
  wrapper: wrapType,
300
284
  },
301
- });
285
+ };
302
286
  }
303
287
  },
304
288
  serialize(object) {
305
289
  if (object.type === 'math') {
306
- const { latex, wrapper } = object.data || {};
307
- log('[serialize] latex: ', latex);
308
- const decoded = htmlDecode(lessThanHandling(latex));
290
+ const l = object.data.get('latex');
291
+ const wrapper = object.data.get('wrapper');
292
+ log('[serialize] latex: ', l);
293
+ const decoded = htmlDecode(lessThanHandling(l));
309
294
 
310
295
  if (MathPlugin.mathMlOptions.mmlOutput) {
311
296
  const res = renderMath(`<span data-latex="" data-raw="${decoded}">${wrapMath(decoded, wrapper)}</span>`);
312
297
  const newLatex = mmlToLatex(res);
313
298
 
314
299
  // we need to remove all the spaces from the latex to be able to compare it
315
- const strippedL = latex.replace(/\s/g, '');
300
+ const strippedL = l.replace(/\s/g, '');
316
301
  const strippedNewL = newLatex.replace(/\s/g, '');
317
302
 
318
303
  // we check if the latex keeps his form after being converted to mathml and back to latex
@@ -321,16 +306,8 @@ export const serialization = {
321
306
  return <span data-type="mathml" dangerouslySetInnerHTML={{ __html: res }} />;
322
307
  } else {
323
308
  // if it doesn't we keep the latex version
324
- console.log(
325
- 'This latex can not be safely converted to mathml:',
326
- latex,
327
- 'so we will keep the latex version!!!',
328
- );
329
- console.warn(
330
- 'This latex can not be safely converted to mathml:',
331
- latex,
332
- 'so we will keep the latex version!!!',
333
- );
309
+ console.log('This latex can not be safely converted to mathml:', l, 'so we will keep the latex version!!!');
310
+ console.warn('This latex can not be safely converted to mathml:', l, 'so we will keep the latex version!!!');
334
311
  }
335
312
  }
336
313
 
@@ -345,7 +322,7 @@ export const serialization = {
345
322
  * Here for rendering mathml content
346
323
  */
347
324
  if (object.type === 'mathml') {
348
- const { html } = object.data || {};
325
+ const html = object.data.get('html');
349
326
 
350
327
  return <span data-type="mathml" dangerouslySetInnerHTML={{ __html: html }} />;
351
328
  }
@@ -1,13 +1,10 @@
1
1
  import React from 'react';
2
2
  import ReactDOM from 'react-dom';
3
- import { Node as SlateNode, Transforms } from 'slate';
4
- import { jsx } from 'slate-hyperscript';
5
- import { ReactEditor } from 'slate-react';
3
+ import { Inline } from 'slate';
6
4
  import TheatersIcon from '@material-ui/icons/Theaters';
7
5
  import VolumeUpIcon from '@material-ui/icons/VolumeUp';
8
- import omit from 'lodash/omit';
9
-
10
6
  import debug from 'debug';
7
+
11
8
  import MediaDialog from './media-dialog';
12
9
  import MediaToolbar from './media-toolbar';
13
10
  import MediaWrapper from './media-wrapper';
@@ -52,41 +49,17 @@ export const insertDialog = (props) => {
52
49
  document.body.appendChild(newEl);
53
50
  };
54
51
 
55
- const getNodeBy = (editor, callback) => {
56
- const descendants = SlateNode.descendants(editor, {
57
- reverse: true,
58
- });
59
-
60
- for (const [descendant, descendantPath] of descendants) {
61
- if (callback(descendant, descendantPath)) {
62
- return [descendant, descendantPath];
63
- }
64
- }
65
- };
66
-
67
- const moveFocusAfterMedia = (editor, node) => {
68
- if (!editor || !node) {
69
- return;
70
- }
71
-
72
- setTimeout(() => {
73
- ReactEditor.focus(editor);
74
- Transforms.move(editor, { distance: 1, unit: 'offset' });
75
- }, 0);
76
- };
77
-
78
52
  const types = ['audio', 'video'];
79
53
 
80
54
  export default function MediaPlugin(type, opts) {
81
55
  const toolbar = {
82
56
  icon: type === 'audio' ? <VolumeUpIcon /> : <TheatersIcon />,
83
- onClick: (editor) => {
57
+ onClick: (value, onChange) => {
84
58
  log('[toolbar] onClick');
85
- const inline = {
59
+ const inline = Inline.create({
86
60
  type: type,
87
61
  isVoid: true,
88
62
  data: {
89
- newMedia: true,
90
63
  editing: false,
91
64
  ends: undefined,
92
65
  height: undefined,
@@ -96,164 +69,154 @@ export default function MediaPlugin(type, opts) {
96
69
  url: undefined,
97
70
  width: undefined,
98
71
  },
99
- children: [{ text: '' }],
100
- };
101
-
102
- editor.insertNode(inline);
72
+ });
103
73
 
74
+ const change = value.change().insertInline(inline);
75
+ onChange(change);
104
76
  insertDialog({
105
77
  type,
106
78
  opts,
107
79
  callback: (val, data) => {
108
- const nodePath = ReactEditor.findPath(editor, inline);
80
+ const nodeIsThere = change.value.document.findDescendant((d) => d.key === inline.key);
109
81
 
110
- if (inline) {
82
+ if (nodeIsThere) {
111
83
  if (!val) {
112
- editor.apply({
113
- type: 'remove_node',
114
- path: nodePath,
115
- });
84
+ const c = change.removeNodeByKey(inline.key);
85
+ onChange(c, () => opts.focus());
116
86
  } else {
117
- editor.apply({
118
- type: 'set_node',
119
- path: nodePath,
120
- properties: {
121
- data: inline.data,
122
- },
123
- newProperties: {
124
- data: {
125
- ...data,
126
- newMedia: false,
127
- },
128
- },
129
- });
87
+ const c = change.setNodeByKey(inline.key, { data });
88
+ onChange(c, () => opts.focus('beginning', nodeIsThere));
130
89
  }
90
+ } else {
91
+ opts.focus();
131
92
  }
132
-
133
- moveFocusAfterMedia(editor, inline);
134
93
  },
135
94
  });
136
95
  },
96
+ supports: (node) => node.object === 'inline' && node.type === type,
137
97
  };
138
98
 
139
99
  return {
140
100
  name: type,
141
101
  toolbar,
142
- rules: (editor) => {
143
- const { isVoid, isInline } = editor;
144
-
145
- editor.isVoid = (element) => {
146
- return ['audio', 'video'].includes(element.type) ? true : isVoid(element);
147
- };
102
+ deleteNode: (e, node, value, onChange) => {
103
+ e.preventDefault();
104
+ const change = value.change().removeNodeByKey(node.key);
148
105
 
149
- editor.isInline = (element) => {
150
- return ['audio', 'video'].includes(element.type) ? true : isInline(element);
151
- };
152
-
153
- return editor;
106
+ onChange(change);
154
107
  },
155
- supports: (node) => node.type === type,
156
108
  renderNode(props) {
157
109
  if (props.node.type === type) {
158
- const { node, editor } = props;
110
+ const { node, key } = props;
159
111
  const { data } = node;
160
- const { src, height, width, editing, tag, ...rest } = omit(data, ['newMedia', 'urlToUse']);
161
- const attributes = { ...rest, ...props.attributes };
162
- const handleEdit = (event) => {
163
- const nodeToEdit = ReactEditor.toSlateNode(editor, event.target);
164
- const nodePath = ReactEditor.findPath(editor, nodeToEdit);
165
-
166
- editor.apply({
167
- type: 'set_node',
168
- path: nodePath,
169
- properties: {
170
- data: node.data,
171
- },
172
- newProperties: {
173
- data: {
174
- ...data,
175
- editing: true,
176
- },
112
+ const jsonData = data.toJSON();
113
+ const { src, height, width, editing, tag, ...rest } = jsonData;
114
+ const handleEdit = () => {
115
+ const change = opts.createChange();
116
+ const c = change.setNodeByKey(key, {
117
+ data: {
118
+ ...jsonData,
119
+ editing: true,
177
120
  },
178
121
  });
179
122
 
180
- insertDialog({
181
- ...data,
182
- edit: true,
183
- type,
184
- opts,
185
- callback: (val, data) => {
186
- const nodePath = ReactEditor.findPath(editor, nodeToEdit);
187
-
188
- if (nodePath && val) {
189
- editor.apply({
190
- type: 'set_node',
191
- path: nodePath,
192
- properties: {
193
- data: node.data,
194
- },
195
- newProperties: {
196
- data: {
197
- ...data,
198
- editing: true,
199
- },
200
- },
201
- });
202
- }
203
- },
123
+ opts.onChange(c, () => {
124
+ insertDialog({
125
+ ...jsonData,
126
+ edit: true,
127
+ type,
128
+ opts,
129
+ callback: (val, data) => {
130
+ const { key } = node;
131
+
132
+ const nodeIsThere = change.value.document.findDescendant(
133
+ (d) => d.type === type && d.data.get('editing'),
134
+ );
135
+
136
+ if (nodeIsThere && val) {
137
+ const c = change.setNodeByKey(key, { data, editing: false });
138
+ opts.onChange(c, () => opts.focus('beginning', nodeIsThere));
139
+ } else {
140
+ opts.focus();
141
+ }
142
+ },
143
+ });
204
144
  });
205
145
  };
206
- const handleDelete = (event) => {
207
- const nodeToEdit = ReactEditor.toSlateNode(editor, event.target);
208
- const nodePath = ReactEditor.findPath(editor, nodeToEdit);
146
+ const handleDelete = () => {
147
+ const change = opts.createChange();
148
+ const c = change.removeNodeByKey(node.key);
209
149
 
210
- editor.apply({
211
- type: 'remove_node',
212
- path: nodePath,
213
- });
150
+ opts.onChange(c);
214
151
  };
215
- const computedProps = {};
152
+ const style = {};
216
153
 
217
154
  if (width) {
218
- computedProps.width = `${width}px`;
155
+ style.width = `${width}px`;
219
156
  }
220
157
 
221
158
  if (height) {
222
- computedProps.height = `${height}px`;
159
+ style.height = `${height}px`;
223
160
  }
224
161
 
225
- computedProps.editing = editing ? 1 : 0;
226
-
227
162
  if (tag === 'audio') {
228
163
  return (
229
- <MediaWrapper data-type={type} width={computedProps.width} attributes={attributes}>
164
+ <MediaWrapper editor data-type={type} width={style.width} {...rest}>
230
165
  <audio controls="controls">
231
166
  <source type="audio/mp3" src={src} />
232
167
  </audio>
233
168
  <MediaToolbar hideEdit onRemove={handleDelete} />
234
- {props.children}
235
169
  </MediaWrapper>
236
170
  );
237
171
  }
238
172
 
239
173
  return (
240
- <MediaWrapper data-type={type} width={computedProps.width} attributes={attributes}>
241
- <div contentEditable={false}>
242
- <iframe
243
- frameBorder="0"
244
- allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
245
- allowFullScreen
246
- src={src}
247
- {...rest}
248
- {...computedProps}
249
- />
250
- <MediaToolbar onEdit={handleEdit} onRemove={handleDelete} />
251
- </div>
252
- {props.children}
174
+ <MediaWrapper editor data-type={type} width={style.width} {...rest}>
175
+ <iframe
176
+ frameBorder="0"
177
+ allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
178
+ allowFullScreen
179
+ src={src}
180
+ editing={editing ? 1 : 0}
181
+ {...rest}
182
+ {...style}
183
+ />
184
+ <MediaToolbar onEdit={handleEdit} onRemove={handleDelete} />
253
185
  </MediaWrapper>
254
186
  );
255
187
  }
256
188
  },
189
+ normalizeNode: (node) => {
190
+ const textNodeMap = {};
191
+ const updateNodesArray = [];
192
+ let index = 0;
193
+
194
+ if (node.object !== 'document') return;
195
+
196
+ node.findDescendant((d) => {
197
+ if (d.object === 'text') {
198
+ textNodeMap[index] = d;
199
+ }
200
+
201
+ const isMedia = types.indexOf(d.type) >= 0;
202
+
203
+ if (isMedia) {
204
+ if (index > 0 && textNodeMap[index - 1] && textNodeMap[index - 1].text === '') {
205
+ updateNodesArray.push(textNodeMap[index - 1]);
206
+ }
207
+ }
208
+
209
+ index++;
210
+ });
211
+
212
+ if (!updateNodesArray.length) return;
213
+
214
+ return (change) => {
215
+ change.withoutNormalization(() => {
216
+ updateNodesArray.forEach((n) => change.insertTextByKey(n.key, 0, ' '));
217
+ });
218
+ };
219
+ },
257
220
  };
258
221
  }
259
222
 
@@ -280,8 +243,10 @@ export const serialization = {
280
243
  const width = parseInt(el.getAttribute('width'), 10) || null;
281
244
  const height = parseInt(el.getAttribute('height'), 10) || null;
282
245
 
283
- const out = jsx('element', {
284
- type,
246
+ const out = {
247
+ object: 'inline',
248
+ type: type,
249
+ isVoid: true,
285
250
  data: {
286
251
  tag,
287
252
  src: src || el.getAttribute('src'),
@@ -293,21 +258,27 @@ export const serialization = {
293
258
  width,
294
259
  url,
295
260
  },
296
- });
297
-
261
+ };
298
262
  log('return object: ', out);
299
263
  return out;
300
264
  },
301
- serialize(object) {
265
+ serialize(object /*, children*/) {
302
266
  const typeIndex = types.indexOf(object.type);
303
267
 
304
- if (typeIndex < 0) {
305
- return;
306
- }
268
+ if (typeIndex < 0) return;
307
269
 
308
270
  const type = types[typeIndex];
309
271
 
310
- const { editing, tag, ends, src, starts, title, width, height, url } = object.data || {};
272
+ const { data } = object;
273
+ const editing = data.get('editing');
274
+ const tag = data.get('tag');
275
+ const ends = data.get('ends');
276
+ const src = data.get('src');
277
+ const starts = data.get('starts');
278
+ const title = data.get('title');
279
+ const width = data.get('width');
280
+ const height = data.get('height');
281
+ const url = data.get('url');
311
282
  const style = {};
312
283
 
313
284
  if (width) {