@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
@@ -1,59 +1,45 @@
1
- import React from 'react';
2
- import { jsx } from 'slate-hyperscript';
3
- import debug from 'debug';
4
- import get from 'lodash/get';
5
- import { Node as SlateNode, Editor } from 'slate';
1
+ import { Data, Inline } from 'slate';
6
2
 
7
3
  import Image from '@material-ui/icons/Image';
8
4
  import ImageComponent from './component';
9
5
  import ImageToolbar from './image-toolbar';
10
6
  import InsertImageHandler from './insert-image-handler';
11
- import { ReactEditor } from 'slate-react';
7
+ import React from 'react';
8
+ import debug from 'debug';
12
9
 
13
10
  const log = debug('@pie-lib:editable-html:plugins:image');
14
11
 
15
12
  export default function ImagePlugin(opts) {
16
13
  const toolbar = opts.insertImageRequested && {
17
14
  icon: <Image />,
18
- onClick: editor => {
15
+ onClick: (value, onChange) => {
19
16
  log('[toolbar] onClick');
20
- const inline = {
17
+ const inline = Inline.create({
21
18
  type: 'image',
19
+ isVoid: true,
22
20
  data: {
23
- newImage: true,
24
21
  loaded: false,
25
22
  src: undefined,
26
23
  },
27
- children: [{ text: '' }],
28
- };
29
-
30
- editor.insertNode(inline);
31
-
32
- // get the element just inserted
33
- const [node, nodePath] = Editor.node(editor, editor.selection);
24
+ });
34
25
 
35
- opts.insertImageRequested(() => new InsertImageHandler(node, nodePath, editor));
26
+ const change = value.change().insertInline(inline);
27
+ onChange(change);
28
+ opts.insertImageRequested((getValue) => new InsertImageHandler(inline, getValue, onChange));
36
29
  },
37
- customToolbar: (node, nodePath, editor, onToolbarDone) => {
38
- const alignment = node.data.alignment;
39
- const alt = node.data.alt;
40
- const imageLoaded = node.data.loaded !== false;
41
- const onChange = newValues => {
30
+ supports: (node) => node.object === 'inline' && node.type === 'image',
31
+ customToolbar: (node, value, onToolbarDone) => {
32
+ const alignment = node.data.get('alignment');
33
+ const alt = node.data.get('alt');
34
+ const imageLoaded = node.data.get('loaded') !== false;
35
+ const onChange = (newValues, done) => {
42
36
  const update = {
43
- ...node.data,
44
- ...newValues
37
+ ...node.data.toObject(),
38
+ ...newValues,
45
39
  };
46
40
 
47
- editor.apply({
48
- type: 'set_node',
49
- path: nodePath,
50
- properties: {
51
- data: node.data
52
- },
53
- newProperties: { data: update }
54
- });
55
-
56
- onToolbarDone(null, false);
41
+ const change = value.change().setNodeByKey(node.key, { data: update });
42
+ onToolbarDone(change, done);
57
43
  };
58
44
 
59
45
  const Tb = () => (
@@ -73,72 +59,26 @@ export default function ImagePlugin(opts) {
73
59
  return {
74
60
  name: 'image',
75
61
  toolbar,
76
- rules: editor => {
77
- const { isVoid, isInline } = editor;
78
-
79
- editor.isVoid = element => {
80
- return element.type === 'image' ? true : isVoid(element);
81
- };
82
-
83
- editor.isInline = element => {
84
- return element.type === 'image' ? true : isInline(element);
85
- };
86
-
87
- return editor;
88
- },
89
- supports: node => node.type === 'image',
90
- deleteNode: (e, node, nodePath, editor, onChange) => {
62
+ deleteNode: (e, node, value, onChange) => {
91
63
  e.preventDefault();
92
-
93
64
  if (opts.onDelete) {
94
- const update = {
95
- ...node.data,
96
- deleteStatus: 'pending'
97
- };
65
+ const update = node.data.merge(Data.create({ deleteStatus: 'pending' }));
98
66
 
99
- editor.apply({
100
- type: 'set_node',
101
- path: nodePath,
102
- properties: {
103
- data: node.data
104
- },
105
- newProperties: { data: update }
106
- });
67
+ let change = value.change().setNodeByKey(node.key, { data: update });
107
68
 
108
- editor.selection = null;
109
- onChange(editor);
110
- opts.onDelete(node.data.src, (err) => {
69
+ onChange(change);
70
+ opts.onDelete(node.data.get('src'), (err, v) => {
111
71
  if (!err) {
112
- editor.apply({
113
- type: 'remove_node',
114
- path: nodePath
115
- });
72
+ change = v.change().removeNodeByKey(node.key);
116
73
  } else {
117
74
  log('[error]: ', err);
118
- editor.apply({
119
- type: 'set_node',
120
- path: nodePath,
121
- properties: {
122
- data: node.data
123
- },
124
- newProperties: { data: { ...node.data, deleteStatus: 'failed' } },
125
- });
75
+ change = v.change().setNodeByKey(node.key, node.data.merge(Data.create({ deleteStatus: 'failed' })));
126
76
  }
127
-
128
- editor.selection = null;
129
- onChange(editor, () => {
130
- setTimeout(() => ReactEditor.focus(editor), 50);
131
- });
77
+ onChange(change);
132
78
  });
133
79
  } else {
134
- editor.selection = null;
135
- editor.apply({
136
- type: 'remove_node',
137
- path: nodePath
138
- });
139
- onChange(editor, () => {
140
- setTimeout(() => ReactEditor.focus(editor), 50);
141
- });
80
+ let change = value.change().removeNodeByKey(node.key);
81
+ onChange(change);
142
82
  }
143
83
  },
144
84
  stopReset: (value) => {
@@ -146,7 +86,7 @@ export default function ImagePlugin(opts) {
146
86
  if (n.type !== 'image') {
147
87
  return;
148
88
  }
149
- return n.data.loaded === false;
89
+ return n.data.get('loaded') === false;
150
90
  });
151
91
  /** don't reset if there is an image pending insertion */
152
92
  return imgPendingInsertion !== undefined && imgPendingInsertion !== null;
@@ -163,11 +103,7 @@ export default function ImagePlugin(opts) {
163
103
  },
164
104
  props,
165
105
  );
166
- return (
167
- <ImageComponent {...all}>
168
- {props.children}
169
- </ImageComponent>
170
- );
106
+ return <ImageComponent {...all} />;
171
107
  }
172
108
  },
173
109
  normalizeNode: (node) => {
@@ -212,8 +148,10 @@ export const serialization = {
212
148
  const width = parseInt(style.width.replace('px', ''), 10) || null;
213
149
  const height = parseInt(style.height.replace('px', ''), 10) || null;
214
150
 
215
- const out = jsx('element', {
151
+ const out = {
152
+ object: 'inline',
216
153
  type: 'image',
154
+ isVoid: true,
217
155
  data: {
218
156
  src: el.getAttribute('src'),
219
157
  width,
@@ -223,7 +161,7 @@ export const serialization = {
223
161
  alignment: el.getAttribute('alignment'),
224
162
  alt: el.getAttribute('alt'),
225
163
  },
226
- });
164
+ };
227
165
  log('return object: ', out);
228
166
  return out;
229
167
  },
@@ -231,17 +169,14 @@ export const serialization = {
231
169
  if (object.type !== 'image') return;
232
170
 
233
171
  const { data } = object;
234
- const {
235
- alignment,
236
- alt,
237
- src,
238
- height,
239
- margin,
240
- justifyContent,
241
- width
242
- } = data;
172
+ const src = data.get('src');
173
+ const width = data.get('width');
174
+ const height = data.get('height');
175
+ const alignment = data.get('alignment') || 'left';
176
+ const margin = data.get('margin');
177
+ const justifyContent = data.get('margin');
178
+ const alt = data.get('alt');
243
179
  const style = {};
244
-
245
180
  if (width) {
246
181
  style.width = `${width}px`;
247
182
  }
@@ -1,33 +1,33 @@
1
- import omit from 'lodash/omit';
1
+ import { Data } from 'slate';
2
2
  import debug from 'debug';
3
3
 
4
4
  const log = debug('@pie-lib:editable-html:image:insert-image-handler');
5
5
 
6
6
  /**
7
7
  * Handles user selection, insertion (or cancellation) of an image into the editor.
8
- * @param {Block} placeHolderPath - a block that has been added to the editor as a place holder for the image
8
+ * @param {Block} placeholderBlock - a block that has been added to the editor as a place holder for the image
9
9
  * @param {Function} getValue - a function to return the value of the editor
10
10
  * @param {Function} onChange - callback to notify changes applied by the handler
11
11
  * @param {Boolean} isPasted - a boolean that keeps track if the file is pasted
12
12
  */
13
13
  class InsertImageHandler {
14
- constructor(node, placeHolderPath, editor, isPasted = false) {
15
- this.node = node;
16
- this.placeHolderPath = placeHolderPath;
17
- this.editor = editor;
14
+ constructor(placeholderBlock, getValue, onChange, isPasted = false) {
15
+ this.placeholderBlock = placeholderBlock;
16
+ this.getValue = getValue;
17
+ this.onChange = onChange;
18
18
  this.isPasted = isPasted;
19
19
  this.chosenFile = null;
20
20
  }
21
21
 
22
22
  getPlaceholderInDocument(value) {
23
23
  const { document } = value;
24
- const directChild = document.getChild(this.placeHolderPath);
24
+ const directChild = document.getChild(this.placeholderBlock.key);
25
25
 
26
26
  if (directChild) {
27
27
  return directChild;
28
28
  }
29
29
 
30
- const child = document.getDescendant(this.placeHolderPath);
30
+ const child = document.getDescendant(this.placeholderBlock.key);
31
31
 
32
32
  if (child) {
33
33
  return child;
@@ -39,10 +39,10 @@ class InsertImageHandler {
39
39
 
40
40
  cancel() {
41
41
  log('insert cancelled');
42
- this.editor.apply({
43
- type: 'remove_node',
44
- path: this.placeHolderPath
45
- });
42
+ const c = this.getValue()
43
+ .change()
44
+ .removeNodeByKey(this.placeholderBlock.key);
45
+ this.onChange(c);
46
46
  }
47
47
 
48
48
  done(err, src) {
@@ -51,30 +51,12 @@ class InsertImageHandler {
51
51
  //eslint-disable-next-line
52
52
  console.log(err);
53
53
  } else {
54
- this.editor.apply({
55
- type: 'set_node',
56
- path: this.placeHolderPath,
57
- properties: {
58
- data: this.node.data
59
- },
60
- newProperties: {
61
- data: {
62
- src,
63
- loaded: true,
64
- percent: 100
65
- }
66
- }
67
- });
68
- const newData = {
69
- ...this.node.data,
70
- src,
71
- loaded: true,
72
- percent: 100
73
- };
54
+ const value = this.getValue();
55
+ const child = this.getPlaceholderInDocument(value);
56
+ const data = child.data.merge(Data.create({ loaded: true, src, percent: 100 }));
74
57
 
75
- this.node = Object.assign({}, this.node, {
76
- data: omit(newData, 'newImage')
77
- });
58
+ const change = value.change().setNodeByKey(this.placeholderBlock.key, { data });
59
+ this.onChange(change);
78
60
  }
79
61
  }
80
62
 
@@ -94,40 +76,23 @@ class InsertImageHandler {
94
76
  log('[fileChosen] file: ', file);
95
77
  const reader = new FileReader();
96
78
  reader.onload = () => {
79
+ const value = this.getValue();
97
80
  const dataURL = reader.result;
98
-
99
- this.editor.apply({
100
- type: 'set_node',
101
- path: this.placeHolderPath,
102
- properties: {
103
- data: this.node.data
104
- },
105
- newProperties: {
106
- data: {
107
- ...this.node.data,
108
- src: dataURL
109
- }
110
- }
111
- });
112
- this.node = Object.assign({}, this.node, { data: { src: dataURL } });
81
+ const child = this.getPlaceholderInDocument(value);
82
+ const data = child.data.set('src', dataURL);
83
+ const change = value.change().setNodeByKey(this.placeholderBlock.key, { data });
84
+ this.onChange(change);
113
85
  };
114
86
  reader.readAsDataURL(file);
115
87
  }
116
88
 
117
89
  progress(percent, bytes, total) {
118
90
  log('progress: ', percent, bytes, total);
119
-
120
- this.editor.apply({
121
- type: 'set_node',
122
- path: this.placeHolderPath,
123
- properties: {
124
- data: this.node.data
125
- },
126
- newProperties: {
127
- data: { ...this.node.data, percent }
128
- }
129
- });
130
- this.node = Object.assign({}, this.node, { data: { percent } });
91
+ const value = this.getValue();
92
+ const child = this.getPlaceholderInDocument(value);
93
+ const data = child.data.set('percent', percent);
94
+ const change = value.change().setNodeByKey(this.placeholderBlock.key, { data });
95
+ this.onChange(change);
131
96
  }
132
97
 
133
98
  // Add a getter method to retrieve the chosen file
@@ -1,9 +1,5 @@
1
- import { Editor } from 'slate';
2
- import { withHistory } from 'slate-history';
3
- import { withReact } from 'slate-react';
4
-
5
1
  import Bold from '@material-ui/icons/FormatBold';
6
- // import Code from '@material-ui/icons/Code';
2
+ //import Code from '@material-ui/icons/Code';
7
3
  import BulletedListIcon from '@material-ui/icons/FormatListBulleted';
8
4
  import NumberedListIcon from '@material-ui/icons/FormatListNumbered';
9
5
  import ImagePlugin from './image';
@@ -21,13 +17,49 @@ import debug from 'debug';
21
17
  import List from './list';
22
18
  import TablePlugin from './table';
23
19
  import RespAreaPlugin from './respArea';
24
- import MarkHotkey from './hotKeys';
20
+ import HtmlPlugin from './html';
25
21
 
26
22
  const log = debug('@pie-lib:editable-html:plugins');
27
23
 
24
+ function MarkHotkey(options) {
25
+ const { type, key, icon, tag } = options;
26
+
27
+ // Return our "plugin" object, containing the `onKeyDown` handler.
28
+ return {
29
+ toolbar: {
30
+ isMark: true,
31
+ type,
32
+ icon,
33
+ onToggle: (change) => {
34
+ log('[onToggleMark] type: ', type);
35
+ return change.toggleMark(type);
36
+ },
37
+ },
38
+ renderMark(props) {
39
+ if (props.mark.type === type) {
40
+ const K = tag || type;
41
+
42
+ return <K>{props.children}</K>;
43
+ }
44
+ },
45
+ onKeyDown(event, change) {
46
+ // Check that the key pressed matches our `key` option.
47
+ if (!event.metaKey || event.key != key) return;
48
+
49
+ // Prevent the default characters from being inserted.
50
+ event.preventDefault();
51
+
52
+ // Toggle the mark `type`.
53
+ change.toggleMark(type);
54
+ return true;
55
+ },
56
+ };
57
+ }
58
+
28
59
  export const ALL_PLUGINS = [
29
60
  'bold',
30
61
  // 'code',
62
+ 'html',
31
63
  'italic',
32
64
  'underline',
33
65
  'strikethrough',
@@ -80,20 +112,6 @@ export const buildPlugins = (activePlugins, opts) => {
80
112
  ToolbarPlugin(opts.toolbar),
81
113
  SoftBreakPlugin({ shift: true }),
82
114
  addIf('responseArea', respAreaPlugin),
115
+ addIf('html', HtmlPlugin(opts.html)),
83
116
  ]);
84
117
  };
85
-
86
- export const withPlugins = (editor, activePlugins) => {
87
- editor.continueNormalization = () => {
88
- Editor.setNormalizing(editor, true);
89
- Editor.normalize(editor, { force: true });
90
- };
91
-
92
- activePlugins.forEach(plugin => {
93
- if (typeof plugin.rules === 'function') {
94
- plugin.rules(editor);
95
- }
96
- });
97
-
98
- return withHistory(withReact(editor));
99
- };
@@ -1,8 +1,9 @@
1
1
  import React from 'react';
2
+ import { Data } from 'slate';
3
+ import Immutable from 'immutable';
2
4
  import PropTypes from 'prop-types';
5
+ import EditList from 'slate-edit-list';
3
6
  import debug from 'debug';
4
- import { Editor, Element as SlateElement, Transforms } from 'slate';
5
- import { jsx } from "slate-hyperscript";
6
7
 
7
8
  const log = debug('@pie-lib:editable-html:plugins:list');
8
9
 
@@ -15,39 +16,22 @@ const b = (type, next, childNodes) => ({
15
16
  export const serialization = {
16
17
  deserialize(el, next) {
17
18
  const name = el.tagName.toLowerCase();
18
- const children = el.children.length ? Array.from(el.children) : el.childNodes;
19
19
 
20
20
  if (name === 'li') {
21
- return jsx(
22
- 'element',
23
- {
24
- type: 'li'
25
- },
26
- next(children)
27
- );
21
+ return b('list_item', next, el.childNodes);
28
22
  }
29
23
 
30
24
  if (name === 'ul') {
31
- return jsx(
32
- 'element',
33
- {
34
- type: 'ul'
35
- },
36
- next(children)
37
- );
25
+ return b('ul_list', next, el.children.length ? Array.from(el.children) : el.childNodes);
38
26
  }
39
27
 
40
28
  if (name === 'ol') {
41
- return jsx(
42
- 'element',
43
- {
44
- type: 'ol'
45
- },
46
- next(children)
47
- );
29
+ return b('ol_list', next, el.children.length ? Array.from(el.children) : el.childNodes);
48
30
  }
49
31
  },
50
32
  serialize(object, children) {
33
+ if (object.object !== 'block') return;
34
+
51
35
  if (object.type === 'list_item') {
52
36
  return <li>{children}</li>;
53
37
  }
@@ -62,61 +46,88 @@ export const serialization = {
62
46
  },
63
47
  };
64
48
 
65
- const isBlockActive = (editor, format) => {
66
- const { selection } = editor;
67
- if (!selection) return false;
68
-
69
- const [match] = Array.from(
70
- Editor.nodes(editor, {
71
- at: Editor.unhangRange(editor, selection),
72
- match: node => !Editor.isEditor(node) && SlateElement.isElement(node) && node.type === format
73
- })
74
- );
75
-
76
- return !!match;
77
- };
78
-
79
49
  const createEditList = () => {
80
- const core = {
81
- changes: {},
82
- };
50
+ const core = EditList({
51
+ typeDefault: 'span',
52
+ });
83
53
 
84
- core.changes.wrapInList = (editor, format) => {
85
- const isActive = isBlockActive(editor, format);
86
- const isList = LIST_TYPES.includes(format);
54
+ // fix outdated schema
55
+ if (core.schema && core.schema.blocks) {
56
+ Object.keys(core.schema.blocks).forEach((key) => {
57
+ const block = core.schema.blocks[key];
87
58
 
88
- Transforms.unwrapNodes(editor, {
89
- match: n =>
90
- !Editor.isEditor(n) &&
91
- SlateElement.isElement(n) &&
92
- LIST_TYPES.includes(n.type),
93
- split: true
59
+ if (block.parent) {
60
+ return;
61
+ }
62
+
63
+ block.nodes[0] = { type: block.nodes[0].types[0] };
94
64
  });
65
+ }
66
+
67
+ /**
68
+ * This override of the core.changes.wrapInList is needed because the version
69
+ * of immutable that we have does not support getting the element at a specific
70
+ * index with a square bracket (list[0]). We have to use the list.get function instead
71
+ */
72
+
73
+ /**
74
+ * Returns the highest list of blocks that cover the current selection
75
+ */
76
+ const getHighestSelectedBlocks = (value) => {
77
+ const range = value.selection;
78
+ const document = value.document;
79
+
80
+ const startBlock = document.getClosestBlock(range.startKey);
81
+ const endBlock = document.getClosestBlock(range.endKey);
82
+
83
+ if (startBlock === endBlock) {
84
+ return Immutable.List([startBlock]);
85
+ }
95
86
 
96
- const newProperties = {
97
- type: isActive ? 'paragraph' : isList ? 'list_item' : format
98
- };
87
+ const ancestor = document.getCommonAncestor(startBlock.key, endBlock.key);
88
+ const startPath = ancestor.getPath(startBlock.key);
89
+ const endPath = ancestor.getPath(endBlock.key);
99
90
 
100
- Transforms.setNodes(editor, newProperties);
91
+ return ancestor.nodes.slice(startPath.get(0), endPath.get(0) + 1);
92
+ };
101
93
 
102
- if (!isActive && isList) {
103
- const block = { type: format, children: [] };
104
- Transforms.wrapNodes(editor, block);
105
- }
94
+ /**
95
+ * Wrap the blocks in the current selection in a new list. Selected
96
+ * lists are merged together.
97
+ */
98
+ core.changes.wrapInList = function(change, type, data) {
99
+ const selectedBlocks = getHighestSelectedBlocks(change.value);
100
+
101
+ // Wrap in container
102
+ change.wrapBlock({ type: type, data: Data.create(data) }, { normalize: false });
103
+
104
+ // Wrap in list items
105
+ selectedBlocks.forEach(function(node) {
106
+ if (core.utils.isList(node)) {
107
+ // Merge its items with the created list
108
+ node.nodes.forEach(function(_ref) {
109
+ const key = _ref.key;
110
+ return change.unwrapNodeByKey(key, { normalize: false });
111
+ });
112
+ } else if (node.type !== 'list_item') {
113
+ change.wrapBlockByKey(node.key, 'list_item', {
114
+ normalize: false,
115
+ });
116
+ }
117
+ });
118
+
119
+ return change.normalize();
106
120
  };
107
121
 
108
122
  return core;
109
123
  };
110
124
 
111
- const LIST_TYPES = ['ol_list', 'ul_list', 'list_item'];
112
-
113
125
  export default (options) => {
114
126
  const { type, icon } = options;
115
127
 
116
128
  const core = createEditList();
117
129
 
118
- core.supports = (node) => LIST_TYPES.includes(node.type);
119
-
130
+ // eslint-disable-next-line react/display-name
120
131
  core.renderNode = (props) => {
121
132
  const { node, attributes, children } = props;
122
133
 
@@ -134,10 +145,24 @@ export default (options) => {
134
145
  isMark: false,
135
146
  type,
136
147
  icon,
137
- isActive: isBlockActive,
138
- onClick: editor => {
139
- core.changes.wrapInList(editor, type);
140
- }
148
+ isActive: (value, type) => {
149
+ if (!core.utils.isSelectionInList(value)) {
150
+ return false;
151
+ }
152
+ const current = core.utils.getCurrentList(value);
153
+ return current ? current.type === type : false;
154
+ },
155
+ onClick: (value, onChange) => {
156
+ log('[onClick]', value);
157
+ const inList = core.utils.isSelectionInList(value);
158
+ if (inList) {
159
+ const change = value.change().call(core.changes.unwrapList);
160
+ onChange(change);
161
+ } else {
162
+ const change = value.change().call(core.changes.wrapInList, type);
163
+ onChange(change);
164
+ }
165
+ },
141
166
  };
142
167
 
143
168
  core.renderNode.propTypes = {