@pie-lib/editable-html 7.17.4-next.53 → 7.17.4-next.549
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 +150 -0
- package/CHANGELOG.md +421 -0
- package/lib/editor.js +385 -172
- package/lib/editor.js.map +1 -1
- package/lib/index.js +66 -53
- package/lib/index.js.map +1 -1
- package/lib/parse-html.js.map +1 -1
- package/lib/plugins/characters/custom-popper.js +73 -0
- package/lib/plugins/characters/custom-popper.js.map +1 -0
- package/lib/plugins/characters/index.js +285 -0
- package/lib/plugins/characters/index.js.map +1 -0
- package/lib/plugins/characters/utils.js +381 -0
- package/lib/plugins/characters/utils.js.map +1 -0
- package/lib/plugins/image/alt-dialog.js +119 -0
- package/lib/plugins/image/alt-dialog.js.map +1 -0
- package/lib/plugins/image/component.js +253 -77
- package/lib/plugins/image/component.js.map +1 -1
- package/lib/plugins/image/image-toolbar.js +95 -61
- package/lib/plugins/image/image-toolbar.js.map +1 -1
- package/lib/plugins/image/index.js +62 -20
- 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 +19 -13
- 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 +51 -44
- 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 +64 -62
- 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 +7 -6
- package/src/editor.jsx +218 -25
- package/src/index.jsx +22 -5
- package/src/plugins/characters/custom-popper.js +48 -0
- package/src/plugins/characters/index.jsx +268 -0
- package/src/plugins/characters/utils.js +447 -0
- package/src/plugins/image/alt-dialog.jsx +69 -0
- package/src/plugins/image/component.jsx +204 -21
- package/src/plugins/image/image-toolbar.jsx +68 -22
- package/src/plugins/image/index.jsx +47 -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 +50 -31
- package/src/plugins/table/index.jsx +63 -14
- package/src/plugins/toolbar/default-toolbar.jsx +20 -2
- package/src/plugins/toolbar/editor-and-toolbar.jsx +35 -4
- package/src/plugins/toolbar/toolbar-buttons.jsx +13 -2
- package/src/plugins/toolbar/toolbar.jsx +18 -3
- package/src/serialization.jsx +19 -3
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import DialogContent from '@material-ui/core/DialogContent';
|
|
3
|
+
import ArrowBackIos from '@material-ui/icons/ArrowBackIos';
|
|
4
|
+
import TextField from '@material-ui/core/TextField';
|
|
5
|
+
import DialogActions from '@material-ui/core/DialogActions';
|
|
6
|
+
import Button from '@material-ui/core/Button';
|
|
7
|
+
import Dialog from '@material-ui/core/Dialog';
|
|
8
|
+
import PropTypes from "prop-types";
|
|
9
|
+
|
|
10
|
+
export class AltDialog extends React.Component {
|
|
11
|
+
static propTypes = {
|
|
12
|
+
onDone: PropTypes.func.isRequired,
|
|
13
|
+
alt: PropTypes.string
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
constructor(props) {
|
|
17
|
+
super(props);
|
|
18
|
+
|
|
19
|
+
const { alt } = props;
|
|
20
|
+
|
|
21
|
+
this.state = {
|
|
22
|
+
value: alt
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
closeDialog = () => {
|
|
27
|
+
const allDialogs = document.querySelectorAll('#text-dialog');
|
|
28
|
+
|
|
29
|
+
allDialogs.forEach(function(s) {
|
|
30
|
+
return s.remove();
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
onDone = () => {
|
|
35
|
+
const { onDone } = this.props;
|
|
36
|
+
const { value } = this.state;
|
|
37
|
+
|
|
38
|
+
onDone(value);
|
|
39
|
+
this.closeDialog();
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
render() {
|
|
43
|
+
const { value } = this.state;
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<Dialog open disablePortal onClose={this.closeDialog} id="text-dialog" hideBackdrop>
|
|
47
|
+
<DialogContent>
|
|
48
|
+
<div style={{ display: 'flex' }}>
|
|
49
|
+
<ArrowBackIos style={{ paddingTop: '6px' }} />
|
|
50
|
+
<TextField
|
|
51
|
+
multiline
|
|
52
|
+
placeholder={'Enter an Alt Text description of this image'}
|
|
53
|
+
helperText={
|
|
54
|
+
'Users with visual limitations rely on Alt Text, since screen readers cannot otherwise describe the contents of an image.'
|
|
55
|
+
}
|
|
56
|
+
value={value}
|
|
57
|
+
onChange={event => this.setState({ value: event.target.value })}
|
|
58
|
+
/>
|
|
59
|
+
</div>
|
|
60
|
+
</DialogContent>
|
|
61
|
+
<DialogActions>
|
|
62
|
+
<Button onClick={this.onDone}>Done</Button>
|
|
63
|
+
</DialogActions>
|
|
64
|
+
</Dialog>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export default AltDialog;
|
|
@@ -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,152 @@ 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
|
+
const alt = node.data.get('alt');
|
|
212
|
+
let margin;
|
|
213
|
+
let justifyContent;
|
|
89
214
|
|
|
215
|
+
switch (alignment) {
|
|
216
|
+
case 'left':
|
|
217
|
+
justifyContent = 'flex-start';
|
|
218
|
+
margin = '0';
|
|
219
|
+
break;
|
|
220
|
+
case 'center':
|
|
221
|
+
justifyContent = 'center';
|
|
222
|
+
margin = '0 auto';
|
|
223
|
+
break;
|
|
224
|
+
case 'right':
|
|
225
|
+
justifyContent = 'flex-end';
|
|
226
|
+
margin = 'auto 0 0 auto ';
|
|
227
|
+
break;
|
|
228
|
+
default:
|
|
229
|
+
justifyContent = 'flex-start';
|
|
230
|
+
margin = '0';
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
90
233
|
log('[render] node.data:', node.data);
|
|
91
234
|
|
|
92
235
|
const size = this.getSize(node.data);
|
|
@@ -95,7 +238,6 @@ export class Component extends React.Component {
|
|
|
95
238
|
|
|
96
239
|
const className = classNames(
|
|
97
240
|
classes.root,
|
|
98
|
-
active && classes.active,
|
|
99
241
|
!loaded && classes.loading,
|
|
100
242
|
deleteStatus === 'pending' && classes.pendingDelete
|
|
101
243
|
);
|
|
@@ -104,13 +246,31 @@ export class Component extends React.Component {
|
|
|
104
246
|
|
|
105
247
|
return [
|
|
106
248
|
<span key={'sp1'}> </span>,
|
|
107
|
-
<div key={'comp'} onFocus={onFocus} className={className}>
|
|
249
|
+
<div key={'comp'} onFocus={onFocus} className={className} style={{ justifyContent }}>
|
|
108
250
|
<LinearProgress
|
|
109
251
|
mode="determinate"
|
|
110
252
|
value={percent > 0 ? percent : 0}
|
|
111
253
|
className={progressClasses}
|
|
112
254
|
/>
|
|
113
|
-
<
|
|
255
|
+
<div className={classes.imageContainer}>
|
|
256
|
+
<img
|
|
257
|
+
{...attributes}
|
|
258
|
+
className={classNames(classes.image, active && classes.active)}
|
|
259
|
+
ref={ref => {
|
|
260
|
+
this.img = ref;
|
|
261
|
+
}}
|
|
262
|
+
src={src}
|
|
263
|
+
style={size}
|
|
264
|
+
onLoad={this.loadImage}
|
|
265
|
+
alt={alt}
|
|
266
|
+
/>
|
|
267
|
+
<div
|
|
268
|
+
ref={ref => {
|
|
269
|
+
this.resize = ref;
|
|
270
|
+
}}
|
|
271
|
+
className={classNames(classes.resize, 'resize')}
|
|
272
|
+
/>
|
|
273
|
+
</div>
|
|
114
274
|
</div>,
|
|
115
275
|
<span key={'sp2'}> </span>
|
|
116
276
|
];
|
|
@@ -135,7 +295,7 @@ const styles = theme => ({
|
|
|
135
295
|
progress: {
|
|
136
296
|
position: 'absolute',
|
|
137
297
|
left: '0',
|
|
138
|
-
width: '
|
|
298
|
+
width: 'fit-content',
|
|
139
299
|
top: '0%',
|
|
140
300
|
transition: 'opacity 200ms linear'
|
|
141
301
|
},
|
|
@@ -151,15 +311,38 @@ const styles = theme => ({
|
|
|
151
311
|
root: {
|
|
152
312
|
position: 'relative',
|
|
153
313
|
border: 'solid 1px white',
|
|
154
|
-
display: '
|
|
314
|
+
display: 'flex',
|
|
155
315
|
transition: 'opacity 200ms linear'
|
|
156
316
|
},
|
|
157
|
-
active: {
|
|
158
|
-
border: `solid 1px ${theme.palette.primary.main}`
|
|
159
|
-
},
|
|
160
317
|
delete: {
|
|
161
318
|
position: 'absolute',
|
|
162
319
|
right: 0
|
|
320
|
+
},
|
|
321
|
+
imageContainer: {
|
|
322
|
+
position: 'relative',
|
|
323
|
+
width: 'fit-content',
|
|
324
|
+
display: 'flex',
|
|
325
|
+
alignItems: 'center',
|
|
326
|
+
|
|
327
|
+
'&&:hover > .resize': {
|
|
328
|
+
display: 'block'
|
|
329
|
+
}
|
|
330
|
+
},
|
|
331
|
+
active: {
|
|
332
|
+
border: `solid 1px ${theme.palette.primary.main}`
|
|
333
|
+
},
|
|
334
|
+
resize: {
|
|
335
|
+
backgroundColor: theme.palette.primary.main,
|
|
336
|
+
cursor: 'col-resize',
|
|
337
|
+
height: '35px',
|
|
338
|
+
width: '5px',
|
|
339
|
+
borderRadius: 8,
|
|
340
|
+
marginLeft: '5px',
|
|
341
|
+
marginRight: '10px',
|
|
342
|
+
display: 'none'
|
|
343
|
+
},
|
|
344
|
+
drawableHeight: {
|
|
345
|
+
minHeight: 350
|
|
163
346
|
}
|
|
164
347
|
});
|
|
165
348
|
|
|
@@ -1,51 +1,89 @@
|
|
|
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';
|
|
4
|
+
import ReactDOM from 'react-dom';
|
|
6
5
|
import { withStyles } from '@material-ui/core/styles';
|
|
6
|
+
import classNames from 'classnames';
|
|
7
|
+
|
|
8
|
+
import { MarkButton } from '../toolbar/toolbar-buttons';
|
|
9
|
+
import AltDialog from './alt-dialog';
|
|
7
10
|
|
|
8
11
|
const log = debug('@pie-lib:editable-html:plugins:image:image-toolbar');
|
|
9
12
|
|
|
10
|
-
const
|
|
11
|
-
const label = `${percent}%`;
|
|
13
|
+
const AlignmentButton = ({ alignment, active, onClick }) => {
|
|
12
14
|
return (
|
|
13
|
-
<MarkButton active={active} onToggle={() => onClick(
|
|
14
|
-
{
|
|
15
|
+
<MarkButton active={active} onToggle={() => onClick(alignment)} label={alignment}>
|
|
16
|
+
{alignment}
|
|
15
17
|
</MarkButton>
|
|
16
18
|
);
|
|
17
19
|
};
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
AlignmentButton.propTypes = {
|
|
22
|
+
alignment: PropTypes.string.isRequired,
|
|
21
23
|
active: PropTypes.bool.isRequired,
|
|
22
24
|
onClick: PropTypes.func.isRequired
|
|
23
25
|
};
|
|
24
26
|
|
|
25
27
|
export class ImageToolbar extends React.Component {
|
|
26
28
|
static propTypes = {
|
|
27
|
-
percent: PropTypes.number.isRequired,
|
|
28
29
|
onChange: PropTypes.func.isRequired,
|
|
29
|
-
classes: PropTypes.object.isRequired
|
|
30
|
+
classes: PropTypes.object.isRequired,
|
|
31
|
+
alignment: PropTypes.string,
|
|
32
|
+
alt: PropTypes.string,
|
|
33
|
+
imageLoaded: PropTypes.bool
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
onAltTextDone = newAlt => {
|
|
37
|
+
log('[onAltTextDone]: alt:', newAlt);
|
|
38
|
+
|
|
39
|
+
this.props.onChange({ alt: newAlt });
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
onAlignmentClick = alignment => {
|
|
43
|
+
log('[onAlignmentClick]: alignment:', alignment);
|
|
44
|
+
this.props.onChange({ alignment });
|
|
30
45
|
};
|
|
31
46
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
47
|
+
renderDialog = () => {
|
|
48
|
+
const { alt } = this.props;
|
|
49
|
+
const popoverEl = document.createElement('div');
|
|
50
|
+
|
|
51
|
+
const el = <AltDialog alt={alt} onDone={this.onAltTextDone} />;
|
|
52
|
+
|
|
53
|
+
ReactDOM.render(el, popoverEl);
|
|
54
|
+
|
|
55
|
+
document.body.appendChild(popoverEl);
|
|
35
56
|
};
|
|
36
57
|
|
|
37
58
|
render() {
|
|
38
|
-
const { classes,
|
|
59
|
+
const { classes, alignment, imageLoaded } = this.props;
|
|
60
|
+
|
|
39
61
|
return (
|
|
40
62
|
<div className={classes.holder}>
|
|
41
|
-
<
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
63
|
+
<AlignmentButton
|
|
64
|
+
alignment={'left'}
|
|
65
|
+
active={alignment === 'left'}
|
|
66
|
+
onClick={this.onAlignmentClick}
|
|
67
|
+
/>
|
|
68
|
+
<AlignmentButton
|
|
69
|
+
alignment={'center'}
|
|
70
|
+
active={alignment === 'center'}
|
|
71
|
+
onClick={this.onAlignmentClick}
|
|
48
72
|
/>
|
|
73
|
+
<AlignmentButton
|
|
74
|
+
alignment={'right'}
|
|
75
|
+
active={alignment === 'right'}
|
|
76
|
+
onClick={this.onAlignmentClick}
|
|
77
|
+
/>
|
|
78
|
+
<span
|
|
79
|
+
className={classNames({
|
|
80
|
+
[classes.disabled]: !imageLoaded,
|
|
81
|
+
[classes.altButton]: true
|
|
82
|
+
})}
|
|
83
|
+
onMouseDown={event => imageLoaded && this.renderDialog(event)}
|
|
84
|
+
>
|
|
85
|
+
Alt text
|
|
86
|
+
</span>
|
|
49
87
|
</div>
|
|
50
88
|
);
|
|
51
89
|
}
|
|
@@ -56,7 +94,15 @@ const styles = theme => ({
|
|
|
56
94
|
paddingLeft: theme.spacing.unit,
|
|
57
95
|
display: 'flex',
|
|
58
96
|
alignItems: 'center'
|
|
59
|
-
}
|
|
97
|
+
},
|
|
98
|
+
disabled: {
|
|
99
|
+
opacity: 0.5
|
|
100
|
+
},
|
|
101
|
+
altButton: {
|
|
102
|
+
borderLeft: '1px solid grey',
|
|
103
|
+
paddingLeft: 8,
|
|
104
|
+
marginLeft: 4,
|
|
105
|
+
},
|
|
60
106
|
});
|
|
61
107
|
|
|
62
108
|
export default withStyles(styles)(ImageToolbar);
|
|
@@ -29,19 +29,22 @@ 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
|
|
32
|
+
const alignment = node.data.get('alignment');
|
|
33
|
+
const alt = node.data.get('alt');
|
|
34
|
+
const imageLoaded = node.data.get('loaded') !== false;
|
|
35
|
+
const onChange = newValues => {
|
|
35
36
|
const update = {
|
|
36
37
|
...node.data.toObject(),
|
|
37
|
-
|
|
38
|
+
...newValues
|
|
38
39
|
};
|
|
39
40
|
|
|
40
41
|
const change = value.change().setNodeByKey(node.key, { data: update });
|
|
41
42
|
onToolbarDone(change, false);
|
|
42
43
|
};
|
|
43
44
|
|
|
44
|
-
const Tb = () =>
|
|
45
|
+
const Tb = () => (
|
|
46
|
+
<ImageToolbar alt={alt} imageLoaded={imageLoaded} alignment={alignment || 'left'} onChange={onChange} />
|
|
47
|
+
);
|
|
45
48
|
return Tb;
|
|
46
49
|
},
|
|
47
50
|
showDone: true
|
|
@@ -90,7 +93,9 @@ export default function ImagePlugin(opts) {
|
|
|
90
93
|
{
|
|
91
94
|
onDelete: opts.onDelete,
|
|
92
95
|
onFocus: opts.onFocus,
|
|
93
|
-
onBlur: opts.onBlur
|
|
96
|
+
onBlur: opts.onBlur,
|
|
97
|
+
maxImageWidth: opts.maxImageWidth,
|
|
98
|
+
maxImageHeight: opts.maxImageHeight
|
|
94
99
|
},
|
|
95
100
|
props
|
|
96
101
|
);
|
|
@@ -135,7 +140,7 @@ export const serialization = {
|
|
|
135
140
|
if (name !== 'img') return;
|
|
136
141
|
|
|
137
142
|
log('deserialize: ', name);
|
|
138
|
-
const style = el.style || { width: '', height: '' };
|
|
143
|
+
const style = el.style || { width: '', height: '', margin: '', justifyContent: '' };
|
|
139
144
|
const width = parseInt(style.width.replace('px', ''), 10) || null;
|
|
140
145
|
const height = parseInt(style.height.replace('px', ''), 10) || null;
|
|
141
146
|
|
|
@@ -146,7 +151,11 @@ export const serialization = {
|
|
|
146
151
|
data: {
|
|
147
152
|
src: el.getAttribute('src'),
|
|
148
153
|
width,
|
|
149
|
-
height
|
|
154
|
+
height,
|
|
155
|
+
margin: el.style.margin,
|
|
156
|
+
justifyContent: el.style.justifyContent,
|
|
157
|
+
alignment: el.getAttribute('alignment'),
|
|
158
|
+
alt: el.getAttribute('alt')
|
|
150
159
|
}
|
|
151
160
|
};
|
|
152
161
|
log('return object: ', out);
|
|
@@ -159,6 +168,10 @@ export const serialization = {
|
|
|
159
168
|
const src = data.get('src');
|
|
160
169
|
const width = data.get('width');
|
|
161
170
|
const height = data.get('height');
|
|
171
|
+
const alignment = data.get('alignment');
|
|
172
|
+
const margin = data.get('margin');
|
|
173
|
+
const justifyContent = data.get('margin');
|
|
174
|
+
const alt = data.get('alt');
|
|
162
175
|
const style = {};
|
|
163
176
|
if (width) {
|
|
164
177
|
style.width = `${width}px`;
|
|
@@ -168,11 +181,36 @@ export const serialization = {
|
|
|
168
181
|
style.height = `${height}px`;
|
|
169
182
|
}
|
|
170
183
|
|
|
184
|
+
style.margin = margin;
|
|
185
|
+
style.justifyContent = justifyContent;
|
|
186
|
+
|
|
187
|
+
if (alignment) {
|
|
188
|
+
switch (alignment) {
|
|
189
|
+
case 'left':
|
|
190
|
+
style.justifyContent = 'flex-start';
|
|
191
|
+
style.margin = '0';
|
|
192
|
+
break;
|
|
193
|
+
case 'center':
|
|
194
|
+
style.justifyContent = 'center';
|
|
195
|
+
style.margin = '0 auto';
|
|
196
|
+
break;
|
|
197
|
+
case 'right':
|
|
198
|
+
style.justifyContent = 'flex-end';
|
|
199
|
+
style.margin = 'auto 0 0 auto';
|
|
200
|
+
break;
|
|
201
|
+
default:
|
|
202
|
+
style.justifyContent = 'flex-start';
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
171
207
|
style.objectFit = 'contain';
|
|
172
208
|
|
|
173
209
|
const props = {
|
|
174
210
|
src,
|
|
175
|
-
style
|
|
211
|
+
style,
|
|
212
|
+
alignment,
|
|
213
|
+
alt
|
|
176
214
|
};
|
|
177
215
|
|
|
178
216
|
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
|
};
|