@pie-lib/editable-html 10.0.0-beta.2 → 10.0.0-beta.3
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.
- package/lib/editor.js +89 -34
- package/lib/index.js +7 -9
- package/lib/new-serialization.js +2 -2
- package/lib/plugins/image/component.js +6 -5
- package/lib/plugins/image/index.js +81 -72
- package/lib/plugins/index.js +11 -1
- package/lib/plugins/math/index.js +3 -3
- package/lib/plugins/media/index.js +9 -19
- package/lib/plugins/respArea/drag-in-the-blank/index.js +4 -4
- package/lib/plugins/respArea/index.js +83 -81
- package/lib/plugins/table/index.js +84 -9
- package/lib/plugins/toolbar/toolbar.js +21 -16
- package/lib/serialization.js +2 -2
- package/lib/test-serializer.js +2 -2
- package/package.json +3 -4
- package/src/editor.jsx +67 -14
- package/src/index.jsx +6 -8
- package/src/new-serialization.jsx +1 -1
- package/src/plugins/image/component.jsx +5 -4
- package/src/plugins/image/index.jsx +72 -45
- package/src/plugins/index.jsx +6 -0
- package/src/plugins/math/index.jsx +1 -1
- package/src/plugins/media/index.jsx +6 -12
- package/src/plugins/respArea/drag-in-the-blank/index.jsx +3 -3
- package/src/plugins/respArea/index.jsx +75 -61
- package/src/plugins/table/index.jsx +68 -5
- package/src/plugins/toolbar/toolbar.jsx +21 -14
- package/src/serialization.jsx +1 -1
- package/src/test-serializer.js +1 -1
- package/yarn-error.log +18318 -0
|
@@ -1,27 +1,17 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import { jsx } from 'slate-hyperscript';
|
|
2
3
|
import debug from 'debug';
|
|
3
4
|
import get from 'lodash/get';
|
|
4
|
-
import {
|
|
5
|
+
import { Node as SlateNode, Editor } from 'slate';
|
|
5
6
|
|
|
6
7
|
import Image from '@material-ui/icons/Image';
|
|
7
8
|
import ImageComponent from './component';
|
|
8
9
|
import ImageToolbar from './image-toolbar';
|
|
9
10
|
import InsertImageHandler from './insert-image-handler';
|
|
11
|
+
import { ReactEditor } from 'slate-react';
|
|
10
12
|
|
|
11
13
|
const log = debug('@pie-lib:editable-html:plugins:image');
|
|
12
14
|
|
|
13
|
-
const getNodeBy = (editor, callback) => {
|
|
14
|
-
const descendants = SlateNode.descendants(editor, {
|
|
15
|
-
reverse: true
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
for (const [descendant, descendantPath] of descendants) {
|
|
19
|
-
if (callback(descendant, descendantPath)) {
|
|
20
|
-
return [descendant, descendantPath];
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
};
|
|
24
|
-
|
|
25
15
|
export default function ImagePlugin(opts) {
|
|
26
16
|
const toolbar = opts.insertImageRequested && {
|
|
27
17
|
icon: <Image />,
|
|
@@ -39,25 +29,31 @@ export default function ImagePlugin(opts) {
|
|
|
39
29
|
|
|
40
30
|
editor.insertNode(inline);
|
|
41
31
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
descendant => descendant.type === 'image' && get(descendant, 'data.newImage')
|
|
45
|
-
);
|
|
32
|
+
// get the element just inserted
|
|
33
|
+
const [node, nodePath] = Editor.node(editor, editor.selection);
|
|
46
34
|
|
|
47
35
|
opts.insertImageRequested(() => new InsertImageHandler(node, nodePath, editor));
|
|
48
36
|
},
|
|
49
|
-
customToolbar: (node,
|
|
37
|
+
customToolbar: (node, nodePath, editor, onToolbarDone) => {
|
|
50
38
|
const alignment = node.data.alignment;
|
|
51
39
|
const alt = node.data.alt;
|
|
52
40
|
const imageLoaded = node.data.loaded !== false;
|
|
53
41
|
const onChange = newValues => {
|
|
54
42
|
const update = {
|
|
55
|
-
...node.data
|
|
43
|
+
...node.data,
|
|
56
44
|
...newValues
|
|
57
45
|
};
|
|
58
46
|
|
|
59
|
-
|
|
60
|
-
|
|
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);
|
|
61
57
|
};
|
|
62
58
|
|
|
63
59
|
const Tb = () => (
|
|
@@ -85,28 +81,58 @@ export default function ImagePlugin(opts) {
|
|
|
85
81
|
return editor;
|
|
86
82
|
},
|
|
87
83
|
supports: node => node.type === 'image',
|
|
88
|
-
deleteNode: (e, node,
|
|
84
|
+
deleteNode: (e, node, nodePath, editor, onChange) => {
|
|
89
85
|
e.preventDefault();
|
|
86
|
+
|
|
90
87
|
if (opts.onDelete) {
|
|
91
|
-
const update =
|
|
88
|
+
const update = {
|
|
89
|
+
...node.data,
|
|
90
|
+
deleteStatus: 'pending'
|
|
91
|
+
};
|
|
92
92
|
|
|
93
|
-
|
|
93
|
+
editor.apply({
|
|
94
|
+
type: 'set_node',
|
|
95
|
+
path: nodePath,
|
|
96
|
+
properties: {
|
|
97
|
+
data: node.data
|
|
98
|
+
},
|
|
99
|
+
newProperties: { data: update }
|
|
100
|
+
});
|
|
94
101
|
|
|
95
|
-
|
|
96
|
-
|
|
102
|
+
editor.selection = null;
|
|
103
|
+
onChange(editor);
|
|
104
|
+
opts.onDelete(node.data.src, (err) => {
|
|
97
105
|
if (!err) {
|
|
98
|
-
|
|
106
|
+
editor.apply({
|
|
107
|
+
type: 'remove_node',
|
|
108
|
+
path: nodePath
|
|
109
|
+
});
|
|
99
110
|
} else {
|
|
100
111
|
log('[error]: ', err);
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
112
|
+
editor.apply({
|
|
113
|
+
type: 'set_node',
|
|
114
|
+
path: nodePath,
|
|
115
|
+
properties: {
|
|
116
|
+
data: node.data
|
|
117
|
+
},
|
|
118
|
+
newProperties: { data: { ...node.data, deleteStatus: 'failed' } }
|
|
119
|
+
});
|
|
104
120
|
}
|
|
105
|
-
|
|
121
|
+
|
|
122
|
+
editor.selection = null;
|
|
123
|
+
onChange(editor, () => {
|
|
124
|
+
setTimeout(() => ReactEditor.focus(editor), 50);
|
|
125
|
+
});
|
|
106
126
|
});
|
|
107
127
|
} else {
|
|
108
|
-
|
|
109
|
-
|
|
128
|
+
editor.selection = null;
|
|
129
|
+
editor.apply({
|
|
130
|
+
type: 'remove_node',
|
|
131
|
+
path: nodePath
|
|
132
|
+
});
|
|
133
|
+
onChange(editor, () => {
|
|
134
|
+
setTimeout(() => ReactEditor.focus(editor), 50);
|
|
135
|
+
});
|
|
110
136
|
}
|
|
111
137
|
},
|
|
112
138
|
stopReset: value => {
|
|
@@ -114,7 +140,7 @@ export default function ImagePlugin(opts) {
|
|
|
114
140
|
if (n.type !== 'image') {
|
|
115
141
|
return;
|
|
116
142
|
}
|
|
117
|
-
return n.data.
|
|
143
|
+
return n.data.loaded === false;
|
|
118
144
|
});
|
|
119
145
|
/** don't reset if there is an image pending insertion */
|
|
120
146
|
return imgPendingInsertion !== undefined && imgPendingInsertion !== null;
|
|
@@ -180,10 +206,8 @@ export const serialization = {
|
|
|
180
206
|
const width = parseInt(style.width.replace('px', ''), 10) || null;
|
|
181
207
|
const height = parseInt(style.height.replace('px', ''), 10) || null;
|
|
182
208
|
|
|
183
|
-
const out = {
|
|
184
|
-
object: 'inline',
|
|
209
|
+
const out = jsx('element', {
|
|
185
210
|
type: 'image',
|
|
186
|
-
isVoid: true,
|
|
187
211
|
data: {
|
|
188
212
|
src: el.getAttribute('src'),
|
|
189
213
|
width,
|
|
@@ -193,7 +217,7 @@ export const serialization = {
|
|
|
193
217
|
alignment: el.getAttribute('alignment'),
|
|
194
218
|
alt: el.getAttribute('alt')
|
|
195
219
|
}
|
|
196
|
-
};
|
|
220
|
+
});
|
|
197
221
|
log('return object: ', out);
|
|
198
222
|
return out;
|
|
199
223
|
},
|
|
@@ -201,14 +225,17 @@ export const serialization = {
|
|
|
201
225
|
if (object.type !== 'image') return;
|
|
202
226
|
|
|
203
227
|
const { data } = object;
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
228
|
+
const {
|
|
229
|
+
alignment,
|
|
230
|
+
alt,
|
|
231
|
+
src,
|
|
232
|
+
height,
|
|
233
|
+
margin,
|
|
234
|
+
justifyContent,
|
|
235
|
+
width
|
|
236
|
+
} = data;
|
|
211
237
|
const style = {};
|
|
238
|
+
|
|
212
239
|
if (width) {
|
|
213
240
|
style.width = `${width}px`;
|
|
214
241
|
}
|
package/src/plugins/index.jsx
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Editor } from 'slate';
|
|
1
2
|
import { withHistory } from 'slate-history';
|
|
2
3
|
import { withReact } from 'slate-react';
|
|
3
4
|
|
|
@@ -85,6 +86,11 @@ export const buildPlugins = (activePlugins, opts) => {
|
|
|
85
86
|
};
|
|
86
87
|
|
|
87
88
|
export const withPlugins = (editor, activePlugins) => {
|
|
89
|
+
editor.continueNormalization = () => {
|
|
90
|
+
Editor.setNormalizing(editor, true);
|
|
91
|
+
Editor.normalize(editor, { force: true });
|
|
92
|
+
};
|
|
93
|
+
|
|
88
94
|
activePlugins.forEach(plugin => {
|
|
89
95
|
if (typeof plugin.rules === 'function') {
|
|
90
96
|
plugin.rules(editor);
|
|
@@ -25,7 +25,7 @@ function generateAdditionalKeys(keyData = []) {
|
|
|
25
25
|
|
|
26
26
|
export const CustomToolbarComp = React.memo(
|
|
27
27
|
props => {
|
|
28
|
-
const { node, onFocus, onBlur, onClick, editor
|
|
28
|
+
const { node, nodePath, onFocus, onBlur, onClick, editor } = props;
|
|
29
29
|
const { pluginProps } = props || {};
|
|
30
30
|
const { math } = pluginProps || {};
|
|
31
31
|
const { keypadMode, customKeys, controlledKeypadMode = true } = math || {};
|
|
@@ -106,12 +106,9 @@ export default function MediaPlugin(type, opts) {
|
|
|
106
106
|
type,
|
|
107
107
|
opts,
|
|
108
108
|
callback: (val, data) => {
|
|
109
|
-
const
|
|
110
|
-
editor,
|
|
111
|
-
descendant => descendant.type === type && get(descendant, 'data.newMedia')
|
|
112
|
-
);
|
|
109
|
+
const nodePath = ReactEditor.findPath(editor, inline);
|
|
113
110
|
|
|
114
|
-
if (
|
|
111
|
+
if (inline) {
|
|
115
112
|
if (!val) {
|
|
116
113
|
editor.apply({
|
|
117
114
|
type: 'remove_node',
|
|
@@ -122,7 +119,7 @@ export default function MediaPlugin(type, opts) {
|
|
|
122
119
|
type: 'set_node',
|
|
123
120
|
path: nodePath,
|
|
124
121
|
properties: {
|
|
125
|
-
data:
|
|
122
|
+
data: inline.data
|
|
126
123
|
},
|
|
127
124
|
newProperties: {
|
|
128
125
|
data: {
|
|
@@ -134,7 +131,7 @@ export default function MediaPlugin(type, opts) {
|
|
|
134
131
|
}
|
|
135
132
|
}
|
|
136
133
|
|
|
137
|
-
moveFocusAfterMedia(editor,
|
|
134
|
+
moveFocusAfterMedia(editor, inline);
|
|
138
135
|
}
|
|
139
136
|
});
|
|
140
137
|
}
|
|
@@ -187,12 +184,9 @@ export default function MediaPlugin(type, opts) {
|
|
|
187
184
|
type,
|
|
188
185
|
opts,
|
|
189
186
|
callback: (val, data) => {
|
|
190
|
-
const
|
|
191
|
-
editor,
|
|
192
|
-
descendant => descendant.type === type && get(descendant, 'data.editing')
|
|
193
|
-
);
|
|
187
|
+
const nodePath = ReactEditor.findPath(editor, nodeToEdit);
|
|
194
188
|
|
|
195
|
-
if (
|
|
189
|
+
if (nodePath && val) {
|
|
196
190
|
editor.apply({
|
|
197
191
|
type: 'set_node',
|
|
198
192
|
path: nodePath,
|
|
@@ -9,7 +9,7 @@ export const onValueChange = (nodeProps, n, value) => {
|
|
|
9
9
|
change.setNodeByKey(n.key, {
|
|
10
10
|
data: {
|
|
11
11
|
...value,
|
|
12
|
-
index: n.data.
|
|
12
|
+
index: n.data.index
|
|
13
13
|
}
|
|
14
14
|
});
|
|
15
15
|
|
|
@@ -22,12 +22,12 @@ export const onRemoveResponse = (nodeProps, value) => {
|
|
|
22
22
|
const val = nodeProps.editor.value;
|
|
23
23
|
const change = val.change();
|
|
24
24
|
const dragInTheBlank = val.document.findDescendant(
|
|
25
|
-
n => n.data && n.data.
|
|
25
|
+
n => n.data && n.data.index === value.index
|
|
26
26
|
);
|
|
27
27
|
|
|
28
28
|
change.setNodeByKey(dragInTheBlank.key, {
|
|
29
29
|
data: {
|
|
30
|
-
index: dragInTheBlank.data.
|
|
30
|
+
index: dragInTheBlank.data.index
|
|
31
31
|
}
|
|
32
32
|
});
|
|
33
33
|
|
|
@@ -3,6 +3,8 @@ import { Node as SlateNode } from 'slate';
|
|
|
3
3
|
import { jsx } from 'slate-hyperscript';
|
|
4
4
|
import debug from 'debug';
|
|
5
5
|
|
|
6
|
+
import cloneDeep from 'lodash/cloneDeep';
|
|
7
|
+
import isEqual from 'lodash/isEqual';
|
|
6
8
|
import isUndefined from 'lodash/isUndefined';
|
|
7
9
|
import InlineDropdown from './inline-dropdown';
|
|
8
10
|
import DragInTheBlank from './drag-in-the-blank';
|
|
@@ -31,15 +33,15 @@ export default function ResponseAreaPlugin(opts) {
|
|
|
31
33
|
onClick: editor => {
|
|
32
34
|
log('[toolbar] onClick');
|
|
33
35
|
const currentRespAreaList = [];
|
|
34
|
-
const descendants = SlateNode.descendants(editor, {
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
const descendants = Array.from(SlateNode.descendants(editor, { reverse: true })).map(
|
|
37
|
+
([d]) => d
|
|
38
|
+
);
|
|
37
39
|
|
|
38
|
-
|
|
39
|
-
if (isOfCurrentType(
|
|
40
|
-
currentRespAreaList.push(
|
|
40
|
+
descendants.forEach(d => {
|
|
41
|
+
if (isOfCurrentType(d)) {
|
|
42
|
+
currentRespAreaList.push(d);
|
|
41
43
|
}
|
|
42
|
-
}
|
|
44
|
+
});
|
|
43
45
|
|
|
44
46
|
if (currentRespAreaList.length >= opts.maxResponseAreas) {
|
|
45
47
|
return;
|
|
@@ -91,7 +93,7 @@ export default function ResponseAreaPlugin(opts) {
|
|
|
91
93
|
name: 'response_area',
|
|
92
94
|
toolbar,
|
|
93
95
|
rules: editor => {
|
|
94
|
-
const { isVoid, isInline } = editor;
|
|
96
|
+
const { isVoid, isInline, onChange } = editor;
|
|
95
97
|
|
|
96
98
|
editor.isVoid = element => {
|
|
97
99
|
return elTypesArray.includes(element.type) ? true : isVoid(element);
|
|
@@ -101,6 +103,62 @@ export default function ResponseAreaPlugin(opts) {
|
|
|
101
103
|
return elTypesArray.includes(element.type) ? true : isInline(element);
|
|
102
104
|
};
|
|
103
105
|
|
|
106
|
+
let oldEditor = cloneDeep(editor);
|
|
107
|
+
|
|
108
|
+
editor.onChange = options => {
|
|
109
|
+
const descendants = Array.from(SlateNode.descendants(editor, { reverse: true })).map(
|
|
110
|
+
([d]) => d
|
|
111
|
+
);
|
|
112
|
+
const type = opts.type.replace(/-/g, '_');
|
|
113
|
+
|
|
114
|
+
if (isUndefined(lastIndexMap[type])) {
|
|
115
|
+
lastIndexMap[type] = 0;
|
|
116
|
+
|
|
117
|
+
descendants.forEach(d => {
|
|
118
|
+
if (d.type === type) {
|
|
119
|
+
const newIndex = parseInt(d.data.index, 10);
|
|
120
|
+
|
|
121
|
+
if (newIndex > lastIndexMap[type]) {
|
|
122
|
+
lastIndexMap[type] = newIndex;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (isEqual(editor, oldEditor)) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const oldDescendants = Array.from(SlateNode.descendants(oldEditor, { reverse: true })).map(
|
|
133
|
+
([d]) => d
|
|
134
|
+
);
|
|
135
|
+
const currentRespAreaList = descendants.filter(isOfCurrentType);
|
|
136
|
+
const oldRespAreaList = oldDescendants.filter(isOfCurrentType);
|
|
137
|
+
|
|
138
|
+
toolbar.disabled = currentRespAreaList.length >= opts.maxResponseAreas;
|
|
139
|
+
|
|
140
|
+
const arrayToFilter =
|
|
141
|
+
oldRespAreaList.length > currentRespAreaList.length
|
|
142
|
+
? oldRespAreaList
|
|
143
|
+
: currentRespAreaList;
|
|
144
|
+
const arrayToUseForFilter =
|
|
145
|
+
arrayToFilter === oldRespAreaList ? currentRespAreaList : oldRespAreaList;
|
|
146
|
+
|
|
147
|
+
const elementsWithChangedStatus = arrayToFilter.filter(
|
|
148
|
+
d => !arrayToUseForFilter.find(e => e.data.index === d.data.index)
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
if (
|
|
152
|
+
elementsWithChangedStatus.length &&
|
|
153
|
+
oldRespAreaList.length > currentRespAreaList.length
|
|
154
|
+
) {
|
|
155
|
+
opts.onHandleAreaChange(elementsWithChangedStatus);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
oldEditor = cloneDeep(editor);
|
|
159
|
+
onChange(options);
|
|
160
|
+
};
|
|
161
|
+
|
|
104
162
|
return editor;
|
|
105
163
|
},
|
|
106
164
|
filterPlugins: (node, plugins) => {
|
|
@@ -110,12 +168,15 @@ export default function ResponseAreaPlugin(opts) {
|
|
|
110
168
|
|
|
111
169
|
return plugins.filter(p => p.name !== 'response_area');
|
|
112
170
|
},
|
|
113
|
-
deleteNode: (e, node,
|
|
171
|
+
deleteNode: (e, node, nodePath, editor, onChange) => {
|
|
114
172
|
e.preventDefault();
|
|
115
173
|
|
|
116
|
-
|
|
174
|
+
editor.apply({
|
|
175
|
+
type: 'remove_node',
|
|
176
|
+
path: nodePath
|
|
177
|
+
});
|
|
117
178
|
|
|
118
|
-
onChange(
|
|
179
|
+
onChange(editor);
|
|
119
180
|
},
|
|
120
181
|
supports: node => elTypesArray.indexOf(node.type) >= 0,
|
|
121
182
|
renderNode(props) {
|
|
@@ -166,49 +227,6 @@ export default function ResponseAreaPlugin(opts) {
|
|
|
166
227
|
);
|
|
167
228
|
}
|
|
168
229
|
},
|
|
169
|
-
onChange(change, editor) {
|
|
170
|
-
const type = opts.type.replace(/-/g, '_');
|
|
171
|
-
|
|
172
|
-
if (isUndefined(lastIndexMap[type])) {
|
|
173
|
-
lastIndexMap[type] = 0;
|
|
174
|
-
|
|
175
|
-
change.value.document.forEachDescendant(d => {
|
|
176
|
-
if (d.type === type) {
|
|
177
|
-
const newIndex = parseInt(d.data.get('index'), 10);
|
|
178
|
-
|
|
179
|
-
if (newIndex > lastIndexMap[type]) {
|
|
180
|
-
lastIndexMap[type] = newIndex;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
if (!editor.value) {
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
const currentRespAreaList = change.value.document.filterDescendants(isOfCurrentType);
|
|
191
|
-
const oldRespAreaList = editor.value.document.filterDescendants(isOfCurrentType);
|
|
192
|
-
|
|
193
|
-
if (currentRespAreaList.size >= opts.maxResponseAreas) {
|
|
194
|
-
toolbar.disabled = true;
|
|
195
|
-
} else {
|
|
196
|
-
toolbar.disabled = false;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
const arrayToFilter =
|
|
200
|
-
oldRespAreaList.size > currentRespAreaList.size ? oldRespAreaList : currentRespAreaList;
|
|
201
|
-
const arrayToUseForFilter =
|
|
202
|
-
arrayToFilter === oldRespAreaList ? currentRespAreaList : oldRespAreaList;
|
|
203
|
-
|
|
204
|
-
const elementsWithChangedStatus = arrayToFilter.filter(
|
|
205
|
-
d => !arrayToUseForFilter.find(e => e.data.get('index') === d.data.get('index'))
|
|
206
|
-
);
|
|
207
|
-
|
|
208
|
-
if (elementsWithChangedStatus.size && oldRespAreaList.size > currentRespAreaList.size) {
|
|
209
|
-
opts.onHandleAreaChange(elementsWithChangedStatus);
|
|
210
|
-
}
|
|
211
|
-
},
|
|
212
230
|
onDrop(event, change, editor) {
|
|
213
231
|
const closestEl = event.target.closest('[data-key]');
|
|
214
232
|
const inline = editor.value.document.findDescendant(d => d.key === closestEl.dataset.key);
|
|
@@ -254,18 +272,14 @@ export const serialization = {
|
|
|
254
272
|
}
|
|
255
273
|
},
|
|
256
274
|
serialize(object) {
|
|
257
|
-
if (object.object !== 'inline') {
|
|
258
|
-
return;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
275
|
switch (object.type) {
|
|
262
276
|
case 'inline_dropdown': {
|
|
263
|
-
const data = object.data
|
|
277
|
+
const data = object.data;
|
|
264
278
|
|
|
265
279
|
return <span data-type="inline_dropdown" data-index={data.index} data-value={data.value} />;
|
|
266
280
|
}
|
|
267
281
|
case 'explicit_constructed_response': {
|
|
268
|
-
const data = object.data
|
|
282
|
+
const data = object.data;
|
|
269
283
|
|
|
270
284
|
return (
|
|
271
285
|
<span
|
|
@@ -276,7 +290,7 @@ export const serialization = {
|
|
|
276
290
|
);
|
|
277
291
|
}
|
|
278
292
|
case 'drag_in_the_blank': {
|
|
279
|
-
const data = object.data
|
|
293
|
+
const data = object.data;
|
|
280
294
|
|
|
281
295
|
return (
|
|
282
296
|
<span
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { Node as SlateNode, Editor, Transforms } from 'slate';
|
|
2
|
+
import { Node as SlateNode, Element as SlateElement, Editor, Transforms } from 'slate';
|
|
3
3
|
import debug from 'debug';
|
|
4
4
|
import GridOn from '@material-ui/icons/GridOn';
|
|
5
5
|
import TableToolbar from './table-toolbar';
|
|
@@ -128,7 +128,70 @@ const TABLE_TYPES = ['tbody', 'tr', 'td', 'table'];
|
|
|
128
128
|
|
|
129
129
|
export default (opts, toolbarPlugins /* : {toolbar: {}}[] */) => {
|
|
130
130
|
const core = {
|
|
131
|
-
utils: {}
|
|
131
|
+
utils: {},
|
|
132
|
+
rules: editor => {
|
|
133
|
+
const { normalizeNode } = editor;
|
|
134
|
+
|
|
135
|
+
editor.normalizeNode = entry => {
|
|
136
|
+
const [tableNode, tablePath] = entry;
|
|
137
|
+
const tableParent = SlateNode.get(editor, tablePath.slice(0, -1));
|
|
138
|
+
|
|
139
|
+
// If the element is a paragraph, ensure its children are valid.
|
|
140
|
+
if (SlateElement.isElement(tableNode) && tableNode.type === 'table') {
|
|
141
|
+
const emptyBlock = {
|
|
142
|
+
type: 'paragraph',
|
|
143
|
+
children: [{ text: '' }]
|
|
144
|
+
};
|
|
145
|
+
const tableIndex = tablePath.slice(-1)[0];
|
|
146
|
+
|
|
147
|
+
// if table is the first element, we need to add a space before
|
|
148
|
+
// so users can focus before the table
|
|
149
|
+
if (tableIndex === 0) {
|
|
150
|
+
const beforeTablePath = [...tablePath.slice(0, -1), 0];
|
|
151
|
+
|
|
152
|
+
editor.apply({
|
|
153
|
+
type: 'insert_node',
|
|
154
|
+
path: beforeTablePath,
|
|
155
|
+
node: emptyBlock
|
|
156
|
+
});
|
|
157
|
+
editor.continueNormalization();
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// if table is the last element, we add element after it
|
|
162
|
+
if (tableParent.children.length - 1 === tableIndex) {
|
|
163
|
+
const afterTablePath = [...tablePath.slice(0, -1), tableIndex + 1];
|
|
164
|
+
|
|
165
|
+
editor.apply({
|
|
166
|
+
type: 'insert_node',
|
|
167
|
+
path: afterTablePath,
|
|
168
|
+
node: emptyBlock
|
|
169
|
+
});
|
|
170
|
+
editor.continueNormalization();
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// if table does not have a tbody, we add it
|
|
175
|
+
if (tableNode.children[0].type !== 'tbody') {
|
|
176
|
+
const tBodyNode = { type: 'tbody', children: [] };
|
|
177
|
+
|
|
178
|
+
Transforms.wrapNodes(editor, tBodyNode, {
|
|
179
|
+
at: {
|
|
180
|
+
anchor: { path: [...tablePath, 0], offset: 0 },
|
|
181
|
+
focus: { path: [...tablePath, tableNode.children.length], offset: 0 }
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
editor.continueNormalization();
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Fall back to the original `normalizeNode` to enforce other constraints.
|
|
190
|
+
normalizeNode(entry);
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
return editor;
|
|
194
|
+
}
|
|
132
195
|
};
|
|
133
196
|
|
|
134
197
|
core.utils.createTable = (row = 2, columns = 2) => {
|
|
@@ -192,7 +255,7 @@ export default (opts, toolbarPlugins /* : {toolbar: {}}[] */) => {
|
|
|
192
255
|
/**
|
|
193
256
|
* Note - the node may not be a table node - it may be a node inside a table.
|
|
194
257
|
*/
|
|
195
|
-
customToolbar: (node,
|
|
258
|
+
customToolbar: (node, nodePath, editor, onToolbarDone) => {
|
|
196
259
|
log('[customToolbar] node.data: ', node.data);
|
|
197
260
|
|
|
198
261
|
const [tableBlock] = core.utils.getTableBlock(editor);
|
|
@@ -317,7 +380,7 @@ export default (opts, toolbarPlugins /* : {toolbar: {}}[] */) => {
|
|
|
317
380
|
|
|
318
381
|
const onDone = () => {
|
|
319
382
|
log('[onDone] call onToolbarDone...');
|
|
320
|
-
onToolbarDone(
|
|
383
|
+
onToolbarDone(true);
|
|
321
384
|
};
|
|
322
385
|
|
|
323
386
|
const Tb = () => (
|
|
@@ -339,7 +402,7 @@ export default (opts, toolbarPlugins /* : {toolbar: {}}[] */) => {
|
|
|
339
402
|
}
|
|
340
403
|
};
|
|
341
404
|
|
|
342
|
-
core.supports =
|
|
405
|
+
core.supports = node => TABLE_TYPES.includes(node.type);
|
|
343
406
|
|
|
344
407
|
const Node = props => {
|
|
345
408
|
switch (props.node.type) {
|
|
@@ -16,7 +16,7 @@ import { removeDialogs as removeCharacterDialogs } from '../characters';
|
|
|
16
16
|
|
|
17
17
|
const log = debug('@pie-lib:editable-html:plugins:toolbar');
|
|
18
18
|
|
|
19
|
-
const getCustomToolbar = (plugin, node,
|
|
19
|
+
const getCustomToolbar = (plugin, node, nodePath, editor, handleDone) => {
|
|
20
20
|
if (!plugin) {
|
|
21
21
|
return;
|
|
22
22
|
}
|
|
@@ -33,7 +33,7 @@ const getCustomToolbar = (plugin, node, value, editor, handleDone) => {
|
|
|
33
33
|
return plugin.toolbar.CustomToolbarComp;
|
|
34
34
|
} else if (typeof plugin.toolbar.customToolbar === 'function') {
|
|
35
35
|
log('deprecated - use CustomToolbarComp');
|
|
36
|
-
return plugin.toolbar.customToolbar(node,
|
|
36
|
+
return plugin.toolbar.customToolbar(node, nodePath, editor, handleDone);
|
|
37
37
|
}
|
|
38
38
|
};
|
|
39
39
|
|
|
@@ -120,12 +120,12 @@ export const Toolbar = props => {
|
|
|
120
120
|
};
|
|
121
121
|
};
|
|
122
122
|
|
|
123
|
-
const onToolbarDone = (
|
|
124
|
-
log('[onToolbarDone] change: ',
|
|
123
|
+
const onToolbarDone = (editor, finishEditing) => {
|
|
124
|
+
log('[onToolbarDone] change: ', editor, 'finishEditing: ', finishEditing);
|
|
125
125
|
const { onChange, onDone } = props;
|
|
126
126
|
|
|
127
|
-
if (
|
|
128
|
-
onChange(
|
|
127
|
+
if (editor) {
|
|
128
|
+
onChange(editor, () => {
|
|
129
129
|
if (finishEditing) {
|
|
130
130
|
onDone();
|
|
131
131
|
}
|
|
@@ -139,13 +139,13 @@ export const Toolbar = props => {
|
|
|
139
139
|
};
|
|
140
140
|
|
|
141
141
|
const onDeleteClick = debounce(
|
|
142
|
-
(e, plugin, node,
|
|
142
|
+
(e, plugin, node, nodePath, editor, onChange) => plugin.deleteNode(e, node, nodePath, editor, onChange),
|
|
143
143
|
500
|
|
144
144
|
);
|
|
145
145
|
|
|
146
|
-
const onDeleteMouseDown =
|
|
146
|
+
const onDeleteMouseDown = e => {
|
|
147
147
|
e.persist();
|
|
148
|
-
onDeleteClick(e, plugin, node,
|
|
148
|
+
onDeleteClick(e, plugin, node, nodePath, editor, onChange);
|
|
149
149
|
};
|
|
150
150
|
|
|
151
151
|
const {
|
|
@@ -210,21 +210,28 @@ export const Toolbar = props => {
|
|
|
210
210
|
|
|
211
211
|
log('[render] plugin: ', plugin);
|
|
212
212
|
|
|
213
|
-
const handleDone =
|
|
213
|
+
const handleDone = done => {
|
|
214
|
+
const isDone = done ? editor : null;
|
|
214
215
|
let handler = onDone;
|
|
215
216
|
|
|
216
217
|
if (plugin && plugin.toolbar && plugin.toolbar.customToolbar) {
|
|
217
218
|
handler = onToolbarDone;
|
|
218
219
|
}
|
|
219
220
|
|
|
220
|
-
|
|
221
|
+
if (isDone) {
|
|
222
|
+
editor.selection = null;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
handler(isDone);
|
|
221
226
|
|
|
222
227
|
if (parentPlugin && parentPlugin.handleDone) {
|
|
223
|
-
parentPlugin.handleDone(
|
|
228
|
+
parentPlugin.handleDone(isDone, node, plugin);
|
|
224
229
|
}
|
|
225
230
|
};
|
|
226
231
|
|
|
227
|
-
const CustomToolbar =
|
|
232
|
+
const CustomToolbar =
|
|
233
|
+
getCustomToolbar(plugin, node, nodePath, editor, handleDone) ||
|
|
234
|
+
getCustomToolbar(parentPlugin, node, nodePath, editor, handleDone);
|
|
228
235
|
|
|
229
236
|
const filteredPlugins =
|
|
230
237
|
plugin && plugin.filterPlugins ? plugin.filterPlugins(node, plugins) : plugins;
|
|
@@ -290,7 +297,7 @@ export const Toolbar = props => {
|
|
|
290
297
|
<IconButton
|
|
291
298
|
aria-label="Delete"
|
|
292
299
|
className={classes.iconRoot}
|
|
293
|
-
onMouseDown={e => onDeleteMouseDown(e
|
|
300
|
+
onMouseDown={e => onDeleteMouseDown(e)}
|
|
294
301
|
classes={{
|
|
295
302
|
root: classes.iconRoot
|
|
296
303
|
}}
|