@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,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,
37
+ ...node.data.toObject(),
44
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,7 +103,7 @@ export default function ImagePlugin(opts) {
163
103
  },
164
104
  props,
165
105
  );
166
- return <ImageComponent {...all}>{props.children}</ImageComponent>;
106
+ return <ImageComponent {...all} />;
167
107
  }
168
108
  },
169
109
  normalizeNode: (node) => {
@@ -208,8 +148,10 @@ export const serialization = {
208
148
  const width = parseInt(style.width.replace('px', ''), 10) || null;
209
149
  const height = parseInt(style.height.replace('px', ''), 10) || null;
210
150
 
211
- const out = jsx('element', {
151
+ const out = {
152
+ object: 'inline',
212
153
  type: 'image',
154
+ isVoid: true,
213
155
  data: {
214
156
  src: el.getAttribute('src'),
215
157
  width,
@@ -219,7 +161,7 @@ export const serialization = {
219
161
  alignment: el.getAttribute('alignment'),
220
162
  alt: el.getAttribute('alt'),
221
163
  },
222
- });
164
+ };
223
165
  log('return object: ', out);
224
166
  return out;
225
167
  },
@@ -227,9 +169,14 @@ export const serialization = {
227
169
  if (object.type !== 'image') return;
228
170
 
229
171
  const { data } = object;
230
- const { alignment, alt, src, height, margin, justifyContent, width } = 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');
231
179
  const style = {};
232
-
233
180
  if (width) {
234
181
  style.width = `${width}px`;
235
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,58 +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) => !Editor.isEditor(n) && SlateElement.isElement(n) && LIST_TYPES.includes(n.type),
90
- split: true,
59
+ if (block.parent) {
60
+ return;
61
+ }
62
+
63
+ block.nodes[0] = { type: block.nodes[0].types[0] };
91
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
+ }
92
86
 
93
- const newProperties = {
94
- type: isActive ? 'paragraph' : isList ? 'list_item' : format,
95
- };
87
+ const ancestor = document.getCommonAncestor(startBlock.key, endBlock.key);
88
+ const startPath = ancestor.getPath(startBlock.key);
89
+ const endPath = ancestor.getPath(endBlock.key);
96
90
 
97
- Transforms.setNodes(editor, newProperties);
91
+ return ancestor.nodes.slice(startPath.get(0), endPath.get(0) + 1);
92
+ };
98
93
 
99
- if (!isActive && isList) {
100
- const block = { type: format, children: [] };
101
- Transforms.wrapNodes(editor, block);
102
- }
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();
103
120
  };
104
121
 
105
122
  return core;
106
123
  };
107
124
 
108
- const LIST_TYPES = ['ol_list', 'ul_list', 'list_item'];
109
-
110
125
  export default (options) => {
111
126
  const { type, icon } = options;
112
127
 
113
128
  const core = createEditList();
114
129
 
115
- core.supports = (node) => LIST_TYPES.includes(node.type);
116
-
130
+ // eslint-disable-next-line react/display-name
117
131
  core.renderNode = (props) => {
118
132
  const { node, attributes, children } = props;
119
133
 
@@ -131,9 +145,23 @@ export default (options) => {
131
145
  isMark: false,
132
146
  type,
133
147
  icon,
134
- isActive: isBlockActive,
135
- onClick: (editor) => {
136
- core.changes.wrapInList(editor, type);
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
+ }
137
165
  },
138
166
  };
139
167