@pie-lib/editable-html 7.17.4-next.42 → 7.17.4-next.423
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 +165 -0
- package/CHANGELOG.md +286 -0
- package/lib/editor.js +348 -179
- package/lib/editor.js.map +1 -1
- package/lib/index.js +63 -52
- package/lib/index.js.map +1 -1
- package/lib/parse-html.js.map +1 -1
- package/lib/plugins/characters/custom-popover.js +70 -0
- package/lib/plugins/characters/custom-popover.js.map +1 -0
- package/lib/plugins/characters/index.js +266 -0
- package/lib/plugins/characters/index.js.map +1 -0
- package/lib/plugins/characters/utils.js +382 -0
- package/lib/plugins/characters/utils.js.map +1 -0
- package/lib/plugins/image/component.js +251 -77
- package/lib/plugins/image/component.js.map +1 -1
- package/lib/plugins/image/image-toolbar.js +49 -63
- package/lib/plugins/image/image-toolbar.js.map +1 -1
- package/lib/plugins/image/index.js +56 -19
- package/lib/plugins/image/index.js.map +1 -1
- package/lib/plugins/image/insert-image-handler.js +9 -15
- package/lib/plugins/image/insert-image-handler.js.map +1 -1
- package/lib/plugins/index.js +20 -12
- package/lib/plugins/index.js.map +1 -1
- package/lib/plugins/list/index.js +82 -14
- package/lib/plugins/list/index.js.map +1 -1
- package/lib/plugins/math/index.js +50 -55
- package/lib/plugins/math/index.js.map +1 -1
- package/lib/plugins/media/index.js +26 -25
- package/lib/plugins/media/index.js.map +1 -1
- package/lib/plugins/media/media-dialog.js +45 -56
- package/lib/plugins/media/media-dialog.js.map +1 -1
- package/lib/plugins/media/media-toolbar.js +24 -30
- package/lib/plugins/media/media-toolbar.js.map +1 -1
- package/lib/plugins/media/media-wrapper.js +28 -35
- package/lib/plugins/media/media-wrapper.js.map +1 -1
- package/lib/plugins/respArea/drag-in-the-blank/choice.js +68 -46
- package/lib/plugins/respArea/drag-in-the-blank/choice.js.map +1 -1
- package/lib/plugins/respArea/drag-in-the-blank/index.js +12 -12
- package/lib/plugins/respArea/drag-in-the-blank/index.js.map +1 -1
- package/lib/plugins/respArea/explicit-constructed-response/index.js +10 -9
- package/lib/plugins/respArea/explicit-constructed-response/index.js.map +1 -1
- package/lib/plugins/respArea/icons/index.js +11 -11
- package/lib/plugins/respArea/icons/index.js.map +1 -1
- package/lib/plugins/respArea/index.js +58 -42
- package/lib/plugins/respArea/index.js.map +1 -1
- package/lib/plugins/respArea/inline-dropdown/index.js +8 -8
- package/lib/plugins/respArea/inline-dropdown/index.js.map +1 -1
- package/lib/plugins/respArea/utils.js +5 -5
- package/lib/plugins/respArea/utils.js.map +1 -1
- package/lib/plugins/table/icons/index.js +12 -12
- package/lib/plugins/table/icons/index.js.map +1 -1
- package/lib/plugins/table/index.js +83 -27
- package/lib/plugins/table/index.js.map +1 -1
- package/lib/plugins/table/table-toolbar.js +41 -50
- package/lib/plugins/table/table-toolbar.js.map +1 -1
- package/lib/plugins/toolbar/default-toolbar.js +14 -11
- package/lib/plugins/toolbar/default-toolbar.js.map +1 -1
- package/lib/plugins/toolbar/done-button.js +5 -5
- package/lib/plugins/toolbar/done-button.js.map +1 -1
- package/lib/plugins/toolbar/editor-and-toolbar.js +43 -43
- package/lib/plugins/toolbar/editor-and-toolbar.js.map +1 -1
- package/lib/plugins/toolbar/index.js +5 -5
- package/lib/plugins/toolbar/index.js.map +1 -1
- package/lib/plugins/toolbar/toolbar-buttons.js +49 -52
- package/lib/plugins/toolbar/toolbar-buttons.js.map +1 -1
- package/lib/plugins/toolbar/toolbar.js +60 -64
- package/lib/plugins/toolbar/toolbar.js.map +1 -1
- package/lib/plugins/utils.js +1 -1
- package/lib/plugins/utils.js.map +1 -1
- package/lib/serialization.js +32 -9
- package/lib/serialization.js.map +1 -1
- package/lib/theme.js.map +1 -1
- package/package.json +5 -5
- package/src/editor.jsx +189 -31
- package/src/index.jsx +20 -3
- package/src/plugins/characters/custom-popover.js +45 -0
- package/src/plugins/characters/index.jsx +244 -0
- package/src/plugins/characters/utils.js +448 -0
- package/src/plugins/image/component.jsx +202 -21
- package/src/plugins/image/image-toolbar.jsx +25 -20
- package/src/plugins/image/index.jsx +40 -9
- package/src/plugins/index.jsx +4 -1
- package/src/plugins/list/index.jsx +67 -5
- package/src/plugins/math/index.jsx +31 -37
- package/src/plugins/media/index.jsx +3 -0
- package/src/plugins/media/media-dialog.js +1 -1
- package/src/plugins/respArea/drag-in-the-blank/choice.jsx +28 -1
- package/src/plugins/respArea/explicit-constructed-response/index.jsx +3 -3
- package/src/plugins/respArea/index.jsx +51 -31
- package/src/plugins/table/index.jsx +61 -14
- package/src/plugins/toolbar/default-toolbar.jsx +8 -0
- package/src/plugins/toolbar/editor-and-toolbar.jsx +12 -4
- package/src/plugins/toolbar/toolbar-buttons.jsx +13 -2
- package/src/plugins/toolbar/toolbar.jsx +14 -4
- package/src/serialization.jsx +19 -3
|
@@ -20,7 +20,9 @@ export class Component extends React.Component {
|
|
|
20
20
|
classes: PropTypes.object.isRequired,
|
|
21
21
|
attributes: PropTypes.object,
|
|
22
22
|
onFocus: PropTypes.func,
|
|
23
|
-
onBlur: PropTypes.func
|
|
23
|
+
onBlur: PropTypes.func,
|
|
24
|
+
maxImageWidth: PropTypes.number,
|
|
25
|
+
maxImageHeight: PropTypes.number
|
|
24
26
|
};
|
|
25
27
|
|
|
26
28
|
getWidth = percent => {
|
|
@@ -41,19 +43,11 @@ export class Component extends React.Component {
|
|
|
41
43
|
applySizeData = () => {
|
|
42
44
|
const { node, editor } = this.props;
|
|
43
45
|
|
|
44
|
-
const resizePercent = node.data.get('resizePercent');
|
|
45
|
-
log('[applySizeData]: resizePercent: ', resizePercent);
|
|
46
|
-
|
|
47
46
|
let update = node.data;
|
|
48
47
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
update = update.set('
|
|
52
|
-
} else {
|
|
53
|
-
const w = update.get('width');
|
|
54
|
-
if (w) {
|
|
55
|
-
update = update.set('resizePercent', this.getPercentFromWidth(w));
|
|
56
|
-
}
|
|
48
|
+
const w = update.get('width');
|
|
49
|
+
if (w) {
|
|
50
|
+
update = update.set('resizePercent', this.getPercentFromWidth(w));
|
|
57
51
|
}
|
|
58
52
|
|
|
59
53
|
log('[applySizeData] update: ', update);
|
|
@@ -63,8 +57,19 @@ export class Component extends React.Component {
|
|
|
63
57
|
}
|
|
64
58
|
};
|
|
65
59
|
|
|
60
|
+
initialiseResize = () => {
|
|
61
|
+
window.addEventListener('mousemove', this.startResizing, false);
|
|
62
|
+
window.addEventListener('mouseup', this.stopResizing, false);
|
|
63
|
+
};
|
|
64
|
+
|
|
66
65
|
componentDidMount() {
|
|
67
66
|
this.applySizeData();
|
|
67
|
+
|
|
68
|
+
const resizeHandle = this.resize;
|
|
69
|
+
|
|
70
|
+
if (resizeHandle) {
|
|
71
|
+
resizeHandle.addEventListener('mousedown', this.initialiseResize, false);
|
|
72
|
+
}
|
|
68
73
|
}
|
|
69
74
|
|
|
70
75
|
componentDidUpdate() {
|
|
@@ -79,14 +84,151 @@ export class Component extends React.Component {
|
|
|
79
84
|
};
|
|
80
85
|
}
|
|
81
86
|
|
|
87
|
+
loadImage = () => {
|
|
88
|
+
let { maxImageWidth, maxImageHeight } = this.props || {};
|
|
89
|
+
maxImageWidth = maxImageWidth || 700;
|
|
90
|
+
maxImageHeight = maxImageHeight || 900;
|
|
91
|
+
|
|
92
|
+
const box = this.img;
|
|
93
|
+
|
|
94
|
+
//on first load
|
|
95
|
+
if (!box.style.width || box.style.width === 'auto') {
|
|
96
|
+
const dimensions = {
|
|
97
|
+
width: (box && box.naturalWidth) || 100,
|
|
98
|
+
height: (box && box.naturalHeight) || 100
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const { width, height } = this.updateImageDimensions(
|
|
102
|
+
dimensions,
|
|
103
|
+
{
|
|
104
|
+
width: dimensions.width < maxImageWidth ? dimensions.width : maxImageWidth,
|
|
105
|
+
height: dimensions.height < maxImageHeight ? dimensions.height : maxImageHeight
|
|
106
|
+
},
|
|
107
|
+
true
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
box.style.width = `${width}px`;
|
|
111
|
+
box.style.height = `${height}px`;
|
|
112
|
+
|
|
113
|
+
this.setState({
|
|
114
|
+
dimensions: { height: height, width: width }
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const { node, editor } = this.props;
|
|
118
|
+
|
|
119
|
+
let update = node.data;
|
|
120
|
+
|
|
121
|
+
update = update.set('width', width);
|
|
122
|
+
update = update.set('height', height);
|
|
123
|
+
|
|
124
|
+
if (!update.equals(node.data)) {
|
|
125
|
+
editor.change(c => c.setNodeByKey(node.key, { data: update }));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
startResizing = e => {
|
|
131
|
+
const bounds = e.target.getBoundingClientRect();
|
|
132
|
+
const box = this.img;
|
|
133
|
+
const dimensions = {
|
|
134
|
+
width: (box && box.naturalWidth) || 100,
|
|
135
|
+
height: (box && box.naturalHeight) || 100
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const { width, height } = this.updateImageDimensions(
|
|
139
|
+
dimensions,
|
|
140
|
+
{
|
|
141
|
+
width: e.clientX - bounds.left,
|
|
142
|
+
height: e.clientY - bounds.top
|
|
143
|
+
},
|
|
144
|
+
true
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
const hasMinimumWidth = width > 50 && height > 50;
|
|
148
|
+
const hasDimensionsConstraints = width <= 700 && height <= 900;
|
|
149
|
+
|
|
150
|
+
if (hasMinimumWidth && hasDimensionsConstraints && box) {
|
|
151
|
+
box.style.width = `${width}px`;
|
|
152
|
+
box.style.height = `${height}px`;
|
|
153
|
+
|
|
154
|
+
this.setState({
|
|
155
|
+
dimensions: { height: height, width: width }
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const { node, editor } = this.props;
|
|
159
|
+
|
|
160
|
+
let update = node.data;
|
|
161
|
+
|
|
162
|
+
update = update.set('width', width);
|
|
163
|
+
update = update.set('height', height);
|
|
164
|
+
|
|
165
|
+
if (!update.equals(node.data)) {
|
|
166
|
+
editor.change(c => c.setNodeByKey(node.key, { data: update }));
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
stopResizing = () => {
|
|
172
|
+
window.removeEventListener('mousemove', this.startResizing, false);
|
|
173
|
+
window.removeEventListener('mouseup', this.stopResizing, false);
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
updateImageDimensions = (initialDim, nextDim, keepAspectRatio, resizeType) => {
|
|
177
|
+
// if we want to keep image aspect ratio
|
|
178
|
+
if (keepAspectRatio) {
|
|
179
|
+
const imageAspectRatio = initialDim.width / initialDim.height;
|
|
180
|
+
|
|
181
|
+
if (resizeType === 'height') {
|
|
182
|
+
// if we want to change image height => we update the width accordingly
|
|
183
|
+
return {
|
|
184
|
+
width: nextDim.height * imageAspectRatio,
|
|
185
|
+
height: nextDim.height
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// if we want to change image width => we update the height accordingly
|
|
190
|
+
return {
|
|
191
|
+
width: nextDim.width,
|
|
192
|
+
height: nextDim.width / imageAspectRatio
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// if we don't want to keep aspect ratio, we just update both values
|
|
197
|
+
return {
|
|
198
|
+
width: nextDim.width,
|
|
199
|
+
height: nextDim.height
|
|
200
|
+
};
|
|
201
|
+
};
|
|
202
|
+
|
|
82
203
|
render() {
|
|
83
204
|
const { node, editor, classes, attributes, onFocus } = this.props;
|
|
84
205
|
const active = editor.value.isFocused && editor.value.selection.hasEdgeIn(node);
|
|
85
206
|
const src = node.data.get('src');
|
|
86
|
-
const percent = node.data.get('percent');
|
|
87
207
|
const loaded = node.data.get('loaded') !== false;
|
|
88
208
|
const deleteStatus = node.data.get('deleteStatus');
|
|
209
|
+
const alignment = node.data.get('alignment');
|
|
210
|
+
const percent = node.data.get('percent');
|
|
211
|
+
let margin;
|
|
212
|
+
let justifyContent;
|
|
89
213
|
|
|
214
|
+
switch (alignment) {
|
|
215
|
+
case 'left':
|
|
216
|
+
justifyContent = 'flex-start';
|
|
217
|
+
margin = '0';
|
|
218
|
+
break;
|
|
219
|
+
case 'center':
|
|
220
|
+
justifyContent = 'center';
|
|
221
|
+
margin = '0 auto';
|
|
222
|
+
break;
|
|
223
|
+
case 'right':
|
|
224
|
+
justifyContent = 'flex-end';
|
|
225
|
+
margin = 'auto 0 0 auto ';
|
|
226
|
+
break;
|
|
227
|
+
default:
|
|
228
|
+
justifyContent = 'flex-start';
|
|
229
|
+
margin = '0';
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
90
232
|
log('[render] node.data:', node.data);
|
|
91
233
|
|
|
92
234
|
const size = this.getSize(node.data);
|
|
@@ -95,7 +237,6 @@ export class Component extends React.Component {
|
|
|
95
237
|
|
|
96
238
|
const className = classNames(
|
|
97
239
|
classes.root,
|
|
98
|
-
active && classes.active,
|
|
99
240
|
!loaded && classes.loading,
|
|
100
241
|
deleteStatus === 'pending' && classes.pendingDelete
|
|
101
242
|
);
|
|
@@ -104,13 +245,30 @@ export class Component extends React.Component {
|
|
|
104
245
|
|
|
105
246
|
return [
|
|
106
247
|
<span key={'sp1'}> </span>,
|
|
107
|
-
<div key={'comp'} onFocus={onFocus} className={className}>
|
|
248
|
+
<div key={'comp'} onFocus={onFocus} className={className} style={{ justifyContent }}>
|
|
108
249
|
<LinearProgress
|
|
109
250
|
mode="determinate"
|
|
110
251
|
value={percent > 0 ? percent : 0}
|
|
111
252
|
className={progressClasses}
|
|
112
253
|
/>
|
|
113
|
-
<
|
|
254
|
+
<div className={classes.imageContainer}>
|
|
255
|
+
<img
|
|
256
|
+
{...attributes}
|
|
257
|
+
className={classNames(classes.image, active && classes.active)}
|
|
258
|
+
ref={ref => {
|
|
259
|
+
this.img = ref;
|
|
260
|
+
}}
|
|
261
|
+
src={src}
|
|
262
|
+
style={size}
|
|
263
|
+
onLoad={this.loadImage}
|
|
264
|
+
/>
|
|
265
|
+
<div
|
|
266
|
+
ref={ref => {
|
|
267
|
+
this.resize = ref;
|
|
268
|
+
}}
|
|
269
|
+
className={classNames(classes.resize, 'resize')}
|
|
270
|
+
/>
|
|
271
|
+
</div>
|
|
114
272
|
</div>,
|
|
115
273
|
<span key={'sp2'}> </span>
|
|
116
274
|
];
|
|
@@ -135,7 +293,7 @@ const styles = theme => ({
|
|
|
135
293
|
progress: {
|
|
136
294
|
position: 'absolute',
|
|
137
295
|
left: '0',
|
|
138
|
-
width: '
|
|
296
|
+
width: 'fit-content',
|
|
139
297
|
top: '0%',
|
|
140
298
|
transition: 'opacity 200ms linear'
|
|
141
299
|
},
|
|
@@ -151,15 +309,38 @@ const styles = theme => ({
|
|
|
151
309
|
root: {
|
|
152
310
|
position: 'relative',
|
|
153
311
|
border: 'solid 1px white',
|
|
154
|
-
display: '
|
|
312
|
+
display: 'flex',
|
|
155
313
|
transition: 'opacity 200ms linear'
|
|
156
314
|
},
|
|
157
|
-
active: {
|
|
158
|
-
border: `solid 1px ${theme.palette.primary.main}`
|
|
159
|
-
},
|
|
160
315
|
delete: {
|
|
161
316
|
position: 'absolute',
|
|
162
317
|
right: 0
|
|
318
|
+
},
|
|
319
|
+
imageContainer: {
|
|
320
|
+
position: 'relative',
|
|
321
|
+
width: 'fit-content',
|
|
322
|
+
display: 'flex',
|
|
323
|
+
alignItems: 'center',
|
|
324
|
+
|
|
325
|
+
'&&:hover > .resize': {
|
|
326
|
+
display: 'block'
|
|
327
|
+
}
|
|
328
|
+
},
|
|
329
|
+
active: {
|
|
330
|
+
border: `solid 1px ${theme.palette.primary.main}`
|
|
331
|
+
},
|
|
332
|
+
resize: {
|
|
333
|
+
backgroundColor: theme.palette.primary.main,
|
|
334
|
+
cursor: 'col-resize',
|
|
335
|
+
height: '35px',
|
|
336
|
+
width: '5px',
|
|
337
|
+
borderRadius: 8,
|
|
338
|
+
marginLeft: '5px',
|
|
339
|
+
marginRight: '10px',
|
|
340
|
+
display: 'none'
|
|
341
|
+
},
|
|
342
|
+
drawableHeight: {
|
|
343
|
+
minHeight: 350
|
|
163
344
|
}
|
|
164
345
|
});
|
|
165
346
|
|
|
@@ -1,50 +1,55 @@
|
|
|
1
|
-
import { MarkButton } from '../toolbar/toolbar-buttons';
|
|
2
|
-
|
|
3
1
|
import PropTypes from 'prop-types';
|
|
4
2
|
import React from 'react';
|
|
5
3
|
import debug from 'debug';
|
|
6
4
|
import { withStyles } from '@material-ui/core/styles';
|
|
5
|
+
import { MarkButton } from '../toolbar/toolbar-buttons';
|
|
7
6
|
|
|
8
7
|
const log = debug('@pie-lib:editable-html:plugins:image:image-toolbar');
|
|
9
8
|
|
|
10
|
-
const
|
|
11
|
-
const label = `${percent}%`;
|
|
9
|
+
const AlignmentButton = ({ alignment, active, onClick }) => {
|
|
12
10
|
return (
|
|
13
|
-
<MarkButton active={active} onToggle={() => onClick(
|
|
14
|
-
{
|
|
11
|
+
<MarkButton active={active} onToggle={() => onClick(alignment)} label={alignment}>
|
|
12
|
+
{alignment}
|
|
15
13
|
</MarkButton>
|
|
16
14
|
);
|
|
17
15
|
};
|
|
18
16
|
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
AlignmentButton.propTypes = {
|
|
18
|
+
alignment: PropTypes.string.isRequired,
|
|
21
19
|
active: PropTypes.bool.isRequired,
|
|
22
20
|
onClick: PropTypes.func.isRequired
|
|
23
21
|
};
|
|
24
22
|
|
|
25
23
|
export class ImageToolbar extends React.Component {
|
|
26
24
|
static propTypes = {
|
|
27
|
-
percent: PropTypes.number.isRequired,
|
|
28
25
|
onChange: PropTypes.func.isRequired,
|
|
29
26
|
classes: PropTypes.object.isRequired
|
|
30
27
|
};
|
|
31
28
|
|
|
32
|
-
|
|
33
|
-
log('[
|
|
34
|
-
this.props.onChange(
|
|
29
|
+
onAlignmentClick = alignment => {
|
|
30
|
+
log('[onAlignmentClick]: alignment:', alignment);
|
|
31
|
+
this.props.onChange(alignment);
|
|
35
32
|
};
|
|
36
33
|
|
|
37
34
|
render() {
|
|
38
|
-
const { classes,
|
|
35
|
+
const { classes, alignment } = this.props;
|
|
36
|
+
|
|
39
37
|
return (
|
|
40
38
|
<div className={classes.holder}>
|
|
41
|
-
<
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
39
|
+
<AlignmentButton
|
|
40
|
+
alignment={'left'}
|
|
41
|
+
active={alignment === 'left'}
|
|
42
|
+
onClick={this.onAlignmentClick}
|
|
43
|
+
/>
|
|
44
|
+
<AlignmentButton
|
|
45
|
+
alignment={'center'}
|
|
46
|
+
active={alignment === 'center'}
|
|
47
|
+
onClick={this.onAlignmentClick}
|
|
48
|
+
/>
|
|
49
|
+
<AlignmentButton
|
|
50
|
+
alignment={'right'}
|
|
51
|
+
active={alignment === 'right'}
|
|
52
|
+
onClick={this.onAlignmentClick}
|
|
48
53
|
/>
|
|
49
54
|
</div>
|
|
50
55
|
);
|
|
@@ -29,19 +29,18 @@ export default function ImagePlugin(opts) {
|
|
|
29
29
|
},
|
|
30
30
|
supports: node => node.object === 'inline' && node.type === 'image',
|
|
31
31
|
customToolbar: (node, value, onToolbarDone) => {
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
const onChange = resizePercent => {
|
|
32
|
+
const alignment = node.data.get('alignment');
|
|
33
|
+
const onChange = alignment => {
|
|
35
34
|
const update = {
|
|
36
35
|
...node.data.toObject(),
|
|
37
|
-
|
|
36
|
+
alignment
|
|
38
37
|
};
|
|
39
38
|
|
|
40
39
|
const change = value.change().setNodeByKey(node.key, { data: update });
|
|
41
40
|
onToolbarDone(change, false);
|
|
42
41
|
};
|
|
43
42
|
|
|
44
|
-
const Tb = () => <ImageToolbar
|
|
43
|
+
const Tb = () => <ImageToolbar alignment={alignment || 'left'} onChange={onChange} />;
|
|
45
44
|
return Tb;
|
|
46
45
|
},
|
|
47
46
|
showDone: true
|
|
@@ -90,7 +89,9 @@ export default function ImagePlugin(opts) {
|
|
|
90
89
|
{
|
|
91
90
|
onDelete: opts.onDelete,
|
|
92
91
|
onFocus: opts.onFocus,
|
|
93
|
-
onBlur: opts.onBlur
|
|
92
|
+
onBlur: opts.onBlur,
|
|
93
|
+
maxImageWidth: opts.maxImageWidth,
|
|
94
|
+
maxImageHeight: opts.maxImageHeight
|
|
94
95
|
},
|
|
95
96
|
props
|
|
96
97
|
);
|
|
@@ -135,7 +136,7 @@ export const serialization = {
|
|
|
135
136
|
if (name !== 'img') return;
|
|
136
137
|
|
|
137
138
|
log('deserialize: ', name);
|
|
138
|
-
const style = el.style || { width: '', height: '' };
|
|
139
|
+
const style = el.style || { width: '', height: '', margin: '', justifyContent: '' };
|
|
139
140
|
const width = parseInt(style.width.replace('px', ''), 10) || null;
|
|
140
141
|
const height = parseInt(style.height.replace('px', ''), 10) || null;
|
|
141
142
|
|
|
@@ -146,7 +147,10 @@ export const serialization = {
|
|
|
146
147
|
data: {
|
|
147
148
|
src: el.getAttribute('src'),
|
|
148
149
|
width,
|
|
149
|
-
height
|
|
150
|
+
height,
|
|
151
|
+
margin: el.style.margin,
|
|
152
|
+
justifyContent: el.style.justifyContent,
|
|
153
|
+
alignment: el.getAttribute('alignment')
|
|
150
154
|
}
|
|
151
155
|
};
|
|
152
156
|
log('return object: ', out);
|
|
@@ -159,6 +163,9 @@ export const serialization = {
|
|
|
159
163
|
const src = data.get('src');
|
|
160
164
|
const width = data.get('width');
|
|
161
165
|
const height = data.get('height');
|
|
166
|
+
const alignment = data.get('alignment');
|
|
167
|
+
const margin = data.get('margin');
|
|
168
|
+
const justifyContent = data.get('margin');
|
|
162
169
|
const style = {};
|
|
163
170
|
if (width) {
|
|
164
171
|
style.width = `${width}px`;
|
|
@@ -168,11 +175,35 @@ export const serialization = {
|
|
|
168
175
|
style.height = `${height}px`;
|
|
169
176
|
}
|
|
170
177
|
|
|
178
|
+
style.margin = margin;
|
|
179
|
+
style.justifyContent = justifyContent;
|
|
180
|
+
|
|
181
|
+
if (alignment) {
|
|
182
|
+
switch (alignment) {
|
|
183
|
+
case 'left':
|
|
184
|
+
style.justifyContent = 'flex-start';
|
|
185
|
+
style.margin = '0';
|
|
186
|
+
break;
|
|
187
|
+
case 'center':
|
|
188
|
+
style.justifyContent = 'center';
|
|
189
|
+
style.margin = '0 auto';
|
|
190
|
+
break;
|
|
191
|
+
case 'right':
|
|
192
|
+
style.justifyContent = 'flex-end';
|
|
193
|
+
style.margin = 'auto 0 0 auto';
|
|
194
|
+
break;
|
|
195
|
+
default:
|
|
196
|
+
style.justifyContent = 'flex-start';
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
171
201
|
style.objectFit = 'contain';
|
|
172
202
|
|
|
173
203
|
const props = {
|
|
174
204
|
src,
|
|
175
|
-
style
|
|
205
|
+
style,
|
|
206
|
+
alignment
|
|
176
207
|
};
|
|
177
208
|
|
|
178
209
|
return <img {...props} />;
|
package/src/plugins/index.jsx
CHANGED
|
@@ -4,6 +4,7 @@ import BulletedListIcon from '@material-ui/icons/FormatListBulleted';
|
|
|
4
4
|
import NumberedListIcon from '@material-ui/icons/FormatListNumbered';
|
|
5
5
|
import ImagePlugin from './image';
|
|
6
6
|
import MediaPlugin from './media';
|
|
7
|
+
import CharactersPlugin from './characters';
|
|
7
8
|
import Italic from '@material-ui/icons/FormatItalic';
|
|
8
9
|
import MathPlugin from './math';
|
|
9
10
|
import React from 'react';
|
|
@@ -63,6 +64,7 @@ export const ALL_PLUGINS = [
|
|
|
63
64
|
'numbered-list',
|
|
64
65
|
'image',
|
|
65
66
|
'math',
|
|
67
|
+
'languageCharacters',
|
|
66
68
|
'table',
|
|
67
69
|
'video',
|
|
68
70
|
'audio',
|
|
@@ -103,10 +105,11 @@ export const buildPlugins = (activePlugins, opts) => {
|
|
|
103
105
|
addIf('video', MediaPlugin('video', opts.media)),
|
|
104
106
|
addIf('audio', MediaPlugin('audio', opts.media)),
|
|
105
107
|
addIf('math', mathPlugin),
|
|
108
|
+
...opts.languageCharacters.map(config => addIf('languageCharacters', CharactersPlugin(config))),
|
|
106
109
|
addIf('bulleted-list', List({ key: 'l', type: 'ul_list', icon: <BulletedListIcon /> })),
|
|
107
110
|
addIf('numbered-list', List({ key: 'n', type: 'ol_list', icon: <NumberedListIcon /> })),
|
|
108
111
|
ToolbarPlugin(opts.toolbar),
|
|
109
|
-
SoftBreakPlugin(),
|
|
112
|
+
SoftBreakPlugin({ shift: true }),
|
|
110
113
|
addIf('responseArea', respAreaPlugin)
|
|
111
114
|
]);
|
|
112
115
|
};
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import { Data } from 'slate';
|
|
3
|
+
import Immutable from 'immutable';
|
|
2
4
|
import PropTypes from 'prop-types';
|
|
3
5
|
import EditList from 'slate-edit-list';
|
|
4
6
|
import debug from 'debug';
|
|
@@ -44,15 +46,12 @@ export const serialization = {
|
|
|
44
46
|
}
|
|
45
47
|
};
|
|
46
48
|
|
|
47
|
-
|
|
48
|
-
const { type, icon } = options;
|
|
49
|
-
|
|
49
|
+
const createEditList = () => {
|
|
50
50
|
const core = EditList({
|
|
51
51
|
typeDefault: 'span'
|
|
52
52
|
});
|
|
53
53
|
|
|
54
54
|
// fix outdated schema
|
|
55
|
-
|
|
56
55
|
if (core.schema && core.schema.blocks) {
|
|
57
56
|
Object.keys(core.schema.blocks).forEach(key => {
|
|
58
57
|
const block = core.schema.blocks[key];
|
|
@@ -65,6 +64,69 @@ export default options => {
|
|
|
65
64
|
});
|
|
66
65
|
}
|
|
67
66
|
|
|
67
|
+
/**
|
|
68
|
+
* This override of the core.changes.wrapInList is needed because the version
|
|
69
|
+
* of immutable that we have does not support getting the element at a specific
|
|
70
|
+
* index with a square bracket (list[0]). We have to use the list.get function instead
|
|
71
|
+
*/
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Returns the highest list of blocks that cover the current selection
|
|
75
|
+
*/
|
|
76
|
+
const getHighestSelectedBlocks = value => {
|
|
77
|
+
const range = value.selection;
|
|
78
|
+
const document = value.document;
|
|
79
|
+
|
|
80
|
+
const startBlock = document.getClosestBlock(range.startKey);
|
|
81
|
+
const endBlock = document.getClosestBlock(range.endKey);
|
|
82
|
+
|
|
83
|
+
if (startBlock === endBlock) {
|
|
84
|
+
return Immutable.List([startBlock]);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const ancestor = document.getCommonAncestor(startBlock.key, endBlock.key);
|
|
88
|
+
const startPath = ancestor.getPath(startBlock.key);
|
|
89
|
+
const endPath = ancestor.getPath(endBlock.key);
|
|
90
|
+
|
|
91
|
+
return ancestor.nodes.slice(startPath.get(0), endPath.get(0) + 1);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Wrap the blocks in the current selection in a new list. Selected
|
|
96
|
+
* lists are merged together.
|
|
97
|
+
*/
|
|
98
|
+
core.changes.wrapInList = function(change, type, data) {
|
|
99
|
+
const selectedBlocks = getHighestSelectedBlocks(change.value);
|
|
100
|
+
|
|
101
|
+
// Wrap in container
|
|
102
|
+
change.wrapBlock({ type: type, data: Data.create(data) }, { normalize: false });
|
|
103
|
+
|
|
104
|
+
// Wrap in list items
|
|
105
|
+
selectedBlocks.forEach(function(node) {
|
|
106
|
+
if (core.utils.isList(node)) {
|
|
107
|
+
// Merge its items with the created list
|
|
108
|
+
node.nodes.forEach(function(_ref) {
|
|
109
|
+
const key = _ref.key;
|
|
110
|
+
return change.unwrapNodeByKey(key, { normalize: false });
|
|
111
|
+
});
|
|
112
|
+
} else if (node.type !== 'list_item') {
|
|
113
|
+
change.wrapBlockByKey(node.key, 'list_item', {
|
|
114
|
+
normalize: false
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
return change.normalize();
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
return core;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
export default options => {
|
|
126
|
+
const { type, icon } = options;
|
|
127
|
+
|
|
128
|
+
const core = createEditList();
|
|
129
|
+
|
|
68
130
|
// eslint-disable-next-line react/display-name
|
|
69
131
|
core.renderNode = props => {
|
|
70
132
|
const { node, attributes, children } = props;
|
|
@@ -88,7 +150,7 @@ export default options => {
|
|
|
88
150
|
return false;
|
|
89
151
|
}
|
|
90
152
|
const current = core.utils.getCurrentList(value);
|
|
91
|
-
return current.type === type;
|
|
153
|
+
return current ? current.type === type : false;
|
|
92
154
|
},
|
|
93
155
|
onClick: (value, onChange) => {
|
|
94
156
|
log('[onClick]', value);
|
|
@@ -143,42 +143,6 @@ export default function MathPlugin() {
|
|
|
143
143
|
|
|
144
144
|
return <span {...props.attributes} dangerouslySetInnerHTML={{ __html: html }} />;
|
|
145
145
|
}
|
|
146
|
-
},
|
|
147
|
-
|
|
148
|
-
normalizeNode: node => {
|
|
149
|
-
if (node.object !== 'document') {
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const addSpacesArray = [];
|
|
154
|
-
|
|
155
|
-
const allElements = node.filterDescendants(d => d.type === 'math');
|
|
156
|
-
|
|
157
|
-
allElements.forEach(el => {
|
|
158
|
-
const prevText = node.getPreviousText(el.key);
|
|
159
|
-
const lastCharIsNewLine = prevText.text[prevText.text.length - 1] === '\n';
|
|
160
|
-
|
|
161
|
-
if (prevText.text.length === 0 || lastCharIsNewLine) {
|
|
162
|
-
addSpacesArray.push({
|
|
163
|
-
nr: lastCharIsNewLine ? 1 : 2,
|
|
164
|
-
key: prevText.key
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
if (!addSpacesArray.length) {
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
return change => {
|
|
174
|
-
change.withoutNormalization(() => {
|
|
175
|
-
addSpacesArray.forEach(({ key, nr }) => {
|
|
176
|
-
const node = change.value.document.getNode(key);
|
|
177
|
-
|
|
178
|
-
change.insertTextByKey(key, node.text.length, '\u00A0'.repeat(nr));
|
|
179
|
-
});
|
|
180
|
-
});
|
|
181
|
-
};
|
|
182
146
|
}
|
|
183
147
|
};
|
|
184
148
|
}
|
|
@@ -208,6 +172,36 @@ const getTagName = el => {
|
|
|
208
172
|
return ((el && el.tagName) || '').toLowerCase();
|
|
209
173
|
};
|
|
210
174
|
|
|
175
|
+
/**
|
|
176
|
+
* Makes sure that strings that contain stuff like:
|
|
177
|
+
* x<y are not transformed into x by the DOMParser because it thinks
|
|
178
|
+
* that <y is the start of a dom element tag
|
|
179
|
+
* @param input
|
|
180
|
+
* @returns {*}
|
|
181
|
+
*/
|
|
182
|
+
const lessThanHandling = input => {
|
|
183
|
+
const arrowSplit = input.split('<');
|
|
184
|
+
|
|
185
|
+
// if we don't have at least 2 characters there's no point in checking
|
|
186
|
+
if (input.length > 2) {
|
|
187
|
+
return arrowSplit.reduce((st, part) => {
|
|
188
|
+
/*
|
|
189
|
+
We check if this element resulted is:
|
|
190
|
+
div - continuation of a beginning of a HTML element
|
|
191
|
+
/div - closing of a HTML tag
|
|
192
|
+
br/> - beginning and closing of a html TAG
|
|
193
|
+
*/
|
|
194
|
+
if (part.match(/<[a-zA-Z/][\s\S]*>/ig)) {
|
|
195
|
+
return `${st}${st ? '<' : ''}${part}`;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return `${st}${st ? '<' : ''}${part}`;
|
|
199
|
+
}, '');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return input;
|
|
203
|
+
};
|
|
204
|
+
|
|
211
205
|
export const serialization = {
|
|
212
206
|
deserialize(el) {
|
|
213
207
|
const tagName = getTagName(el);
|
|
@@ -269,7 +263,7 @@ export const serialization = {
|
|
|
269
263
|
const l = object.data.get('latex');
|
|
270
264
|
const wrapper = object.data.get('wrapper');
|
|
271
265
|
log('[serialize] latex: ', l);
|
|
272
|
-
const decoded = htmlDecode(l);
|
|
266
|
+
const decoded = htmlDecode(lessThanHandling(l));
|
|
273
267
|
return (
|
|
274
268
|
<span data-latex="" data-raw={decoded}>
|
|
275
269
|
{wrapMath(decoded, wrapper)}
|