@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.
- 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 +140 -450
- package/src/index.jsx +96 -62
- package/src/plugins/characters/index.jsx +18 -14
- package/src/plugins/html/icons/index.jsx +19 -0
- package/src/plugins/html/index.jsx +68 -0
- package/src/plugins/image/component.jsx +41 -67
- package/src/plugins/image/index.jsx +43 -108
- package/src/plugins/image/insert-image-handler.js +27 -62
- package/src/plugins/index.jsx +39 -21
- package/src/plugins/list/index.jsx +91 -66
- package/src/plugins/math/index.jsx +71 -84
- package/src/plugins/media/index.jsx +118 -147
- 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 +7 -10
- package/src/plugins/respArea/explicit-constructed-response/index.jsx +2 -3
- package/src/plugins/respArea/index.jsx +90 -138
- package/src/plugins/respArea/inline-dropdown/index.jsx +2 -3
- package/src/plugins/respArea/utils.jsx +28 -23
- package/src/plugins/table/index.jsx +216 -340
- package/src/plugins/table/table-toolbar.jsx +5 -9
- package/src/plugins/toolbar/default-toolbar.jsx +31 -51
- package/src/plugins/toolbar/editor-and-toolbar.jsx +114 -121
- package/src/plugins/toolbar/toolbar.jsx +224 -258
- package/src/plugins/utils.js +2 -19
- 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,380 +1,22 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
Editor as OldSlateEditor,
|
|
4
|
-
findNode,
|
|
5
|
-
getEventRange,
|
|
6
|
-
getEventTransfer,
|
|
7
|
-
} from 'slate-react';
|
|
8
|
-
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';
|
|
9
3
|
|
|
10
4
|
import isEqual from 'lodash/isEqual';
|
|
11
|
-
import * as serialization from './
|
|
5
|
+
import * as serialization from './serialization';
|
|
12
6
|
import PropTypes from 'prop-types';
|
|
7
|
+
import React from 'react';
|
|
13
8
|
import { Value, Block, Inline } from 'slate';
|
|
14
|
-
import { ALL_PLUGINS, DEFAULT_PLUGINS
|
|
9
|
+
import { buildPlugins, ALL_PLUGINS, DEFAULT_PLUGINS } from './plugins';
|
|
15
10
|
import debug from 'debug';
|
|
16
11
|
import { withStyles } from '@material-ui/core/styles';
|
|
17
12
|
import classNames from 'classnames';
|
|
18
13
|
import { color } from '@pie-lib/render-ui';
|
|
19
14
|
import Plain from 'slate-plain-serializer';
|
|
15
|
+
import { AlertDialog } from '@pie-lib/config-ui';
|
|
20
16
|
|
|
21
|
-
import { getBase64
|
|
17
|
+
import { getBase64 } from './serialization';
|
|
22
18
|
import InsertImageHandler from './plugins/image/insert-image-handler';
|
|
23
19
|
|
|
24
|
-
import isHotkey from 'is-hotkey';
|
|
25
|
-
import { ReactEditor, useSlateStatic, Editable, useFocused, useSlate, Slate } from 'slate-react';
|
|
26
|
-
import { Node as SlateNode, Path, Editor, Transforms, createEditor, Element as SlateElement } from 'slate';
|
|
27
|
-
|
|
28
|
-
import { Button, Icon } from './components';
|
|
29
|
-
import EditorAndToolbar from './plugins/toolbar/editor-and-toolbar';
|
|
30
|
-
|
|
31
|
-
window.Path = Path;
|
|
32
|
-
window.SlateNode = SlateNode;
|
|
33
|
-
window.ReactEditor = ReactEditor;
|
|
34
|
-
window.Editor = Editor;
|
|
35
|
-
|
|
36
|
-
const HOTKEYS = {
|
|
37
|
-
'mod+b': 'bold',
|
|
38
|
-
'mod+i': 'italic',
|
|
39
|
-
'mod+u': 'underline',
|
|
40
|
-
'mod+`': 'code'
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
const LIST_TYPES = ['numbered-list', 'bulleted-list'];
|
|
44
|
-
const TEXT_ALIGN_TYPES = ['left', 'center', 'right', 'justify'];
|
|
45
|
-
|
|
46
|
-
const initialValue = [
|
|
47
|
-
{
|
|
48
|
-
type: 'paragraph',
|
|
49
|
-
children: [
|
|
50
|
-
{
|
|
51
|
-
type: 'math',
|
|
52
|
-
data: {
|
|
53
|
-
latex: '\\frac{1}{2}',
|
|
54
|
-
wrapper: 'round_brackets'
|
|
55
|
-
},
|
|
56
|
-
children: [
|
|
57
|
-
{
|
|
58
|
-
text: '\\(\\frac{1}{2}\\)'
|
|
59
|
-
}
|
|
60
|
-
]
|
|
61
|
-
}
|
|
62
|
-
]
|
|
63
|
-
}
|
|
64
|
-
];
|
|
65
|
-
|
|
66
|
-
const SlateEditor = editorProps => {
|
|
67
|
-
const mounted = useRef(false);
|
|
68
|
-
const { autoFocus, value, plugins, actionsRef, onEditingDone } = editorProps;
|
|
69
|
-
const renderElement = useCallback(props => <Element {...props} plugins={plugins} />, []);
|
|
70
|
-
const renderLeaf = useCallback(props => <Leaf {...props} />, []);
|
|
71
|
-
const editor = useMemo(() => withPlugins(createEditor(), plugins), []);
|
|
72
|
-
const [isFocused, setIsFocused] = useState(false);
|
|
73
|
-
const editorRef = useRef(null);
|
|
74
|
-
|
|
75
|
-
useEffect(() => {
|
|
76
|
-
mounted.current = true;
|
|
77
|
-
|
|
78
|
-
return () => {
|
|
79
|
-
mounted.current = false;
|
|
80
|
-
};
|
|
81
|
-
}, []);
|
|
82
|
-
|
|
83
|
-
useEffect(() => {
|
|
84
|
-
if (editorProps.onEditor) {
|
|
85
|
-
editorProps.onEditor(editor);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (autoFocus) {
|
|
89
|
-
Transforms.select(editor, [0, 0]);
|
|
90
|
-
ReactEditor.focus(editor);
|
|
91
|
-
|
|
92
|
-
if (mounted.current) {
|
|
93
|
-
setIsFocused(true);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}, [editor]);
|
|
97
|
-
|
|
98
|
-
const slateValue = useMemo(() => {
|
|
99
|
-
// Slate throws an error if the value on the initial render is invalid
|
|
100
|
-
// so we directly set the value on the editor in order
|
|
101
|
-
// to be able to trigger normalization on the initial value before rendering
|
|
102
|
-
editor.children = value;
|
|
103
|
-
editor.marks = {};
|
|
104
|
-
Editor.normalize(editor, { force: true });
|
|
105
|
-
// We return the normalized internal value so that the rendering can take over from here
|
|
106
|
-
return editor.children;
|
|
107
|
-
}, [editor, value]);
|
|
108
|
-
|
|
109
|
-
window.editor = editor;
|
|
110
|
-
|
|
111
|
-
const onKeyDown = event => {
|
|
112
|
-
if (event.key === 'Enter' && event.shiftKey === true) {
|
|
113
|
-
editor.insertText('\n');
|
|
114
|
-
event.preventDefault();
|
|
115
|
-
event.stopPropagation();
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
for (const hotkey in HOTKEYS) {
|
|
119
|
-
if (isHotkey(hotkey, event)) {
|
|
120
|
-
event.preventDefault();
|
|
121
|
-
const mark = HOTKEYS[hotkey];
|
|
122
|
-
toggleMark(editor, mark);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
const onFocus = () => setIsFocused(true);
|
|
127
|
-
const onBlur = e => {
|
|
128
|
-
setTimeout(() => {
|
|
129
|
-
if (!editorRef.current || !editorRef.current.contains(document.activeElement)) {
|
|
130
|
-
if (editorProps.onBlur) {
|
|
131
|
-
editorProps.onBlur(e);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
if (mounted.current) {
|
|
135
|
-
setIsFocused(false);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}, 50);
|
|
139
|
-
};
|
|
140
|
-
const actions = {
|
|
141
|
-
focus: (position, node) => {
|
|
142
|
-
const [, textPath] = node
|
|
143
|
-
? Editor.leaf(editor, ReactEditor.findPath(editor, node), { edge: 'end' })
|
|
144
|
-
: Editor.leaf(editor, [0], { edge: 'end' });
|
|
145
|
-
|
|
146
|
-
Transforms.select(editor, textPath);
|
|
147
|
-
ReactEditor.focus(editor);
|
|
148
|
-
},
|
|
149
|
-
finishEditing: () => {
|
|
150
|
-
// if (!mounted.current) {
|
|
151
|
-
// return;
|
|
152
|
-
// }
|
|
153
|
-
|
|
154
|
-
if (typeof onEditingDone === 'function') {
|
|
155
|
-
onEditingDone(editor);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
if (actionsRef) {
|
|
161
|
-
actionsRef(actions);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
return (
|
|
165
|
-
<Slate editor={editor} initialValue={slateValue}>
|
|
166
|
-
<RootRef rootRef={editorRef}>
|
|
167
|
-
<EditorAndToolbar
|
|
168
|
-
{...editorProps}
|
|
169
|
-
editor={editor}
|
|
170
|
-
isFocused={isFocused}
|
|
171
|
-
onDone={() => {
|
|
172
|
-
setIsFocused(false);
|
|
173
|
-
document.activeElement.blur();
|
|
174
|
-
editorProps.onDone(editor);
|
|
175
|
-
}}
|
|
176
|
-
>
|
|
177
|
-
<Editable
|
|
178
|
-
renderElement={renderElement}
|
|
179
|
-
renderLeaf={renderLeaf}
|
|
180
|
-
placeholder="Enter some rich text…"
|
|
181
|
-
spellCheck
|
|
182
|
-
onKeyDown={onKeyDown}
|
|
183
|
-
onFocus={onFocus}
|
|
184
|
-
onBlur={onBlur}
|
|
185
|
-
/>
|
|
186
|
-
</EditorAndToolbar>
|
|
187
|
-
</RootRef>
|
|
188
|
-
</Slate>
|
|
189
|
-
);
|
|
190
|
-
};
|
|
191
|
-
|
|
192
|
-
const toggleBlock = (editor, format) => {
|
|
193
|
-
const isActive = isBlockActive(
|
|
194
|
-
editor,
|
|
195
|
-
format,
|
|
196
|
-
TEXT_ALIGN_TYPES.includes(format) ? 'align' : 'type'
|
|
197
|
-
);
|
|
198
|
-
const isList = LIST_TYPES.includes(format);
|
|
199
|
-
|
|
200
|
-
Transforms.unwrapNodes(editor, {
|
|
201
|
-
match: n =>
|
|
202
|
-
!Editor.isEditor(n) &&
|
|
203
|
-
SlateElement.isElement(n) &&
|
|
204
|
-
LIST_TYPES.includes(n.type) &&
|
|
205
|
-
!TEXT_ALIGN_TYPES.includes(format),
|
|
206
|
-
split: true
|
|
207
|
-
});
|
|
208
|
-
let newProperties;
|
|
209
|
-
if (TEXT_ALIGN_TYPES.includes(format)) {
|
|
210
|
-
newProperties = {
|
|
211
|
-
align: isActive ? undefined : format
|
|
212
|
-
};
|
|
213
|
-
} else {
|
|
214
|
-
newProperties = {
|
|
215
|
-
type: isActive ? 'paragraph' : isList ? 'list_item' : format
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
Transforms.setNodes(editor, newProperties);
|
|
219
|
-
|
|
220
|
-
if (!isActive && isList) {
|
|
221
|
-
const block = { type: format, children: [] };
|
|
222
|
-
Transforms.wrapNodes(editor, block);
|
|
223
|
-
}
|
|
224
|
-
};
|
|
225
|
-
|
|
226
|
-
const toggleMark = (editor, format) => {
|
|
227
|
-
const isActive = isMarkActive(editor, format);
|
|
228
|
-
|
|
229
|
-
if (isActive) {
|
|
230
|
-
Editor.removeMark(editor, format);
|
|
231
|
-
} else {
|
|
232
|
-
Editor.addMark(editor, format, true);
|
|
233
|
-
}
|
|
234
|
-
};
|
|
235
|
-
|
|
236
|
-
const isBlockActive = (editor, format, blockType = 'type') => {
|
|
237
|
-
const { selection } = editor;
|
|
238
|
-
if (!selection) return false;
|
|
239
|
-
|
|
240
|
-
const [match] = Array.from(
|
|
241
|
-
Editor.nodes(editor, {
|
|
242
|
-
at: Editor.unhangRange(editor, selection),
|
|
243
|
-
match: n => !Editor.isEditor(n) && SlateElement.isElement(n) && n[blockType] === format
|
|
244
|
-
})
|
|
245
|
-
);
|
|
246
|
-
|
|
247
|
-
return !!match;
|
|
248
|
-
};
|
|
249
|
-
|
|
250
|
-
const isMarkActive = (editor, format) => {
|
|
251
|
-
const marks = Editor.marks(editor);
|
|
252
|
-
return marks ? marks[format] === true : false;
|
|
253
|
-
};
|
|
254
|
-
|
|
255
|
-
const Element = props => {
|
|
256
|
-
const editor = useSlateStatic();
|
|
257
|
-
const focused = useFocused();
|
|
258
|
-
const { attributes, children, element, plugins } = props;
|
|
259
|
-
const style = { textAlign: element.align };
|
|
260
|
-
|
|
261
|
-
const nodeProps = { ...attributes, ...props, node: { ...element }, children };
|
|
262
|
-
const pluginToRender = plugins.find(
|
|
263
|
-
plugin => typeof plugin.supports === 'function' && plugin.supports(element)
|
|
264
|
-
);
|
|
265
|
-
|
|
266
|
-
if (pluginToRender) {
|
|
267
|
-
return pluginToRender.renderNode({ ...nodeProps, editor, focused });
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
switch (element.type) {
|
|
271
|
-
case 'block-quote':
|
|
272
|
-
return (
|
|
273
|
-
<blockquote style={style} {...attributes}>
|
|
274
|
-
{children}
|
|
275
|
-
</blockquote>
|
|
276
|
-
);
|
|
277
|
-
case 'bulleted-list':
|
|
278
|
-
return (
|
|
279
|
-
<ul style={style} {...attributes}>
|
|
280
|
-
{children}
|
|
281
|
-
</ul>
|
|
282
|
-
);
|
|
283
|
-
case 'heading-one':
|
|
284
|
-
return (
|
|
285
|
-
<h1 style={style} {...attributes}>
|
|
286
|
-
{children}
|
|
287
|
-
</h1>
|
|
288
|
-
);
|
|
289
|
-
case 'heading-two':
|
|
290
|
-
return (
|
|
291
|
-
<h2 style={style} {...attributes}>
|
|
292
|
-
{children}
|
|
293
|
-
</h2>
|
|
294
|
-
);
|
|
295
|
-
case 'list-item':
|
|
296
|
-
return (
|
|
297
|
-
<li style={style} {...attributes}>
|
|
298
|
-
{children}
|
|
299
|
-
</li>
|
|
300
|
-
);
|
|
301
|
-
case 'numbered-list':
|
|
302
|
-
return (
|
|
303
|
-
<ol style={style} {...attributes}>
|
|
304
|
-
{children}
|
|
305
|
-
</ol>
|
|
306
|
-
);
|
|
307
|
-
default:
|
|
308
|
-
return (
|
|
309
|
-
<div
|
|
310
|
-
style={{
|
|
311
|
-
...style,
|
|
312
|
-
margin: 0
|
|
313
|
-
}}
|
|
314
|
-
{...attributes}
|
|
315
|
-
>
|
|
316
|
-
{children}
|
|
317
|
-
</div>
|
|
318
|
-
);
|
|
319
|
-
}
|
|
320
|
-
};
|
|
321
|
-
|
|
322
|
-
const Leaf = ({ attributes, children, leaf }) => {
|
|
323
|
-
if (leaf.bold) {
|
|
324
|
-
children = <strong>{children}</strong>;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
if (leaf.code) {
|
|
328
|
-
children = <code>{children}</code>;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
if (leaf.italic) {
|
|
332
|
-
children = <em>{children}</em>;
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
if (leaf.underline) {
|
|
336
|
-
children = <u>{children}</u>;
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
if (leaf.strikethrough) {
|
|
340
|
-
children = <del>{children}</del>;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
return <span {...attributes}>{children}</span>;
|
|
344
|
-
};
|
|
345
|
-
|
|
346
|
-
const BlockButton = ({ format, icon }) => {
|
|
347
|
-
const editor = useSlate();
|
|
348
|
-
return (
|
|
349
|
-
<Button
|
|
350
|
-
active={isBlockActive(editor, format, TEXT_ALIGN_TYPES.includes(format) ? 'align' : 'type')}
|
|
351
|
-
onMouseDown={event => {
|
|
352
|
-
event.preventDefault();
|
|
353
|
-
toggleBlock(editor, format);
|
|
354
|
-
}}
|
|
355
|
-
>
|
|
356
|
-
<Icon>{icon}</Icon>
|
|
357
|
-
</Button>
|
|
358
|
-
);
|
|
359
|
-
};
|
|
360
|
-
|
|
361
|
-
const MarkButton = ({ format, icon }) => {
|
|
362
|
-
const editor = useSlate();
|
|
363
|
-
return (
|
|
364
|
-
<Button
|
|
365
|
-
active={isMarkActive(editor, format)}
|
|
366
|
-
onMouseDown={event => {
|
|
367
|
-
event.preventDefault();
|
|
368
|
-
toggleMark(editor, format);
|
|
369
|
-
}}
|
|
370
|
-
>
|
|
371
|
-
<Icon>{icon}</Icon>
|
|
372
|
-
</Button>
|
|
373
|
-
);
|
|
374
|
-
};
|
|
375
|
-
|
|
376
|
-
// old-editable
|
|
377
|
-
|
|
378
20
|
export { ALL_PLUGINS, DEFAULT_PLUGINS, serialization };
|
|
379
21
|
|
|
380
22
|
const log = debug('editable-html:editor');
|
|
@@ -395,31 +37,27 @@ const defaultResponseAreaProps = {
|
|
|
395
37
|
|
|
396
38
|
const defaultLanguageCharactersProps = [];
|
|
397
39
|
|
|
398
|
-
const createToolbarOpts = (toolbarOpts, error) => {
|
|
40
|
+
const createToolbarOpts = (toolbarOpts, error, isHtmlMode) => {
|
|
399
41
|
return {
|
|
400
42
|
...defaultToolbarOpts,
|
|
401
43
|
...toolbarOpts,
|
|
402
44
|
error,
|
|
45
|
+
isHtmlMode,
|
|
403
46
|
};
|
|
404
47
|
};
|
|
405
48
|
|
|
406
|
-
export class
|
|
49
|
+
export class Editor extends React.Component {
|
|
407
50
|
static propTypes = {
|
|
408
51
|
autoFocus: PropTypes.bool,
|
|
52
|
+
editorRef: PropTypes.func.isRequired,
|
|
409
53
|
error: PropTypes.any,
|
|
410
54
|
onRef: PropTypes.func.isRequired,
|
|
411
55
|
onChange: PropTypes.func.isRequired,
|
|
412
|
-
onEditor: PropTypes.func,
|
|
413
56
|
onFocus: PropTypes.func,
|
|
414
57
|
onBlur: PropTypes.func,
|
|
415
58
|
onKeyDown: PropTypes.func,
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
type: PropTypes.string,
|
|
419
|
-
children: PropTypes.array,
|
|
420
|
-
data: PropTypes.object
|
|
421
|
-
})
|
|
422
|
-
),
|
|
59
|
+
focus: PropTypes.func.isRequired,
|
|
60
|
+
value: SlateTypes.value.isRequired,
|
|
423
61
|
imageSupport: PropTypes.object,
|
|
424
62
|
mathMlOptions: PropTypes.shape({
|
|
425
63
|
mmlOutput: PropTypes.bool,
|
|
@@ -496,8 +134,15 @@ export class EditorComponent extends React.Component {
|
|
|
496
134
|
this.state = {
|
|
497
135
|
value: props.value,
|
|
498
136
|
toolbarOpts: createToolbarOpts(props.toolbarOpts, props.error),
|
|
137
|
+
isHtmlMode: false,
|
|
138
|
+
isEdited: false,
|
|
139
|
+
dialog: {
|
|
140
|
+
open: false,
|
|
141
|
+
},
|
|
499
142
|
};
|
|
500
143
|
|
|
144
|
+
this.toggleHtmlMode = this.toggleHtmlMode.bind(this);
|
|
145
|
+
|
|
501
146
|
this.onResize = () => {
|
|
502
147
|
props.onChange(this.state.value, true);
|
|
503
148
|
};
|
|
@@ -505,12 +150,48 @@ export class EditorComponent extends React.Component {
|
|
|
505
150
|
this.handlePlugins(this.props);
|
|
506
151
|
}
|
|
507
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
|
+
|
|
508
182
|
handlePlugins = (props) => {
|
|
509
183
|
const normalizedResponseAreaProps = {
|
|
510
184
|
...defaultResponseAreaProps,
|
|
511
185
|
...props.responseAreaProps,
|
|
512
186
|
};
|
|
513
187
|
|
|
188
|
+
const htmlPluginOpts = {
|
|
189
|
+
isHtmlMode: this.state.isHtmlMode,
|
|
190
|
+
isEdited: this.state.isEdited,
|
|
191
|
+
toggleHtmlMode: this.toggleHtmlMode,
|
|
192
|
+
handleAlertDialog: this.handleAlertDialog,
|
|
193
|
+
};
|
|
194
|
+
|
|
514
195
|
this.plugins = buildPlugins(props.activePlugins, {
|
|
515
196
|
math: {
|
|
516
197
|
onClick: this.onMathClick,
|
|
@@ -518,6 +199,7 @@ export class EditorComponent extends React.Component {
|
|
|
518
199
|
onBlur: this.onPluginBlur,
|
|
519
200
|
...props.mathMlOptions,
|
|
520
201
|
},
|
|
202
|
+
html: htmlPluginOpts,
|
|
521
203
|
image: {
|
|
522
204
|
disableImageAlignmentButtons: props.disableImageAlignmentButtons,
|
|
523
205
|
onDelete:
|
|
@@ -551,6 +233,21 @@ export class EditorComponent extends React.Component {
|
|
|
551
233
|
disableScrollbar: !!props.disableScrollbar,
|
|
552
234
|
disableUnderline: props.disableUnderline,
|
|
553
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
|
+
},
|
|
554
251
|
},
|
|
555
252
|
table: {
|
|
556
253
|
onFocus: () => {
|
|
@@ -581,6 +278,7 @@ export class EditorComponent extends React.Component {
|
|
|
581
278
|
languageCharacters: props.languageCharactersProps,
|
|
582
279
|
media: {
|
|
583
280
|
focus: this.focus,
|
|
281
|
+
createChange: () => this.state.value.change(),
|
|
584
282
|
onChange: this.onChange,
|
|
585
283
|
uploadSoundSupport: props.uploadSoundSupport,
|
|
586
284
|
},
|
|
@@ -592,6 +290,9 @@ export class EditorComponent extends React.Component {
|
|
|
592
290
|
};
|
|
593
291
|
|
|
594
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
|
+
|
|
595
296
|
window.addEventListener('resize', this.onResize);
|
|
596
297
|
|
|
597
298
|
if (this.editor && this.props.autoFocus) {
|
|
@@ -599,6 +300,8 @@ export class EditorComponent extends React.Component {
|
|
|
599
300
|
if (this.editor) {
|
|
600
301
|
const editorDOM = document.querySelector(`[data-key="${this.editor.value.document.key}"]`);
|
|
601
302
|
|
|
303
|
+
this.editor.focus();
|
|
304
|
+
|
|
602
305
|
if (editorDOM) {
|
|
603
306
|
editorDOM.focus();
|
|
604
307
|
}
|
|
@@ -608,8 +311,8 @@ export class EditorComponent extends React.Component {
|
|
|
608
311
|
}
|
|
609
312
|
|
|
610
313
|
UNSAFE_componentWillReceiveProps(nextProps) {
|
|
611
|
-
const { toolbarOpts } = this.state;
|
|
612
|
-
const newToolbarOpts = createToolbarOpts(nextProps.toolbarOpts, nextProps.error);
|
|
314
|
+
const { isHtmlMode, toolbarOpts } = this.state;
|
|
315
|
+
const newToolbarOpts = createToolbarOpts(nextProps.toolbarOpts, nextProps.error, isHtmlMode);
|
|
613
316
|
|
|
614
317
|
if (!isEqual(newToolbarOpts, toolbarOpts)) {
|
|
615
318
|
this.setState({
|
|
@@ -624,7 +327,7 @@ export class EditorComponent extends React.Component {
|
|
|
624
327
|
this.handlePlugins(nextProps);
|
|
625
328
|
}
|
|
626
329
|
|
|
627
|
-
if (!
|
|
330
|
+
if (!nextProps.value.document.equals(this.props.value.document)) {
|
|
628
331
|
this.setState({
|
|
629
332
|
focus: false,
|
|
630
333
|
value: nextProps.value,
|
|
@@ -632,9 +335,14 @@ export class EditorComponent extends React.Component {
|
|
|
632
335
|
}
|
|
633
336
|
}
|
|
634
337
|
|
|
635
|
-
componentDidUpdate() {
|
|
338
|
+
componentDidUpdate(prevProps, prevState) {
|
|
636
339
|
// The cursor is on a zero width element and when that is placed near void elements, it is not visible
|
|
637
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
|
+
|
|
638
346
|
const zeroWidthEls = document.querySelectorAll('[data-slate-zero-width="z"]');
|
|
639
347
|
|
|
640
348
|
Array.from(zeroWidthEls).forEach((el) => {
|
|
@@ -670,23 +378,15 @@ export class EditorComponent extends React.Component {
|
|
|
670
378
|
};
|
|
671
379
|
|
|
672
380
|
onMathClick = (node) => {
|
|
381
|
+
this.editor.change((c) => c.collapseToStartOf(node));
|
|
673
382
|
this.setState({ selectedNode: node });
|
|
674
383
|
};
|
|
675
384
|
|
|
676
|
-
onEditingDone =
|
|
385
|
+
onEditingDone = () => {
|
|
677
386
|
log('[onEditingDone]');
|
|
678
|
-
|
|
387
|
+
this.setState({ stashedValue: null, focusedNode: null });
|
|
679
388
|
log('[onEditingDone] value: ', this.state.value);
|
|
680
|
-
this.props.onChange(
|
|
681
|
-
};
|
|
682
|
-
|
|
683
|
-
onDone = editor => {
|
|
684
|
-
const { nonEmpty } = this.props;
|
|
685
|
-
|
|
686
|
-
log('[onDone]');
|
|
687
|
-
this.setState({ toolbarInFocus: false, focusedNode: null });
|
|
688
|
-
|
|
689
|
-
this.onEditingDone(editor);
|
|
389
|
+
this.props.onChange(this.state.value, true);
|
|
690
390
|
};
|
|
691
391
|
|
|
692
392
|
/**
|
|
@@ -705,6 +405,10 @@ export class EditorComponent extends React.Component {
|
|
|
705
405
|
|
|
706
406
|
this.setState({ toolbarInFocus: false, focusedNode: null });
|
|
707
407
|
|
|
408
|
+
if (this.editor) {
|
|
409
|
+
this.editor.blur();
|
|
410
|
+
}
|
|
411
|
+
|
|
708
412
|
if (doneOn === 'blur') {
|
|
709
413
|
if (nonEmpty && this.state.value.startText?.text?.length === 0) {
|
|
710
414
|
this.resetValue(true).then(() => {
|
|
@@ -719,11 +423,10 @@ export class EditorComponent extends React.Component {
|
|
|
719
423
|
};
|
|
720
424
|
|
|
721
425
|
onBlur = (event) => {
|
|
722
|
-
return this.props.onBlur(event);
|
|
723
426
|
log('[onBlur]');
|
|
724
427
|
const target = event.relatedTarget;
|
|
725
428
|
|
|
726
|
-
const node =
|
|
429
|
+
const node = target ? findNode(target, this.state.value) : null;
|
|
727
430
|
|
|
728
431
|
log('[onBlur] node: ', node);
|
|
729
432
|
|
|
@@ -846,17 +549,23 @@ export class EditorComponent extends React.Component {
|
|
|
846
549
|
}
|
|
847
550
|
};
|
|
848
551
|
|
|
849
|
-
onChange = (
|
|
552
|
+
onChange = (change, done) => {
|
|
850
553
|
log('[onChange]');
|
|
554
|
+
|
|
555
|
+
const { value } = change;
|
|
851
556
|
const { charactersLimit } = this.props;
|
|
852
|
-
const allText = Editor.string(editor, []);
|
|
853
557
|
|
|
854
|
-
if (
|
|
558
|
+
if (value && value.document && value.document.text && value.document.text.length > charactersLimit) {
|
|
855
559
|
return;
|
|
856
560
|
}
|
|
857
561
|
|
|
858
|
-
|
|
859
|
-
|
|
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
|
+
}
|
|
860
569
|
|
|
861
570
|
this.setState({ value }, () => {
|
|
862
571
|
log('[onChange], call done()');
|
|
@@ -925,6 +634,25 @@ export class EditorComponent extends React.Component {
|
|
|
925
634
|
return undefined;
|
|
926
635
|
};
|
|
927
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
|
+
|
|
928
656
|
focus = (pos, node) => {
|
|
929
657
|
const position = pos || 'end';
|
|
930
658
|
|
|
@@ -1025,7 +753,6 @@ export class EditorComponent extends React.Component {
|
|
|
1025
753
|
|
|
1026
754
|
render() {
|
|
1027
755
|
const {
|
|
1028
|
-
autoFocus,
|
|
1029
756
|
disabled,
|
|
1030
757
|
spellCheck,
|
|
1031
758
|
highlightShape,
|
|
@@ -1036,7 +763,7 @@ export class EditorComponent extends React.Component {
|
|
|
1036
763
|
onKeyDown,
|
|
1037
764
|
} = this.props;
|
|
1038
765
|
|
|
1039
|
-
const { value, focusedNode, toolbarOpts } = this.state;
|
|
766
|
+
const { value, focusedNode, toolbarOpts, dialog } = this.state;
|
|
1040
767
|
|
|
1041
768
|
log('[render] value: ', value);
|
|
1042
769
|
const sizeStyle = this.buildSizeStyle();
|
|
@@ -1046,64 +773,19 @@ export class EditorComponent extends React.Component {
|
|
|
1046
773
|
[classes.toolbarOnTop]: toolbarOpts.alwaysVisible && toolbarOpts.position === 'top',
|
|
1047
774
|
},
|
|
1048
775
|
className,
|
|
1049
|
-
classes.slateEditor,
|
|
1050
776
|
);
|
|
1051
777
|
|
|
1052
778
|
return (
|
|
1053
779
|
<div ref={(ref) => (this.wrapperRef = ref)} style={{ width: sizeStyle.width }} className={names}>
|
|
1054
780
|
<SlateEditor
|
|
1055
781
|
plugins={this.plugins}
|
|
1056
|
-
|
|
782
|
+
innerRef={(r) => {
|
|
1057
783
|
if (r) {
|
|
1058
|
-
this.
|
|
784
|
+
this.slateEditor = r;
|
|
1059
785
|
}
|
|
1060
786
|
}}
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
onEditor={this.props.onEditor}
|
|
1064
|
-
value={value}
|
|
1065
|
-
focus={this.focus}
|
|
1066
|
-
onKeyDown={onKeyDown}
|
|
1067
|
-
onChange={this.onChange}
|
|
1068
|
-
getFocusedValue={this.getFocusedValue}
|
|
1069
|
-
onBlur={this.onBlur}
|
|
1070
|
-
onDrop={(event, editor) => this.onDropPaste(event, editor, true)}
|
|
1071
|
-
onPaste={(event, editor) => this.onDropPaste(event, editor)}
|
|
1072
|
-
onFocus={this.onFocus}
|
|
1073
|
-
onEditingDone={this.onEditingDone}
|
|
1074
|
-
onDone={this.onDone}
|
|
1075
|
-
focusedNode={focusedNode}
|
|
1076
|
-
normalize={this.normalize}
|
|
1077
|
-
readOnly={disabled}
|
|
1078
|
-
spellCheck={spellCheck}
|
|
1079
|
-
className={classNames(
|
|
1080
|
-
{
|
|
1081
|
-
[classes.noPadding]: toolbarOpts && toolbarOpts.noBorder
|
|
1082
|
-
},
|
|
1083
|
-
classes.slateEditor
|
|
1084
|
-
)}
|
|
1085
|
-
style={{
|
|
1086
|
-
minHeight: sizeStyle.minHeight,
|
|
1087
|
-
height: sizeStyle.height,
|
|
1088
|
-
maxHeight: sizeStyle.maxHeight
|
|
1089
|
-
}}
|
|
1090
|
-
pluginProps={pluginProps}
|
|
1091
|
-
toolbarOpts={toolbarOpts}
|
|
1092
|
-
placeholder={placeholder}
|
|
1093
|
-
renderPlaceholder={this.renderPlaceholder}
|
|
1094
|
-
/>
|
|
1095
|
-
</div>
|
|
1096
|
-
);
|
|
1097
|
-
|
|
1098
|
-
return (
|
|
1099
|
-
<div
|
|
1100
|
-
ref={ref => (this.wrapperRef = ref)}
|
|
1101
|
-
style={{ width: sizeStyle.width }}
|
|
1102
|
-
className={names}
|
|
1103
|
-
>
|
|
1104
|
-
<OldSlateEditor
|
|
1105
|
-
plugins={this.plugins}
|
|
1106
|
-
toolbarRef={r => {
|
|
787
|
+
ref={(r) => (this.editor = r && this.props.editorRef(r))}
|
|
788
|
+
toolbarRef={(r) => {
|
|
1107
789
|
if (r) {
|
|
1108
790
|
this.toolbarRef = r;
|
|
1109
791
|
}
|
|
@@ -1138,6 +820,14 @@ export class EditorComponent extends React.Component {
|
|
|
1138
820
|
toolbarOpts={toolbarOpts}
|
|
1139
821
|
placeholder={placeholder}
|
|
1140
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}
|
|
1141
831
|
/>
|
|
1142
832
|
</div>
|
|
1143
833
|
);
|
|
@@ -1182,4 +872,4 @@ const styles = {
|
|
|
1182
872
|
},
|
|
1183
873
|
};
|
|
1184
874
|
|
|
1185
|
-
export default withStyles(styles)(
|
|
875
|
+
export default withStyles(styles)(Editor);
|