@pie-lib/editable-html 7.17.4-next.59 → 7.17.4-next.592
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 +135 -0
- package/CHANGELOG.md +421 -0
- package/lib/editor.js +392 -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 +71 -27
- package/lib/plugins/media/index.js.map +1 -1
- package/lib/plugins/media/media-dialog.js +248 -72
- 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 +62 -45
- package/lib/plugins/toolbar/editor-and-toolbar.js.map +1 -1
- package/lib/plugins/toolbar/index.js +6 -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 +226 -26
- 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 +49 -6
- package/src/plugins/media/media-dialog.js +261 -89
- 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 +50 -11
- package/src/plugins/toolbar/index.jsx +1 -0
- package/src/plugins/toolbar/toolbar-buttons.jsx +13 -2
- package/src/plugins/toolbar/toolbar.jsx +18 -3
- package/src/serialization.jsx +19 -3
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
3
|
import debug from 'debug';
|
|
4
|
+
import { color } from '@pie-lib/render-ui';
|
|
4
5
|
import { withStyles } from '@material-ui/core/styles';
|
|
6
|
+
import Button from '@material-ui/core/Button';
|
|
5
7
|
import Dialog from '@material-ui/core/Dialog';
|
|
8
|
+
import MuiTabs from '@material-ui/core/Tabs';
|
|
9
|
+
import MuiTab from '@material-ui/core/Tab';
|
|
6
10
|
import DialogTitle from '@material-ui/core/DialogTitle';
|
|
7
11
|
import DialogContent from '@material-ui/core/DialogContent';
|
|
8
12
|
import DialogContentText from '@material-ui/core/DialogContentText';
|
|
9
13
|
import DialogActions from '@material-ui/core/DialogActions';
|
|
10
|
-
import Button from '@material-ui/core/Button';
|
|
11
14
|
import TextField from '@material-ui/core/TextField';
|
|
15
|
+
import Typography from '@material-ui/core/Typography';
|
|
16
|
+
import IconButton from '@material-ui/core/IconButton';
|
|
17
|
+
import ActionDelete from '@material-ui/icons/Delete';
|
|
12
18
|
|
|
13
19
|
const log = debug('@pie-lib:editable-html:plugins:media:dialog');
|
|
14
20
|
|
|
@@ -75,6 +81,10 @@ export class MediaDialog extends React.Component {
|
|
|
75
81
|
edit: PropTypes.bool,
|
|
76
82
|
disablePortal: PropTypes.bool,
|
|
77
83
|
handleClose: PropTypes.func,
|
|
84
|
+
uploadSoundSupport: PropTypes.shape({
|
|
85
|
+
add: PropTypes.func,
|
|
86
|
+
delete: PropTypes.func
|
|
87
|
+
}),
|
|
78
88
|
type: PropTypes.string,
|
|
79
89
|
src: PropTypes.string,
|
|
80
90
|
url: PropTypes.string,
|
|
@@ -98,7 +108,13 @@ export class MediaDialog extends React.Component {
|
|
|
98
108
|
height: height || 315,
|
|
99
109
|
invalid: false,
|
|
100
110
|
starts: starts || 0,
|
|
101
|
-
width: width || 560
|
|
111
|
+
width: width || 560,
|
|
112
|
+
tabValue: 0,
|
|
113
|
+
fileUpload: {
|
|
114
|
+
uploadIsLoading: false,
|
|
115
|
+
localUrl: '',
|
|
116
|
+
error: null
|
|
117
|
+
}
|
|
102
118
|
};
|
|
103
119
|
}
|
|
104
120
|
|
|
@@ -220,13 +236,20 @@ export class MediaDialog extends React.Component {
|
|
|
220
236
|
|
|
221
237
|
handleDone = val => {
|
|
222
238
|
const { handleClose } = this.props;
|
|
239
|
+
const { tabValue, fileUpload } = this.state;
|
|
240
|
+
const isInsertURL = tabValue === 0;
|
|
223
241
|
|
|
224
242
|
if (!val) {
|
|
243
|
+
if (fileUpload.localUrl) {
|
|
244
|
+
this.handleRemoveFile();
|
|
245
|
+
}
|
|
246
|
+
|
|
225
247
|
handleClose(val);
|
|
226
|
-
} else {
|
|
248
|
+
} else if (isInsertURL) {
|
|
227
249
|
const { ends, height, url, urlToUse, formattedUrl, starts, width } = this.state;
|
|
228
250
|
|
|
229
251
|
handleClose(val, {
|
|
252
|
+
tag: 'iframe',
|
|
230
253
|
ends,
|
|
231
254
|
height,
|
|
232
255
|
starts,
|
|
@@ -235,13 +258,89 @@ export class MediaDialog extends React.Component {
|
|
|
235
258
|
urlToUse,
|
|
236
259
|
src: formattedUrl
|
|
237
260
|
});
|
|
261
|
+
} else {
|
|
262
|
+
handleClose(val, {
|
|
263
|
+
tag: 'audio',
|
|
264
|
+
src: fileUpload.localUrl
|
|
265
|
+
});
|
|
238
266
|
}
|
|
239
267
|
};
|
|
240
268
|
|
|
269
|
+
handleUploadFile = async e => {
|
|
270
|
+
e.preventDefault();
|
|
271
|
+
|
|
272
|
+
this.setState({
|
|
273
|
+
fileUpload: {
|
|
274
|
+
...this.state.fileUpload,
|
|
275
|
+
uploadIsLoading: true
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
const fileChosen = e.target.files[0];
|
|
280
|
+
|
|
281
|
+
const reader = new FileReader();
|
|
282
|
+
|
|
283
|
+
this.setState({
|
|
284
|
+
fileUpload: {
|
|
285
|
+
...this.state.fileUpload,
|
|
286
|
+
uploadIsLoading: true
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
reader.onload = () => {
|
|
291
|
+
const dataURL = reader.result;
|
|
292
|
+
|
|
293
|
+
this.setState({
|
|
294
|
+
fileUpload: {
|
|
295
|
+
...this.state.fileUpload,
|
|
296
|
+
localUrl: dataURL,
|
|
297
|
+
uploadIsLoading: false
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
};
|
|
301
|
+
reader.readAsDataURL(fileChosen);
|
|
302
|
+
|
|
303
|
+
this.props.uploadSoundSupport.add({
|
|
304
|
+
fileChosen,
|
|
305
|
+
done: e => {
|
|
306
|
+
console.log('add done: ', e);
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
handleRemoveFile = async () => {
|
|
312
|
+
this.props.uploadSoundSupport.delete(this.state.fileUpload.localUrl, e => {
|
|
313
|
+
console.log('delete done', e);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
this.setState({
|
|
317
|
+
fileUpload: {
|
|
318
|
+
...this.state.fileUpload,
|
|
319
|
+
localUrl: ''
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
};
|
|
323
|
+
|
|
241
324
|
render() {
|
|
242
|
-
const { classes, open, disablePortal, type, edit } = this.props;
|
|
243
|
-
const {
|
|
325
|
+
const { classes, open, disablePortal, type, edit, uploadSoundSupport } = this.props;
|
|
326
|
+
const {
|
|
327
|
+
ends,
|
|
328
|
+
height,
|
|
329
|
+
invalid,
|
|
330
|
+
starts,
|
|
331
|
+
width,
|
|
332
|
+
url,
|
|
333
|
+
formattedUrl,
|
|
334
|
+
updating,
|
|
335
|
+
tabValue,
|
|
336
|
+
fileUpload
|
|
337
|
+
} = this.state;
|
|
244
338
|
const isYoutube = matchYoutubeUrl(url);
|
|
339
|
+
const isInsertURL = tabValue === 0;
|
|
340
|
+
const isUploadMedia = tabValue === 1;
|
|
341
|
+
const submitIsDisabled = isInsertURL
|
|
342
|
+
? invalid || url === null || url === undefined
|
|
343
|
+
: !fileUpload.localUrl;
|
|
245
344
|
|
|
246
345
|
return (
|
|
247
346
|
<Dialog
|
|
@@ -255,103 +354,154 @@ export class MediaDialog extends React.Component {
|
|
|
255
354
|
>
|
|
256
355
|
<DialogTitle id="form-dialog-title">Insert {typeMap[type]}</DialogTitle>
|
|
257
356
|
<DialogContent>
|
|
258
|
-
<
|
|
259
|
-
{
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
margin="dense"
|
|
266
|
-
id="name"
|
|
267
|
-
label="URL"
|
|
268
|
-
placeholder={`Paste URL of ${type}...`}
|
|
269
|
-
type="text"
|
|
270
|
-
onChange={this.urlChange}
|
|
271
|
-
value={url}
|
|
272
|
-
fullWidth
|
|
273
|
-
/>
|
|
274
|
-
{type === 'video' && (
|
|
275
|
-
<DialogContent
|
|
276
|
-
classes={{
|
|
277
|
-
root: classes.properties
|
|
278
|
-
}}
|
|
279
|
-
>
|
|
280
|
-
<DialogContentText>Video Properties</DialogContentText>
|
|
281
|
-
<TextField
|
|
282
|
-
autoFocus
|
|
283
|
-
margin="dense"
|
|
284
|
-
id="width"
|
|
285
|
-
label="Width"
|
|
286
|
-
type="number"
|
|
287
|
-
placeholder="Width"
|
|
288
|
-
value={width}
|
|
289
|
-
onChange={this.changeHandler('width')}
|
|
290
|
-
/>
|
|
291
|
-
<TextField
|
|
292
|
-
autoFocus
|
|
293
|
-
margin="dense"
|
|
294
|
-
id="height"
|
|
295
|
-
label="Height"
|
|
296
|
-
type="number"
|
|
297
|
-
placeholder="Height"
|
|
298
|
-
value={height}
|
|
299
|
-
onChange={this.changeHandler('height')}
|
|
300
|
-
/>
|
|
301
|
-
</DialogContent>
|
|
302
|
-
)}
|
|
303
|
-
{formattedUrl && (
|
|
304
|
-
<iframe
|
|
305
|
-
width={width}
|
|
306
|
-
height={height}
|
|
307
|
-
src={formattedUrl}
|
|
308
|
-
frameBorder="0"
|
|
309
|
-
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
|
310
|
-
allowFullScreen
|
|
311
|
-
/>
|
|
312
|
-
)}
|
|
313
|
-
{type === 'video' && (formattedUrl || updating) && !invalid && (
|
|
314
|
-
<React.Fragment>
|
|
315
|
-
<DialogContent
|
|
316
|
-
classes={{
|
|
317
|
-
root: classes.properties
|
|
357
|
+
<div>
|
|
358
|
+
<div className={classes.row}>
|
|
359
|
+
<MuiTabs
|
|
360
|
+
indicatorColor="primary"
|
|
361
|
+
value={tabValue}
|
|
362
|
+
onChange={(event, value) => {
|
|
363
|
+
this.setState({ tabValue: value });
|
|
318
364
|
}}
|
|
319
365
|
>
|
|
366
|
+
<MuiTab
|
|
367
|
+
label={type === 'video' ? 'Insert YouTube or Vimeo URL' : 'Insert SoundCloud URL'}
|
|
368
|
+
/>
|
|
369
|
+
{uploadSoundSupport?.add && uploadSoundSupport?.delete && type !== 'video' ? (
|
|
370
|
+
<MuiTab label="Upload file" />
|
|
371
|
+
) : null}
|
|
372
|
+
</MuiTabs>
|
|
373
|
+
</div>
|
|
374
|
+
{isInsertURL && (
|
|
375
|
+
<div>
|
|
320
376
|
<TextField
|
|
321
377
|
autoFocus
|
|
378
|
+
error={invalid}
|
|
379
|
+
helperText={invalid ? 'Invalid URL' : ''}
|
|
322
380
|
margin="dense"
|
|
323
|
-
id="
|
|
324
|
-
label="
|
|
325
|
-
type
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
381
|
+
id="name"
|
|
382
|
+
label="URL"
|
|
383
|
+
placeholder={`Paste URL of ${type}...`}
|
|
384
|
+
type="text"
|
|
385
|
+
onChange={this.urlChange}
|
|
386
|
+
value={url}
|
|
387
|
+
fullWidth
|
|
329
388
|
/>
|
|
330
|
-
{
|
|
331
|
-
<
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
389
|
+
{type === 'video' && (
|
|
390
|
+
<DialogContent
|
|
391
|
+
classes={{
|
|
392
|
+
root: classes.properties
|
|
393
|
+
}}
|
|
394
|
+
>
|
|
395
|
+
<DialogContentText>Video Properties</DialogContentText>
|
|
396
|
+
<TextField
|
|
397
|
+
autoFocus
|
|
398
|
+
margin="dense"
|
|
399
|
+
id="width"
|
|
400
|
+
label="Width"
|
|
401
|
+
type="number"
|
|
402
|
+
placeholder="Width"
|
|
403
|
+
value={width}
|
|
404
|
+
onChange={this.changeHandler('width')}
|
|
405
|
+
/>
|
|
406
|
+
<TextField
|
|
407
|
+
autoFocus
|
|
408
|
+
margin="dense"
|
|
409
|
+
id="height"
|
|
410
|
+
label="Height"
|
|
411
|
+
type="number"
|
|
412
|
+
placeholder="Height"
|
|
413
|
+
value={height}
|
|
414
|
+
onChange={this.changeHandler('height')}
|
|
415
|
+
/>
|
|
416
|
+
</DialogContent>
|
|
417
|
+
)}
|
|
418
|
+
{formattedUrl && (
|
|
419
|
+
<iframe
|
|
420
|
+
width={width}
|
|
421
|
+
height={height}
|
|
422
|
+
src={formattedUrl}
|
|
423
|
+
frameBorder="0"
|
|
424
|
+
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
|
425
|
+
allowFullScreen
|
|
340
426
|
/>
|
|
341
427
|
)}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
428
|
+
{type === 'video' && (formattedUrl || updating) && !invalid && (
|
|
429
|
+
<React.Fragment>
|
|
430
|
+
<DialogContent
|
|
431
|
+
classes={{
|
|
432
|
+
root: classes.properties
|
|
433
|
+
}}
|
|
434
|
+
>
|
|
435
|
+
<TextField
|
|
436
|
+
autoFocus
|
|
437
|
+
margin="dense"
|
|
438
|
+
id="starts"
|
|
439
|
+
label="Starts"
|
|
440
|
+
type="number"
|
|
441
|
+
placeholder="Starts"
|
|
442
|
+
value={starts}
|
|
443
|
+
onChange={this.changeHandler('starts')}
|
|
444
|
+
/>
|
|
445
|
+
{isYoutube && (
|
|
446
|
+
<TextField
|
|
447
|
+
autoFocus
|
|
448
|
+
margin="dense"
|
|
449
|
+
id="ends"
|
|
450
|
+
label="Ends"
|
|
451
|
+
type="number"
|
|
452
|
+
placeholder="Ends"
|
|
453
|
+
value={ends}
|
|
454
|
+
onChange={this.changeHandler('ends')}
|
|
455
|
+
/>
|
|
456
|
+
)}
|
|
457
|
+
</DialogContent>
|
|
458
|
+
</React.Fragment>
|
|
459
|
+
)}
|
|
460
|
+
</div>
|
|
461
|
+
)}
|
|
462
|
+
{isUploadMedia && (
|
|
463
|
+
<div className={classes.uploadInput}>
|
|
464
|
+
{fileUpload.uploadIsLoading ? (
|
|
465
|
+
<Typography variant="subheading">Loading...</Typography>
|
|
466
|
+
) : (
|
|
467
|
+
<div>
|
|
468
|
+
{fileUpload.localUrl ? (
|
|
469
|
+
<div className={classes.row}>
|
|
470
|
+
<audio controls="controls">
|
|
471
|
+
<source type="audio/mp3" src={fileUpload.localUrl} />
|
|
472
|
+
</audio>
|
|
473
|
+
<IconButton
|
|
474
|
+
aria-label="delete"
|
|
475
|
+
className={classes.deleteIcon}
|
|
476
|
+
onClick={this.handleRemoveFile}
|
|
477
|
+
>
|
|
478
|
+
<ActionDelete />
|
|
479
|
+
</IconButton>
|
|
480
|
+
</div>
|
|
481
|
+
) : (
|
|
482
|
+
<input
|
|
483
|
+
accept="audio/*"
|
|
484
|
+
className={classes.input}
|
|
485
|
+
onChange={this.handleUploadFile}
|
|
486
|
+
type="file"
|
|
487
|
+
/>
|
|
488
|
+
)}
|
|
489
|
+
{!!fileUpload.error && (
|
|
490
|
+
<Typography className={classes.error} variant="caption">
|
|
491
|
+
{fileUpload.error}
|
|
492
|
+
</Typography>
|
|
493
|
+
)}
|
|
494
|
+
</div>
|
|
495
|
+
)}
|
|
496
|
+
</div>
|
|
497
|
+
)}
|
|
498
|
+
</div>
|
|
345
499
|
</DialogContent>
|
|
346
500
|
<DialogActions>
|
|
347
501
|
<Button onClick={() => this.handleDone(false)} color="primary">
|
|
348
502
|
Cancel
|
|
349
503
|
</Button>
|
|
350
|
-
<Button
|
|
351
|
-
disabled={invalid || url === null}
|
|
352
|
-
onClick={() => this.handleDone(true)}
|
|
353
|
-
color="primary"
|
|
354
|
-
>
|
|
504
|
+
<Button disabled={submitIsDisabled} onClick={() => this.handleDone(true)} color="primary">
|
|
355
505
|
{edit ? 'Update' : 'Insert'}
|
|
356
506
|
</Button>
|
|
357
507
|
</DialogActions>
|
|
@@ -366,6 +516,28 @@ const styles = () => ({
|
|
|
366
516
|
},
|
|
367
517
|
properties: {
|
|
368
518
|
padding: 0
|
|
519
|
+
},
|
|
520
|
+
row: {
|
|
521
|
+
display: 'flex',
|
|
522
|
+
flexDirection: 'space-between'
|
|
523
|
+
},
|
|
524
|
+
rowItem: {
|
|
525
|
+
marginRight: '12px',
|
|
526
|
+
cursor: 'pointer'
|
|
527
|
+
},
|
|
528
|
+
active: {
|
|
529
|
+
color: color.primary(),
|
|
530
|
+
borderBottom: `2px solid ${color.primary()}`
|
|
531
|
+
},
|
|
532
|
+
uploadInput: {
|
|
533
|
+
marginTop: '12px'
|
|
534
|
+
},
|
|
535
|
+
error: {
|
|
536
|
+
marginTop: '12px',
|
|
537
|
+
color: 'red'
|
|
538
|
+
},
|
|
539
|
+
deleteIcon: {
|
|
540
|
+
marginLeft: '12px'
|
|
369
541
|
}
|
|
370
542
|
});
|
|
371
543
|
|
|
@@ -2,6 +2,7 @@ import React from 'react';
|
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
3
|
import isUndefined from 'lodash/isUndefined';
|
|
4
4
|
import { DragSource, DropTarget } from '@pie-lib/drag';
|
|
5
|
+
import { color } from '@pie-lib/render-ui';
|
|
5
6
|
import { renderMath } from '@pie-lib/math-rendering';
|
|
6
7
|
import { withStyles } from '@material-ui/core/styles';
|
|
7
8
|
import classnames from 'classnames';
|
|
@@ -20,6 +21,9 @@ const useStyles = withStyles(theme => ({
|
|
|
20
21
|
},
|
|
21
22
|
incorrect: {
|
|
22
23
|
border: 'solid 1px red'
|
|
24
|
+
},
|
|
25
|
+
selected: {
|
|
26
|
+
border: `2px solid ${color.primaryDark()} !important`
|
|
23
27
|
}
|
|
24
28
|
}));
|
|
25
29
|
|
|
@@ -30,9 +34,32 @@ export class BlankContent extends React.Component {
|
|
|
30
34
|
isDragging: PropTypes.bool,
|
|
31
35
|
isOver: PropTypes.bool,
|
|
32
36
|
dragItem: PropTypes.object,
|
|
33
|
-
value: PropTypes.object
|
|
37
|
+
value: PropTypes.object,
|
|
38
|
+
classes: PropTypes.object
|
|
34
39
|
};
|
|
35
40
|
|
|
41
|
+
constructor(props) {
|
|
42
|
+
super(props);
|
|
43
|
+
|
|
44
|
+
this.handleClick = this.handleClick.bind(this);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
componentDidMount() {
|
|
48
|
+
document.addEventListener('click', this.handleClick);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
componentWillUnmount() {
|
|
52
|
+
document.removeEventListener('click', this.handleClick);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
handleClick(event) {
|
|
56
|
+
const { classes } = this.props;
|
|
57
|
+
|
|
58
|
+
if (this.elementRef) {
|
|
59
|
+
this.elementRef.className = this.elementRef.contains(event.target) ? classes.selected : '';
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
36
63
|
componentDidUpdate() {
|
|
37
64
|
if (this.elementRef) {
|
|
38
65
|
renderMath(this.elementRef);
|
|
@@ -2,7 +2,7 @@ import React from 'react';
|
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
3
|
|
|
4
4
|
const ExplicitConstructedResponse = props => {
|
|
5
|
-
const { attributes, value } = props;
|
|
5
|
+
const { attributes, value, error } = props;
|
|
6
6
|
|
|
7
7
|
return (
|
|
8
8
|
<span
|
|
@@ -23,7 +23,7 @@ const ExplicitConstructedResponse = props => {
|
|
|
23
23
|
minHeight: '36px',
|
|
24
24
|
height: '36px',
|
|
25
25
|
background: '#FFF',
|
|
26
|
-
border:
|
|
26
|
+
border: `1px solid ${error ? 'red' : '#C0C3CF'}`,
|
|
27
27
|
boxSizing: 'border-box',
|
|
28
28
|
borderRadius: '3px',
|
|
29
29
|
overflow: 'hidden',
|
|
@@ -39,7 +39,7 @@ const ExplicitConstructedResponse = props => {
|
|
|
39
39
|
|
|
40
40
|
ExplicitConstructedResponse.propTypes = {
|
|
41
41
|
attributes: PropTypes.object,
|
|
42
|
-
value: PropTypes.
|
|
42
|
+
value: PropTypes.string
|
|
43
43
|
};
|
|
44
44
|
|
|
45
45
|
export default ExplicitConstructedResponse;
|
|
@@ -11,9 +11,16 @@ import { ToolbarIcon } from './icons';
|
|
|
11
11
|
const log = debug('@pie-lib:editable-html:plugins:respArea');
|
|
12
12
|
|
|
13
13
|
const lastIndexMap = {};
|
|
14
|
-
const
|
|
14
|
+
const elTypesMap = {
|
|
15
|
+
'inline-dropdown': 'inline_dropdown',
|
|
16
|
+
'explicit-constructed-response': 'explicit_constructed_response',
|
|
17
|
+
'drag-in-the-blank': 'drag_in_the_blank'
|
|
18
|
+
};
|
|
19
|
+
const elTypesArray = Object.values(elTypesMap);
|
|
15
20
|
|
|
16
21
|
export default function ResponseAreaPlugin(opts) {
|
|
22
|
+
const isOfCurrentType = d => d.type === opts.type || d.type === elTypesMap[opts.type];
|
|
23
|
+
|
|
17
24
|
const toolbar = {
|
|
18
25
|
icon: <ToolbarIcon />,
|
|
19
26
|
buttonStyles: {
|
|
@@ -22,6 +29,12 @@ export default function ResponseAreaPlugin(opts) {
|
|
|
22
29
|
onClick: (value, onChange) => {
|
|
23
30
|
log('[toolbar] onClick');
|
|
24
31
|
const change = value.change();
|
|
32
|
+
const currentRespAreaList = change.value.document.filterDescendants(isOfCurrentType);
|
|
33
|
+
|
|
34
|
+
if (currentRespAreaList.size >= opts.maxResponseAreas) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
25
38
|
const type = opts.type.replace(/-/g, '_');
|
|
26
39
|
const prevIndex = lastIndexMap[type];
|
|
27
40
|
const newIndex = prevIndex === 0 ? prevIndex : prevIndex + 1;
|
|
@@ -35,6 +48,10 @@ export default function ResponseAreaPlugin(opts) {
|
|
|
35
48
|
} else {
|
|
36
49
|
// If the markup is empty and there's no focus
|
|
37
50
|
const lastText = value.document.getLastText();
|
|
51
|
+
|
|
52
|
+
if (!lastText) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
38
55
|
const parentNode = value.document.getParent(lastText.key);
|
|
39
56
|
|
|
40
57
|
if (parentNode) {
|
|
@@ -66,7 +83,7 @@ export default function ResponseAreaPlugin(opts) {
|
|
|
66
83
|
name: 'response_area',
|
|
67
84
|
toolbar,
|
|
68
85
|
filterPlugins: (node, plugins) => {
|
|
69
|
-
if (node.type === 'explicit_constructed_response') {
|
|
86
|
+
if (node.type === 'explicit_constructed_response' || node.type === 'drag_in_the_blank') {
|
|
70
87
|
return [];
|
|
71
88
|
}
|
|
72
89
|
|
|
@@ -84,8 +101,19 @@ export default function ResponseAreaPlugin(opts) {
|
|
|
84
101
|
|
|
85
102
|
if (n.type === 'explicit_constructed_response') {
|
|
86
103
|
const data = n.data.toJSON();
|
|
104
|
+
let error;
|
|
87
105
|
|
|
88
|
-
|
|
106
|
+
if (opts.error) {
|
|
107
|
+
error = opts.error();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<ExplicitConstructedResponse
|
|
112
|
+
attributes={attributes}
|
|
113
|
+
value={data.value}
|
|
114
|
+
error={error && error[data.index] && error[data.index][0]}
|
|
115
|
+
/>
|
|
116
|
+
);
|
|
89
117
|
}
|
|
90
118
|
|
|
91
119
|
if (n.type === 'drag_in_the_blank') {
|
|
@@ -102,7 +130,7 @@ export default function ResponseAreaPlugin(opts) {
|
|
|
102
130
|
return <InlineDropdown attributes={attributes} selectedItem={data.value} />;
|
|
103
131
|
}
|
|
104
132
|
},
|
|
105
|
-
onChange(change) {
|
|
133
|
+
onChange(change, editor) {
|
|
106
134
|
const type = opts.type.replace(/-/g, '_');
|
|
107
135
|
|
|
108
136
|
if (isUndefined(lastIndexMap[type])) {
|
|
@@ -118,41 +146,32 @@ export default function ResponseAreaPlugin(opts) {
|
|
|
118
146
|
}
|
|
119
147
|
});
|
|
120
148
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if (node.object !== 'document') {
|
|
149
|
+
|
|
150
|
+
if (!editor.value) {
|
|
124
151
|
return;
|
|
125
152
|
}
|
|
126
153
|
|
|
127
|
-
const
|
|
154
|
+
const currentRespAreaList = change.value.document.filterDescendants(isOfCurrentType);
|
|
155
|
+
const oldRespAreaList = editor.value.document.filterDescendants(isOfCurrentType);
|
|
128
156
|
|
|
129
|
-
|
|
157
|
+
if (currentRespAreaList.size >= opts.maxResponseAreas) {
|
|
158
|
+
toolbar.disabled = true;
|
|
159
|
+
} else {
|
|
160
|
+
toolbar.disabled = false;
|
|
161
|
+
}
|
|
130
162
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
163
|
+
const arrayToFilter =
|
|
164
|
+
oldRespAreaList.size > currentRespAreaList.size ? oldRespAreaList : currentRespAreaList;
|
|
165
|
+
const arrayToUseForFilter =
|
|
166
|
+
arrayToFilter === oldRespAreaList ? currentRespAreaList : oldRespAreaList;
|
|
134
167
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
key: prevText.key
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
});
|
|
168
|
+
const elementsWithChangedStatus = arrayToFilter.filter(
|
|
169
|
+
d => !arrayToUseForFilter.find(e => e.data.get('index') === d.data.get('index'))
|
|
170
|
+
);
|
|
142
171
|
|
|
143
|
-
if (
|
|
144
|
-
|
|
172
|
+
if (elementsWithChangedStatus.size && oldRespAreaList.size > currentRespAreaList.size) {
|
|
173
|
+
opts.onHandleAreaChange(elementsWithChangedStatus);
|
|
145
174
|
}
|
|
146
|
-
|
|
147
|
-
return change => {
|
|
148
|
-
change.withoutNormalization(() => {
|
|
149
|
-
addSpacesArray.forEach(({ key, nr }) => {
|
|
150
|
-
const node = change.value.document.getNode(key);
|
|
151
|
-
|
|
152
|
-
change.insertTextByKey(key, node.text.length, '\u00A0'.repeat(nr));
|
|
153
|
-
});
|
|
154
|
-
});
|
|
155
|
-
};
|
|
156
175
|
},
|
|
157
176
|
onDrop(event, change, editor) {
|
|
158
177
|
const closestEl = event.target.closest('[data-key]');
|