@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.
- package/CHANGELOG.json +1 -1
- package/CHANGELOG.md +81 -0
- package/LICENSE.md +5 -0
- package/lib/editor.js +410 -543
- package/lib/editor.js.map +1 -1
- package/lib/index.js +200 -101
- package/lib/index.js.map +1 -1
- package/lib/parse-html.js +5 -6
- package/lib/parse-html.js.map +1 -1
- package/lib/plugins/characters/custom-popper.js +12 -2
- package/lib/plugins/characters/custom-popper.js.map +1 -1
- package/lib/plugins/characters/index.js +71 -19
- package/lib/plugins/characters/index.js.map +1 -1
- package/lib/plugins/characters/utils.js.map +1 -1
- package/lib/plugins/html/icons/index.js +38 -0
- package/lib/plugins/html/icons/index.js.map +1 -0
- package/lib/plugins/html/index.js +75 -0
- package/lib/plugins/html/index.js.map +1 -0
- package/lib/plugins/image/alt-dialog.js +26 -0
- package/lib/plugins/image/alt-dialog.js.map +1 -1
- package/lib/plugins/image/component.js +124 -90
- package/lib/plugins/image/component.js.map +1 -1
- package/lib/plugins/image/image-toolbar.js +45 -7
- package/lib/plugins/image/image-toolbar.js.map +1 -1
- package/lib/plugins/image/index.js +91 -113
- package/lib/plugins/image/index.js.map +1 -1
- package/lib/plugins/image/insert-image-handler.js +54 -72
- package/lib/plugins/image/insert-image-handler.js.map +1 -1
- package/lib/plugins/index.js +71 -31
- package/lib/plugins/index.js.map +1 -1
- package/lib/plugins/list/index.js +129 -58
- package/lib/plugins/list/index.js.map +1 -1
- package/lib/plugins/math/index.js +152 -118
- package/lib/plugins/math/index.js.map +1 -1
- package/lib/plugins/media/index.js +185 -168
- package/lib/plugins/media/index.js.map +1 -1
- package/lib/plugins/media/media-dialog.js +197 -110
- package/lib/plugins/media/media-dialog.js.map +1 -1
- package/lib/plugins/media/media-toolbar.js +24 -4
- package/lib/plugins/media/media-toolbar.js.map +1 -1
- package/lib/plugins/media/media-wrapper.js +65 -23
- package/lib/plugins/media/media-wrapper.js.map +1 -1
- package/lib/plugins/respArea/drag-in-the-blank/choice.js +50 -10
- package/lib/plugins/respArea/drag-in-the-blank/choice.js.map +1 -1
- package/lib/plugins/respArea/drag-in-the-blank/index.js +22 -9
- package/lib/plugins/respArea/drag-in-the-blank/index.js.map +1 -1
- package/lib/plugins/respArea/explicit-constructed-response/index.js +9 -4
- package/lib/plugins/respArea/explicit-constructed-response/index.js.map +1 -1
- package/lib/plugins/respArea/icons/index.js +18 -1
- package/lib/plugins/respArea/icons/index.js.map +1 -1
- package/lib/plugins/respArea/index.js +133 -122
- package/lib/plugins/respArea/index.js.map +1 -1
- package/lib/plugins/respArea/inline-dropdown/index.js +10 -4
- package/lib/plugins/respArea/inline-dropdown/index.js.map +1 -1
- package/lib/plugins/respArea/utils.js +33 -15
- package/lib/plugins/respArea/utils.js.map +1 -1
- package/lib/plugins/table/icons/index.js +7 -0
- package/lib/plugins/table/icons/index.js.map +1 -1
- package/lib/plugins/table/index.js +279 -390
- package/lib/plugins/table/index.js.map +1 -1
- package/lib/plugins/table/table-toolbar.js +47 -14
- package/lib/plugins/table/table-toolbar.js.map +1 -1
- package/lib/plugins/toolbar/default-toolbar.js +63 -51
- package/lib/plugins/toolbar/default-toolbar.js.map +1 -1
- package/lib/plugins/toolbar/done-button.js +9 -1
- package/lib/plugins/toolbar/done-button.js.map +1 -1
- package/lib/plugins/toolbar/editor-and-toolbar.js +140 -83
- package/lib/plugins/toolbar/editor-and-toolbar.js.map +1 -1
- package/lib/plugins/toolbar/index.js +5 -0
- package/lib/plugins/toolbar/index.js.map +1 -1
- package/lib/plugins/toolbar/toolbar-buttons.js +39 -8
- package/lib/plugins/toolbar/toolbar-buttons.js.map +1 -1
- package/lib/plugins/toolbar/toolbar.js +261 -225
- package/lib/plugins/toolbar/toolbar.js.map +1 -1
- package/lib/plugins/utils.js +16 -19
- package/lib/plugins/utils.js.map +1 -1
- package/lib/serialization.js +70 -11
- package/lib/serialization.js.map +1 -1
- package/lib/theme.js.map +1 -1
- package/package.json +18 -17
- package/src/editor.jsx +139 -434
- package/src/index.jsx +96 -62
- package/src/plugins/characters/index.jsx +17 -12
- package/src/plugins/html/icons/index.jsx +19 -0
- package/src/plugins/html/index.jsx +68 -0
- package/src/plugins/image/component.jsx +38 -60
- package/src/plugins/image/index.jsx +42 -95
- package/src/plugins/image/insert-image-handler.js +27 -62
- package/src/plugins/index.jsx +39 -21
- package/src/plugins/list/index.jsx +90 -62
- package/src/plugins/math/index.jsx +70 -93
- package/src/plugins/media/index.jsx +117 -146
- package/src/plugins/media/media-dialog.js +9 -10
- package/src/plugins/media/media-wrapper.jsx +27 -29
- package/src/plugins/respArea/drag-in-the-blank/index.jsx +4 -5
- package/src/plugins/respArea/explicit-constructed-response/index.jsx +1 -2
- package/src/plugins/respArea/index.jsx +84 -114
- package/src/plugins/respArea/inline-dropdown/index.jsx +2 -3
- package/src/plugins/respArea/utils.jsx +28 -23
- package/src/plugins/table/index.jsx +214 -334
- package/src/plugins/table/table-toolbar.jsx +4 -3
- package/src/plugins/toolbar/default-toolbar.jsx +30 -48
- package/src/plugins/toolbar/editor-and-toolbar.jsx +114 -114
- package/src/plugins/toolbar/toolbar.jsx +224 -254
- package/src/plugins/utils.js +0 -16
- package/src/serialization.jsx +1 -1
- package/lib/components.js +0 -92
- package/lib/components.js.map +0 -1
- package/lib/new-serialization.js +0 -280
- package/lib/new-serialization.js.map +0 -1
- package/lib/plugins/hotKeys/index.js +0 -60
- package/lib/plugins/hotKeys/index.js.map +0 -1
- package/lib/test-serializer.js +0 -138
- package/lib/test-serializer.js.map +0 -1
- package/src/components.js +0 -135
- package/src/new-serialization.jsx +0 -310
- package/src/plugins/hotKeys/index.js +0 -54
- package/src/test-serializer.js +0 -132
package/src/editor.jsx
CHANGED
|
@@ -1,369 +1,22 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import RootRef from '@material-ui/core/RootRef';
|
|
1
|
+
import { Editor as SlateEditor, findNode, getEventRange, getEventTransfer } from 'slate-react';
|
|
2
|
+
import SlateTypes from 'slate-prop-types';
|
|
4
3
|
|
|
5
4
|
import isEqual from 'lodash/isEqual';
|
|
6
|
-
import * as serialization from './
|
|
5
|
+
import * as serialization from './serialization';
|
|
7
6
|
import PropTypes from 'prop-types';
|
|
7
|
+
import React from 'react';
|
|
8
8
|
import { Value, Block, Inline } from 'slate';
|
|
9
|
-
import { ALL_PLUGINS, DEFAULT_PLUGINS
|
|
9
|
+
import { buildPlugins, ALL_PLUGINS, DEFAULT_PLUGINS } from './plugins';
|
|
10
10
|
import debug from 'debug';
|
|
11
11
|
import { withStyles } from '@material-ui/core/styles';
|
|
12
12
|
import classNames from 'classnames';
|
|
13
13
|
import { color } from '@pie-lib/render-ui';
|
|
14
14
|
import Plain from 'slate-plain-serializer';
|
|
15
|
+
import { AlertDialog } from '@pie-lib/config-ui';
|
|
15
16
|
|
|
16
|
-
import { getBase64
|
|
17
|
+
import { getBase64 } from './serialization';
|
|
17
18
|
import InsertImageHandler from './plugins/image/insert-image-handler';
|
|
18
19
|
|
|
19
|
-
import isHotkey from 'is-hotkey';
|
|
20
|
-
import { ReactEditor, useSlateStatic, Editable, useFocused, useSlate, Slate } from 'slate-react';
|
|
21
|
-
import { Node as SlateNode, Path, Editor, Transforms, createEditor, Element as SlateElement } from 'slate';
|
|
22
|
-
|
|
23
|
-
import { Button, Icon } from './components';
|
|
24
|
-
import EditorAndToolbar from './plugins/toolbar/editor-and-toolbar';
|
|
25
|
-
|
|
26
|
-
window.Path = Path;
|
|
27
|
-
window.SlateNode = SlateNode;
|
|
28
|
-
window.ReactEditor = ReactEditor;
|
|
29
|
-
window.Editor = Editor;
|
|
30
|
-
|
|
31
|
-
const HOTKEYS = {
|
|
32
|
-
'mod+b': 'bold',
|
|
33
|
-
'mod+i': 'italic',
|
|
34
|
-
'mod+u': 'underline',
|
|
35
|
-
'mod+`': 'code',
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
const LIST_TYPES = ['numbered-list', 'bulleted-list'];
|
|
39
|
-
const TEXT_ALIGN_TYPES = ['left', 'center', 'right', 'justify'];
|
|
40
|
-
|
|
41
|
-
const initialValue = [
|
|
42
|
-
{
|
|
43
|
-
type: 'paragraph',
|
|
44
|
-
children: [
|
|
45
|
-
{
|
|
46
|
-
type: 'math',
|
|
47
|
-
data: {
|
|
48
|
-
latex: '\\frac{1}{2}',
|
|
49
|
-
wrapper: 'round_brackets',
|
|
50
|
-
},
|
|
51
|
-
children: [
|
|
52
|
-
{
|
|
53
|
-
text: '\\(\\frac{1}{2}\\)',
|
|
54
|
-
},
|
|
55
|
-
],
|
|
56
|
-
},
|
|
57
|
-
],
|
|
58
|
-
},
|
|
59
|
-
];
|
|
60
|
-
|
|
61
|
-
const SlateEditor = (editorProps) => {
|
|
62
|
-
const mounted = useRef(false);
|
|
63
|
-
const { autoFocus, value, plugins, actionsRef, onEditingDone } = editorProps;
|
|
64
|
-
const renderElement = useCallback((props) => <Element {...props} plugins={plugins} />, []);
|
|
65
|
-
const renderLeaf = useCallback((props) => <Leaf {...props} />, []);
|
|
66
|
-
const editor = useMemo(() => withPlugins(createEditor(), plugins), []);
|
|
67
|
-
const [isFocused, setIsFocused] = useState(false);
|
|
68
|
-
const editorRef = useRef(null);
|
|
69
|
-
|
|
70
|
-
useEffect(() => {
|
|
71
|
-
mounted.current = true;
|
|
72
|
-
|
|
73
|
-
return () => {
|
|
74
|
-
mounted.current = false;
|
|
75
|
-
};
|
|
76
|
-
}, []);
|
|
77
|
-
|
|
78
|
-
useEffect(() => {
|
|
79
|
-
if (editorProps.onEditor) {
|
|
80
|
-
editorProps.onEditor(editor);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (autoFocus) {
|
|
84
|
-
Transforms.select(editor, [0, 0]);
|
|
85
|
-
ReactEditor.focus(editor);
|
|
86
|
-
|
|
87
|
-
if (mounted.current) {
|
|
88
|
-
setIsFocused(true);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}, [editor]);
|
|
92
|
-
|
|
93
|
-
const slateValue = useMemo(() => {
|
|
94
|
-
// Slate throws an error if the value on the initial render is invalid
|
|
95
|
-
// so we directly set the value on the editor in order
|
|
96
|
-
// to be able to trigger normalization on the initial value before rendering
|
|
97
|
-
editor.children = value;
|
|
98
|
-
editor.marks = {};
|
|
99
|
-
Editor.normalize(editor, { force: true });
|
|
100
|
-
// We return the normalized internal value so that the rendering can take over from here
|
|
101
|
-
return editor.children;
|
|
102
|
-
}, [editor, value]);
|
|
103
|
-
|
|
104
|
-
window.editor = editor;
|
|
105
|
-
|
|
106
|
-
const onKeyDown = (event) => {
|
|
107
|
-
if (event.key === 'Enter' && event.shiftKey === true) {
|
|
108
|
-
editor.insertText('\n');
|
|
109
|
-
event.preventDefault();
|
|
110
|
-
event.stopPropagation();
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
for (const hotkey in HOTKEYS) {
|
|
114
|
-
if (isHotkey(hotkey, event)) {
|
|
115
|
-
event.preventDefault();
|
|
116
|
-
const mark = HOTKEYS[hotkey];
|
|
117
|
-
toggleMark(editor, mark);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
};
|
|
121
|
-
const onFocus = () => setIsFocused(true);
|
|
122
|
-
const onBlur = (e) => {
|
|
123
|
-
setTimeout(() => {
|
|
124
|
-
if (!editorRef.current || !editorRef.current.contains(document.activeElement)) {
|
|
125
|
-
if (editorProps.onBlur) {
|
|
126
|
-
editorProps.onBlur(e);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
if (mounted.current) {
|
|
130
|
-
setIsFocused(false);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}, 50);
|
|
134
|
-
};
|
|
135
|
-
const actions = {
|
|
136
|
-
focus: (position, node) => {
|
|
137
|
-
const [, textPath] = node
|
|
138
|
-
? Editor.leaf(editor, ReactEditor.findPath(editor, node), { edge: 'end' })
|
|
139
|
-
: Editor.leaf(editor, [0], { edge: 'end' });
|
|
140
|
-
|
|
141
|
-
Transforms.select(editor, textPath);
|
|
142
|
-
ReactEditor.focus(editor);
|
|
143
|
-
},
|
|
144
|
-
finishEditing: () => {
|
|
145
|
-
// if (!mounted.current) {
|
|
146
|
-
// return;
|
|
147
|
-
// }
|
|
148
|
-
|
|
149
|
-
if (typeof onEditingDone === 'function') {
|
|
150
|
-
onEditingDone(editor);
|
|
151
|
-
}
|
|
152
|
-
},
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
if (actionsRef) {
|
|
156
|
-
actionsRef(actions);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
return (
|
|
160
|
-
<Slate editor={editor} initialValue={slateValue}>
|
|
161
|
-
<RootRef rootRef={editorRef}>
|
|
162
|
-
<EditorAndToolbar
|
|
163
|
-
{...editorProps}
|
|
164
|
-
editor={editor}
|
|
165
|
-
isFocused={isFocused}
|
|
166
|
-
onDone={() => {
|
|
167
|
-
setIsFocused(false);
|
|
168
|
-
document.activeElement.blur();
|
|
169
|
-
editorProps.onDone(editor);
|
|
170
|
-
}}
|
|
171
|
-
>
|
|
172
|
-
<Editable
|
|
173
|
-
renderElement={renderElement}
|
|
174
|
-
renderLeaf={renderLeaf}
|
|
175
|
-
placeholder="Enter some rich text…"
|
|
176
|
-
spellCheck
|
|
177
|
-
onKeyDown={onKeyDown}
|
|
178
|
-
onFocus={onFocus}
|
|
179
|
-
onBlur={onBlur}
|
|
180
|
-
/>
|
|
181
|
-
</EditorAndToolbar>
|
|
182
|
-
</RootRef>
|
|
183
|
-
</Slate>
|
|
184
|
-
);
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
const toggleBlock = (editor, format) => {
|
|
188
|
-
const isActive = isBlockActive(editor, format, TEXT_ALIGN_TYPES.includes(format) ? 'align' : 'type');
|
|
189
|
-
const isList = LIST_TYPES.includes(format);
|
|
190
|
-
|
|
191
|
-
Transforms.unwrapNodes(editor, {
|
|
192
|
-
match: (n) =>
|
|
193
|
-
!Editor.isEditor(n) &&
|
|
194
|
-
SlateElement.isElement(n) &&
|
|
195
|
-
LIST_TYPES.includes(n.type) &&
|
|
196
|
-
!TEXT_ALIGN_TYPES.includes(format),
|
|
197
|
-
split: true,
|
|
198
|
-
});
|
|
199
|
-
let newProperties;
|
|
200
|
-
if (TEXT_ALIGN_TYPES.includes(format)) {
|
|
201
|
-
newProperties = {
|
|
202
|
-
align: isActive ? undefined : format,
|
|
203
|
-
};
|
|
204
|
-
} else {
|
|
205
|
-
newProperties = {
|
|
206
|
-
type: isActive ? 'paragraph' : isList ? 'list_item' : format,
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
Transforms.setNodes(editor, newProperties);
|
|
210
|
-
|
|
211
|
-
if (!isActive && isList) {
|
|
212
|
-
const block = { type: format, children: [] };
|
|
213
|
-
Transforms.wrapNodes(editor, block);
|
|
214
|
-
}
|
|
215
|
-
};
|
|
216
|
-
|
|
217
|
-
const toggleMark = (editor, format) => {
|
|
218
|
-
const isActive = isMarkActive(editor, format);
|
|
219
|
-
|
|
220
|
-
if (isActive) {
|
|
221
|
-
Editor.removeMark(editor, format);
|
|
222
|
-
} else {
|
|
223
|
-
Editor.addMark(editor, format, true);
|
|
224
|
-
}
|
|
225
|
-
};
|
|
226
|
-
|
|
227
|
-
const isBlockActive = (editor, format, blockType = 'type') => {
|
|
228
|
-
const { selection } = editor;
|
|
229
|
-
if (!selection) return false;
|
|
230
|
-
|
|
231
|
-
const [match] = Array.from(
|
|
232
|
-
Editor.nodes(editor, {
|
|
233
|
-
at: Editor.unhangRange(editor, selection),
|
|
234
|
-
match: (n) => !Editor.isEditor(n) && SlateElement.isElement(n) && n[blockType] === format,
|
|
235
|
-
}),
|
|
236
|
-
);
|
|
237
|
-
|
|
238
|
-
return !!match;
|
|
239
|
-
};
|
|
240
|
-
|
|
241
|
-
const isMarkActive = (editor, format) => {
|
|
242
|
-
const marks = Editor.marks(editor);
|
|
243
|
-
return marks ? marks[format] === true : false;
|
|
244
|
-
};
|
|
245
|
-
|
|
246
|
-
const Element = (props) => {
|
|
247
|
-
const editor = useSlateStatic();
|
|
248
|
-
const focused = useFocused();
|
|
249
|
-
const { attributes, children, element, plugins } = props;
|
|
250
|
-
const style = { textAlign: element.align };
|
|
251
|
-
|
|
252
|
-
const nodeProps = { ...attributes, ...props, node: { ...element }, children };
|
|
253
|
-
const pluginToRender = plugins.find((plugin) => typeof plugin.supports === 'function' && plugin.supports(element));
|
|
254
|
-
|
|
255
|
-
if (pluginToRender) {
|
|
256
|
-
return pluginToRender.renderNode({ ...nodeProps, editor, focused });
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
switch (element.type) {
|
|
260
|
-
case 'block-quote':
|
|
261
|
-
return (
|
|
262
|
-
<blockquote style={style} {...attributes}>
|
|
263
|
-
{children}
|
|
264
|
-
</blockquote>
|
|
265
|
-
);
|
|
266
|
-
case 'bulleted-list':
|
|
267
|
-
return (
|
|
268
|
-
<ul style={style} {...attributes}>
|
|
269
|
-
{children}
|
|
270
|
-
</ul>
|
|
271
|
-
);
|
|
272
|
-
case 'heading-one':
|
|
273
|
-
return (
|
|
274
|
-
<h1 style={style} {...attributes}>
|
|
275
|
-
{children}
|
|
276
|
-
</h1>
|
|
277
|
-
);
|
|
278
|
-
case 'heading-two':
|
|
279
|
-
return (
|
|
280
|
-
<h2 style={style} {...attributes}>
|
|
281
|
-
{children}
|
|
282
|
-
</h2>
|
|
283
|
-
);
|
|
284
|
-
case 'list-item':
|
|
285
|
-
return (
|
|
286
|
-
<li style={style} {...attributes}>
|
|
287
|
-
{children}
|
|
288
|
-
</li>
|
|
289
|
-
);
|
|
290
|
-
case 'numbered-list':
|
|
291
|
-
return (
|
|
292
|
-
<ol style={style} {...attributes}>
|
|
293
|
-
{children}
|
|
294
|
-
</ol>
|
|
295
|
-
);
|
|
296
|
-
default:
|
|
297
|
-
return (
|
|
298
|
-
<div
|
|
299
|
-
style={{
|
|
300
|
-
...style,
|
|
301
|
-
margin: 0,
|
|
302
|
-
}}
|
|
303
|
-
{...attributes}
|
|
304
|
-
>
|
|
305
|
-
{children}
|
|
306
|
-
</div>
|
|
307
|
-
);
|
|
308
|
-
}
|
|
309
|
-
};
|
|
310
|
-
|
|
311
|
-
const Leaf = ({ attributes, children, leaf }) => {
|
|
312
|
-
if (leaf.bold) {
|
|
313
|
-
children = <strong>{children}</strong>;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
if (leaf.code) {
|
|
317
|
-
children = <code>{children}</code>;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
if (leaf.italic) {
|
|
321
|
-
children = <em>{children}</em>;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
if (leaf.underline) {
|
|
325
|
-
children = <u>{children}</u>;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
if (leaf.strikethrough) {
|
|
329
|
-
children = <del>{children}</del>;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
return <span {...attributes}>{children}</span>;
|
|
333
|
-
};
|
|
334
|
-
|
|
335
|
-
const BlockButton = ({ format, icon }) => {
|
|
336
|
-
const editor = useSlate();
|
|
337
|
-
return (
|
|
338
|
-
<Button
|
|
339
|
-
active={isBlockActive(editor, format, TEXT_ALIGN_TYPES.includes(format) ? 'align' : 'type')}
|
|
340
|
-
onMouseDown={(event) => {
|
|
341
|
-
event.preventDefault();
|
|
342
|
-
toggleBlock(editor, format);
|
|
343
|
-
}}
|
|
344
|
-
>
|
|
345
|
-
<Icon>{icon}</Icon>
|
|
346
|
-
</Button>
|
|
347
|
-
);
|
|
348
|
-
};
|
|
349
|
-
|
|
350
|
-
const MarkButton = ({ format, icon }) => {
|
|
351
|
-
const editor = useSlate();
|
|
352
|
-
return (
|
|
353
|
-
<Button
|
|
354
|
-
active={isMarkActive(editor, format)}
|
|
355
|
-
onMouseDown={(event) => {
|
|
356
|
-
event.preventDefault();
|
|
357
|
-
toggleMark(editor, format);
|
|
358
|
-
}}
|
|
359
|
-
>
|
|
360
|
-
<Icon>{icon}</Icon>
|
|
361
|
-
</Button>
|
|
362
|
-
);
|
|
363
|
-
};
|
|
364
|
-
|
|
365
|
-
// old-editable
|
|
366
|
-
|
|
367
20
|
export { ALL_PLUGINS, DEFAULT_PLUGINS, serialization };
|
|
368
21
|
|
|
369
22
|
const log = debug('editable-html:editor');
|
|
@@ -384,31 +37,27 @@ const defaultResponseAreaProps = {
|
|
|
384
37
|
|
|
385
38
|
const defaultLanguageCharactersProps = [];
|
|
386
39
|
|
|
387
|
-
const createToolbarOpts = (toolbarOpts, error) => {
|
|
40
|
+
const createToolbarOpts = (toolbarOpts, error, isHtmlMode) => {
|
|
388
41
|
return {
|
|
389
42
|
...defaultToolbarOpts,
|
|
390
43
|
...toolbarOpts,
|
|
391
44
|
error,
|
|
45
|
+
isHtmlMode,
|
|
392
46
|
};
|
|
393
47
|
};
|
|
394
48
|
|
|
395
|
-
export class
|
|
49
|
+
export class Editor extends React.Component {
|
|
396
50
|
static propTypes = {
|
|
397
51
|
autoFocus: PropTypes.bool,
|
|
52
|
+
editorRef: PropTypes.func.isRequired,
|
|
398
53
|
error: PropTypes.any,
|
|
399
54
|
onRef: PropTypes.func.isRequired,
|
|
400
55
|
onChange: PropTypes.func.isRequired,
|
|
401
|
-
onEditor: PropTypes.func,
|
|
402
56
|
onFocus: PropTypes.func,
|
|
403
57
|
onBlur: PropTypes.func,
|
|
404
58
|
onKeyDown: PropTypes.func,
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
type: PropTypes.string,
|
|
408
|
-
children: PropTypes.array,
|
|
409
|
-
data: PropTypes.object,
|
|
410
|
-
}),
|
|
411
|
-
),
|
|
59
|
+
focus: PropTypes.func.isRequired,
|
|
60
|
+
value: SlateTypes.value.isRequired,
|
|
412
61
|
imageSupport: PropTypes.object,
|
|
413
62
|
mathMlOptions: PropTypes.shape({
|
|
414
63
|
mmlOutput: PropTypes.bool,
|
|
@@ -485,8 +134,15 @@ export class EditorComponent extends React.Component {
|
|
|
485
134
|
this.state = {
|
|
486
135
|
value: props.value,
|
|
487
136
|
toolbarOpts: createToolbarOpts(props.toolbarOpts, props.error),
|
|
137
|
+
isHtmlMode: false,
|
|
138
|
+
isEdited: false,
|
|
139
|
+
dialog: {
|
|
140
|
+
open: false,
|
|
141
|
+
},
|
|
488
142
|
};
|
|
489
143
|
|
|
144
|
+
this.toggleHtmlMode = this.toggleHtmlMode.bind(this);
|
|
145
|
+
|
|
490
146
|
this.onResize = () => {
|
|
491
147
|
props.onChange(this.state.value, true);
|
|
492
148
|
};
|
|
@@ -494,12 +150,48 @@ export class EditorComponent extends React.Component {
|
|
|
494
150
|
this.handlePlugins(this.props);
|
|
495
151
|
}
|
|
496
152
|
|
|
153
|
+
handleAlertDialog = (open, extraDialogProps, callback) => {
|
|
154
|
+
this.setState(
|
|
155
|
+
{
|
|
156
|
+
dialog: {
|
|
157
|
+
open,
|
|
158
|
+
...extraDialogProps,
|
|
159
|
+
},
|
|
160
|
+
isEdited: false,
|
|
161
|
+
},
|
|
162
|
+
callback,
|
|
163
|
+
);
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
toggleHtmlMode = () => {
|
|
167
|
+
this.setState(
|
|
168
|
+
(prevState) => ({
|
|
169
|
+
isHtmlMode: !prevState.isHtmlMode,
|
|
170
|
+
}),
|
|
171
|
+
() => {
|
|
172
|
+
const { error } = this.props;
|
|
173
|
+
const { toolbarOpts } = this.state;
|
|
174
|
+
const newToolbarOpts = createToolbarOpts(toolbarOpts, error, this.state.isHtmlMode);
|
|
175
|
+
this.setState({
|
|
176
|
+
toolbarOpts: newToolbarOpts,
|
|
177
|
+
});
|
|
178
|
+
},
|
|
179
|
+
);
|
|
180
|
+
};
|
|
181
|
+
|
|
497
182
|
handlePlugins = (props) => {
|
|
498
183
|
const normalizedResponseAreaProps = {
|
|
499
184
|
...defaultResponseAreaProps,
|
|
500
185
|
...props.responseAreaProps,
|
|
501
186
|
};
|
|
502
187
|
|
|
188
|
+
const htmlPluginOpts = {
|
|
189
|
+
isHtmlMode: this.state.isHtmlMode,
|
|
190
|
+
isEdited: this.state.isEdited,
|
|
191
|
+
toggleHtmlMode: this.toggleHtmlMode,
|
|
192
|
+
handleAlertDialog: this.handleAlertDialog,
|
|
193
|
+
};
|
|
194
|
+
|
|
503
195
|
this.plugins = buildPlugins(props.activePlugins, {
|
|
504
196
|
math: {
|
|
505
197
|
onClick: this.onMathClick,
|
|
@@ -507,6 +199,7 @@ export class EditorComponent extends React.Component {
|
|
|
507
199
|
onBlur: this.onPluginBlur,
|
|
508
200
|
...props.mathMlOptions,
|
|
509
201
|
},
|
|
202
|
+
html: htmlPluginOpts,
|
|
510
203
|
image: {
|
|
511
204
|
disableImageAlignmentButtons: props.disableImageAlignmentButtons,
|
|
512
205
|
onDelete:
|
|
@@ -540,6 +233,21 @@ export class EditorComponent extends React.Component {
|
|
|
540
233
|
disableScrollbar: !!props.disableScrollbar,
|
|
541
234
|
disableUnderline: props.disableUnderline,
|
|
542
235
|
autoWidth: props.autoWidthToolbar,
|
|
236
|
+
onDone: () => {
|
|
237
|
+
const { nonEmpty } = props;
|
|
238
|
+
|
|
239
|
+
log('[onDone]');
|
|
240
|
+
this.setState({ toolbarInFocus: false, focusedNode: null });
|
|
241
|
+
this.editor.blur();
|
|
242
|
+
|
|
243
|
+
if (nonEmpty && this.state.value.startText?.text?.length === 0) {
|
|
244
|
+
this.resetValue(true).then(() => {
|
|
245
|
+
this.onEditingDone();
|
|
246
|
+
});
|
|
247
|
+
} else {
|
|
248
|
+
this.onEditingDone();
|
|
249
|
+
}
|
|
250
|
+
},
|
|
543
251
|
},
|
|
544
252
|
table: {
|
|
545
253
|
onFocus: () => {
|
|
@@ -570,6 +278,7 @@ export class EditorComponent extends React.Component {
|
|
|
570
278
|
languageCharacters: props.languageCharactersProps,
|
|
571
279
|
media: {
|
|
572
280
|
focus: this.focus,
|
|
281
|
+
createChange: () => this.state.value.change(),
|
|
573
282
|
onChange: this.onChange,
|
|
574
283
|
uploadSoundSupport: props.uploadSoundSupport,
|
|
575
284
|
},
|
|
@@ -581,6 +290,9 @@ export class EditorComponent extends React.Component {
|
|
|
581
290
|
};
|
|
582
291
|
|
|
583
292
|
componentDidMount() {
|
|
293
|
+
// onRef is needed to get the ref of the component because we export it using withStyles
|
|
294
|
+
this.props.onRef(this);
|
|
295
|
+
|
|
584
296
|
window.addEventListener('resize', this.onResize);
|
|
585
297
|
|
|
586
298
|
if (this.editor && this.props.autoFocus) {
|
|
@@ -588,6 +300,8 @@ export class EditorComponent extends React.Component {
|
|
|
588
300
|
if (this.editor) {
|
|
589
301
|
const editorDOM = document.querySelector(`[data-key="${this.editor.value.document.key}"]`);
|
|
590
302
|
|
|
303
|
+
this.editor.focus();
|
|
304
|
+
|
|
591
305
|
if (editorDOM) {
|
|
592
306
|
editorDOM.focus();
|
|
593
307
|
}
|
|
@@ -597,8 +311,8 @@ export class EditorComponent extends React.Component {
|
|
|
597
311
|
}
|
|
598
312
|
|
|
599
313
|
UNSAFE_componentWillReceiveProps(nextProps) {
|
|
600
|
-
const { toolbarOpts } = this.state;
|
|
601
|
-
const newToolbarOpts = createToolbarOpts(nextProps.toolbarOpts, nextProps.error);
|
|
314
|
+
const { isHtmlMode, toolbarOpts } = this.state;
|
|
315
|
+
const newToolbarOpts = createToolbarOpts(nextProps.toolbarOpts, nextProps.error, isHtmlMode);
|
|
602
316
|
|
|
603
317
|
if (!isEqual(newToolbarOpts, toolbarOpts)) {
|
|
604
318
|
this.setState({
|
|
@@ -613,7 +327,7 @@ export class EditorComponent extends React.Component {
|
|
|
613
327
|
this.handlePlugins(nextProps);
|
|
614
328
|
}
|
|
615
329
|
|
|
616
|
-
if (!
|
|
330
|
+
if (!nextProps.value.document.equals(this.props.value.document)) {
|
|
617
331
|
this.setState({
|
|
618
332
|
focus: false,
|
|
619
333
|
value: nextProps.value,
|
|
@@ -621,9 +335,14 @@ export class EditorComponent extends React.Component {
|
|
|
621
335
|
}
|
|
622
336
|
}
|
|
623
337
|
|
|
624
|
-
componentDidUpdate() {
|
|
338
|
+
componentDidUpdate(prevProps, prevState) {
|
|
625
339
|
// The cursor is on a zero width element and when that is placed near void elements, it is not visible
|
|
626
340
|
// so we increase the width to at least 2px in order for the user to see it
|
|
341
|
+
if (this.state.isHtmlMode !== prevState.isHtmlMode || prevState.isEdited !== this.state.isEdited) {
|
|
342
|
+
this.handlePlugins(this.props);
|
|
343
|
+
this.onEditingDone();
|
|
344
|
+
}
|
|
345
|
+
|
|
627
346
|
const zeroWidthEls = document.querySelectorAll('[data-slate-zero-width="z"]');
|
|
628
347
|
|
|
629
348
|
Array.from(zeroWidthEls).forEach((el) => {
|
|
@@ -659,23 +378,15 @@ export class EditorComponent extends React.Component {
|
|
|
659
378
|
};
|
|
660
379
|
|
|
661
380
|
onMathClick = (node) => {
|
|
381
|
+
this.editor.change((c) => c.collapseToStartOf(node));
|
|
662
382
|
this.setState({ selectedNode: node });
|
|
663
383
|
};
|
|
664
384
|
|
|
665
|
-
onEditingDone = (
|
|
385
|
+
onEditingDone = () => {
|
|
666
386
|
log('[onEditingDone]');
|
|
667
|
-
|
|
387
|
+
this.setState({ stashedValue: null, focusedNode: null });
|
|
668
388
|
log('[onEditingDone] value: ', this.state.value);
|
|
669
|
-
this.props.onChange(
|
|
670
|
-
};
|
|
671
|
-
|
|
672
|
-
onDone = (editor) => {
|
|
673
|
-
const { nonEmpty } = this.props;
|
|
674
|
-
|
|
675
|
-
log('[onDone]');
|
|
676
|
-
this.setState({ toolbarInFocus: false, focusedNode: null });
|
|
677
|
-
|
|
678
|
-
this.onEditingDone(editor);
|
|
389
|
+
this.props.onChange(this.state.value, true);
|
|
679
390
|
};
|
|
680
391
|
|
|
681
392
|
/**
|
|
@@ -694,6 +405,10 @@ export class EditorComponent extends React.Component {
|
|
|
694
405
|
|
|
695
406
|
this.setState({ toolbarInFocus: false, focusedNode: null });
|
|
696
407
|
|
|
408
|
+
if (this.editor) {
|
|
409
|
+
this.editor.blur();
|
|
410
|
+
}
|
|
411
|
+
|
|
697
412
|
if (doneOn === 'blur') {
|
|
698
413
|
if (nonEmpty && this.state.value.startText?.text?.length === 0) {
|
|
699
414
|
this.resetValue(true).then(() => {
|
|
@@ -708,11 +423,10 @@ export class EditorComponent extends React.Component {
|
|
|
708
423
|
};
|
|
709
424
|
|
|
710
425
|
onBlur = (event) => {
|
|
711
|
-
return this.props.onBlur(event);
|
|
712
426
|
log('[onBlur]');
|
|
713
427
|
const target = event.relatedTarget;
|
|
714
428
|
|
|
715
|
-
const node =
|
|
429
|
+
const node = target ? findNode(target, this.state.value) : null;
|
|
716
430
|
|
|
717
431
|
log('[onBlur] node: ', node);
|
|
718
432
|
|
|
@@ -835,17 +549,23 @@ export class EditorComponent extends React.Component {
|
|
|
835
549
|
}
|
|
836
550
|
};
|
|
837
551
|
|
|
838
|
-
onChange = (
|
|
552
|
+
onChange = (change, done) => {
|
|
839
553
|
log('[onChange]');
|
|
554
|
+
|
|
555
|
+
const { value } = change;
|
|
840
556
|
const { charactersLimit } = this.props;
|
|
841
|
-
const allText = Editor.string(editor, []);
|
|
842
557
|
|
|
843
|
-
if (
|
|
558
|
+
if (value && value.document && value.document.text && value.document.text.length > charactersLimit) {
|
|
844
559
|
return;
|
|
845
560
|
}
|
|
846
561
|
|
|
847
|
-
|
|
848
|
-
|
|
562
|
+
if (!this.state.isHtmlMode) {
|
|
563
|
+
this.setState({ isEdited: false });
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
if (this.state.isHtmlMode && !isEqual(this.state.value.document.text, value.document.text)) {
|
|
567
|
+
this.setState({ isEdited: true });
|
|
568
|
+
}
|
|
849
569
|
|
|
850
570
|
this.setState({ value }, () => {
|
|
851
571
|
log('[onChange], call done()');
|
|
@@ -914,6 +634,25 @@ export class EditorComponent extends React.Component {
|
|
|
914
634
|
return undefined;
|
|
915
635
|
};
|
|
916
636
|
|
|
637
|
+
changeData = (key, data) => {
|
|
638
|
+
log('[changeData]. .. ', key, data);
|
|
639
|
+
|
|
640
|
+
/**
|
|
641
|
+
* HACK ALERT: We should be calling setState here and storing the change data:
|
|
642
|
+
*
|
|
643
|
+
* <code>this.setState({changeData: { key, data}})</code>
|
|
644
|
+
* However this is causing issues with the Mathquill instance. The 'input' event stops firing on the element and no
|
|
645
|
+
* more changes get through. The issues seem to be related to the promises in onBlur/onFocus. But removing these
|
|
646
|
+
* brings it's own problems. A major clean up is planned for this component so I've decided to temporarily settle
|
|
647
|
+
* on this hack rather than spend more time on this.
|
|
648
|
+
*/
|
|
649
|
+
|
|
650
|
+
// Uncomment this line to see the bug described above.
|
|
651
|
+
// this.setState({changeData: {key, data}})
|
|
652
|
+
|
|
653
|
+
this.__TEMPORARY_CHANGE_DATA = { key, data };
|
|
654
|
+
};
|
|
655
|
+
|
|
917
656
|
focus = (pos, node) => {
|
|
918
657
|
const position = pos || 'end';
|
|
919
658
|
|
|
@@ -1014,7 +753,6 @@ export class EditorComponent extends React.Component {
|
|
|
1014
753
|
|
|
1015
754
|
render() {
|
|
1016
755
|
const {
|
|
1017
|
-
autoFocus,
|
|
1018
756
|
disabled,
|
|
1019
757
|
spellCheck,
|
|
1020
758
|
highlightShape,
|
|
@@ -1025,7 +763,7 @@ export class EditorComponent extends React.Component {
|
|
|
1025
763
|
onKeyDown,
|
|
1026
764
|
} = this.props;
|
|
1027
765
|
|
|
1028
|
-
const { value, focusedNode, toolbarOpts } = this.state;
|
|
766
|
+
const { value, focusedNode, toolbarOpts, dialog } = this.state;
|
|
1029
767
|
|
|
1030
768
|
log('[render] value: ', value);
|
|
1031
769
|
const sizeStyle = this.buildSizeStyle();
|
|
@@ -1035,59 +773,18 @@ export class EditorComponent extends React.Component {
|
|
|
1035
773
|
[classes.toolbarOnTop]: toolbarOpts.alwaysVisible && toolbarOpts.position === 'top',
|
|
1036
774
|
},
|
|
1037
775
|
className,
|
|
1038
|
-
classes.slateEditor,
|
|
1039
776
|
);
|
|
1040
777
|
|
|
1041
778
|
return (
|
|
1042
779
|
<div ref={(ref) => (this.wrapperRef = ref)} style={{ width: sizeStyle.width }} className={names}>
|
|
1043
780
|
<SlateEditor
|
|
1044
781
|
plugins={this.plugins}
|
|
1045
|
-
|
|
782
|
+
innerRef={(r) => {
|
|
1046
783
|
if (r) {
|
|
1047
|
-
this.
|
|
784
|
+
this.slateEditor = r;
|
|
1048
785
|
}
|
|
1049
786
|
}}
|
|
1050
|
-
|
|
1051
|
-
actionsRef={this.props.onRef}
|
|
1052
|
-
onEditor={this.props.onEditor}
|
|
1053
|
-
value={value}
|
|
1054
|
-
focus={this.focus}
|
|
1055
|
-
onKeyDown={onKeyDown}
|
|
1056
|
-
onChange={this.onChange}
|
|
1057
|
-
getFocusedValue={this.getFocusedValue}
|
|
1058
|
-
onBlur={this.onBlur}
|
|
1059
|
-
onDrop={(event, editor) => this.onDropPaste(event, editor, true)}
|
|
1060
|
-
onPaste={(event, editor) => this.onDropPaste(event, editor)}
|
|
1061
|
-
onFocus={this.onFocus}
|
|
1062
|
-
onEditingDone={this.onEditingDone}
|
|
1063
|
-
onDone={this.onDone}
|
|
1064
|
-
focusedNode={focusedNode}
|
|
1065
|
-
normalize={this.normalize}
|
|
1066
|
-
readOnly={disabled}
|
|
1067
|
-
spellCheck={spellCheck}
|
|
1068
|
-
className={classNames(
|
|
1069
|
-
{
|
|
1070
|
-
[classes.noPadding]: toolbarOpts && toolbarOpts.noBorder,
|
|
1071
|
-
},
|
|
1072
|
-
classes.slateEditor,
|
|
1073
|
-
)}
|
|
1074
|
-
style={{
|
|
1075
|
-
minHeight: sizeStyle.minHeight,
|
|
1076
|
-
height: sizeStyle.height,
|
|
1077
|
-
maxHeight: sizeStyle.maxHeight,
|
|
1078
|
-
}}
|
|
1079
|
-
pluginProps={pluginProps}
|
|
1080
|
-
toolbarOpts={toolbarOpts}
|
|
1081
|
-
placeholder={placeholder}
|
|
1082
|
-
renderPlaceholder={this.renderPlaceholder}
|
|
1083
|
-
/>
|
|
1084
|
-
</div>
|
|
1085
|
-
);
|
|
1086
|
-
|
|
1087
|
-
return (
|
|
1088
|
-
<div ref={(ref) => (this.wrapperRef = ref)} style={{ width: sizeStyle.width }} className={names}>
|
|
1089
|
-
<OldSlateEditor
|
|
1090
|
-
plugins={this.plugins}
|
|
787
|
+
ref={(r) => (this.editor = r && this.props.editorRef(r))}
|
|
1091
788
|
toolbarRef={(r) => {
|
|
1092
789
|
if (r) {
|
|
1093
790
|
this.toolbarRef = r;
|
|
@@ -1123,6 +820,14 @@ export class EditorComponent extends React.Component {
|
|
|
1123
820
|
toolbarOpts={toolbarOpts}
|
|
1124
821
|
placeholder={placeholder}
|
|
1125
822
|
renderPlaceholder={this.renderPlaceholder}
|
|
823
|
+
onDataChange={this.changeData}
|
|
824
|
+
/>
|
|
825
|
+
<AlertDialog
|
|
826
|
+
open={dialog.open}
|
|
827
|
+
title={dialog.title}
|
|
828
|
+
text={dialog.text}
|
|
829
|
+
onClose={dialog.onClose}
|
|
830
|
+
onConfirm={dialog.onConfirm}
|
|
1126
831
|
/>
|
|
1127
832
|
</div>
|
|
1128
833
|
);
|
|
@@ -1167,4 +872,4 @@ const styles = {
|
|
|
1167
872
|
},
|
|
1168
873
|
};
|
|
1169
874
|
|
|
1170
|
-
export default withStyles(styles)(
|
|
875
|
+
export default withStyles(styles)(Editor);
|