@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.
Files changed (98) hide show
  1. package/CHANGELOG.json +150 -0
  2. package/CHANGELOG.md +421 -0
  3. package/lib/editor.js +385 -172
  4. package/lib/editor.js.map +1 -1
  5. package/lib/index.js +66 -53
  6. package/lib/index.js.map +1 -1
  7. package/lib/parse-html.js.map +1 -1
  8. package/lib/plugins/characters/custom-popper.js +73 -0
  9. package/lib/plugins/characters/custom-popper.js.map +1 -0
  10. package/lib/plugins/characters/index.js +285 -0
  11. package/lib/plugins/characters/index.js.map +1 -0
  12. package/lib/plugins/characters/utils.js +381 -0
  13. package/lib/plugins/characters/utils.js.map +1 -0
  14. package/lib/plugins/image/alt-dialog.js +119 -0
  15. package/lib/plugins/image/alt-dialog.js.map +1 -0
  16. package/lib/plugins/image/component.js +253 -77
  17. package/lib/plugins/image/component.js.map +1 -1
  18. package/lib/plugins/image/image-toolbar.js +95 -61
  19. package/lib/plugins/image/image-toolbar.js.map +1 -1
  20. package/lib/plugins/image/index.js +62 -20
  21. package/lib/plugins/image/index.js.map +1 -1
  22. package/lib/plugins/image/insert-image-handler.js +9 -15
  23. package/lib/plugins/image/insert-image-handler.js.map +1 -1
  24. package/lib/plugins/index.js +20 -12
  25. package/lib/plugins/index.js.map +1 -1
  26. package/lib/plugins/list/index.js +82 -14
  27. package/lib/plugins/list/index.js.map +1 -1
  28. package/lib/plugins/math/index.js +50 -55
  29. package/lib/plugins/math/index.js.map +1 -1
  30. package/lib/plugins/media/index.js +26 -25
  31. package/lib/plugins/media/index.js.map +1 -1
  32. package/lib/plugins/media/media-dialog.js +45 -56
  33. package/lib/plugins/media/media-dialog.js.map +1 -1
  34. package/lib/plugins/media/media-toolbar.js +24 -30
  35. package/lib/plugins/media/media-toolbar.js.map +1 -1
  36. package/lib/plugins/media/media-wrapper.js +28 -35
  37. package/lib/plugins/media/media-wrapper.js.map +1 -1
  38. package/lib/plugins/respArea/drag-in-the-blank/choice.js +68 -46
  39. package/lib/plugins/respArea/drag-in-the-blank/choice.js.map +1 -1
  40. package/lib/plugins/respArea/drag-in-the-blank/index.js +12 -12
  41. package/lib/plugins/respArea/drag-in-the-blank/index.js.map +1 -1
  42. package/lib/plugins/respArea/explicit-constructed-response/index.js +10 -9
  43. package/lib/plugins/respArea/explicit-constructed-response/index.js.map +1 -1
  44. package/lib/plugins/respArea/icons/index.js +11 -11
  45. package/lib/plugins/respArea/icons/index.js.map +1 -1
  46. package/lib/plugins/respArea/index.js +58 -42
  47. package/lib/plugins/respArea/index.js.map +1 -1
  48. package/lib/plugins/respArea/inline-dropdown/index.js +8 -8
  49. package/lib/plugins/respArea/inline-dropdown/index.js.map +1 -1
  50. package/lib/plugins/respArea/utils.js +5 -5
  51. package/lib/plugins/respArea/utils.js.map +1 -1
  52. package/lib/plugins/table/icons/index.js +12 -12
  53. package/lib/plugins/table/icons/index.js.map +1 -1
  54. package/lib/plugins/table/index.js +83 -27
  55. package/lib/plugins/table/index.js.map +1 -1
  56. package/lib/plugins/table/table-toolbar.js +41 -50
  57. package/lib/plugins/table/table-toolbar.js.map +1 -1
  58. package/lib/plugins/toolbar/default-toolbar.js +19 -13
  59. package/lib/plugins/toolbar/default-toolbar.js.map +1 -1
  60. package/lib/plugins/toolbar/done-button.js +5 -5
  61. package/lib/plugins/toolbar/done-button.js.map +1 -1
  62. package/lib/plugins/toolbar/editor-and-toolbar.js +51 -44
  63. package/lib/plugins/toolbar/editor-and-toolbar.js.map +1 -1
  64. package/lib/plugins/toolbar/index.js +5 -5
  65. package/lib/plugins/toolbar/index.js.map +1 -1
  66. package/lib/plugins/toolbar/toolbar-buttons.js +49 -52
  67. package/lib/plugins/toolbar/toolbar-buttons.js.map +1 -1
  68. package/lib/plugins/toolbar/toolbar.js +64 -62
  69. package/lib/plugins/toolbar/toolbar.js.map +1 -1
  70. package/lib/plugins/utils.js +1 -1
  71. package/lib/plugins/utils.js.map +1 -1
  72. package/lib/serialization.js +32 -9
  73. package/lib/serialization.js.map +1 -1
  74. package/lib/theme.js.map +1 -1
  75. package/package.json +7 -6
  76. package/src/editor.jsx +218 -25
  77. package/src/index.jsx +22 -5
  78. package/src/plugins/characters/custom-popper.js +48 -0
  79. package/src/plugins/characters/index.jsx +268 -0
  80. package/src/plugins/characters/utils.js +447 -0
  81. package/src/plugins/image/alt-dialog.jsx +69 -0
  82. package/src/plugins/image/component.jsx +204 -21
  83. package/src/plugins/image/image-toolbar.jsx +68 -22
  84. package/src/plugins/image/index.jsx +47 -9
  85. package/src/plugins/index.jsx +4 -1
  86. package/src/plugins/list/index.jsx +67 -5
  87. package/src/plugins/math/index.jsx +31 -37
  88. package/src/plugins/media/index.jsx +3 -0
  89. package/src/plugins/media/media-dialog.js +1 -1
  90. package/src/plugins/respArea/drag-in-the-blank/choice.jsx +28 -1
  91. package/src/plugins/respArea/explicit-constructed-response/index.jsx +3 -3
  92. package/src/plugins/respArea/index.jsx +50 -31
  93. package/src/plugins/table/index.jsx +63 -14
  94. package/src/plugins/toolbar/default-toolbar.jsx +20 -2
  95. package/src/plugins/toolbar/editor-and-toolbar.jsx +35 -4
  96. package/src/plugins/toolbar/toolbar-buttons.jsx +13 -2
  97. package/src/plugins/toolbar/toolbar.jsx +18 -3
  98. 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
- if (resizePercent) {
50
- update = update.set('width', this.getWidth(resizePercent));
51
- update = update.set('height', this.getHeight(resizePercent));
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'}>&nbsp;</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
- <img src={src} {...attributes} ref={r => (this.img = r)} style={size} />
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'}>&nbsp;</span>
116
276
  ];
@@ -135,7 +295,7 @@ const styles = theme => ({
135
295
  progress: {
136
296
  position: 'absolute',
137
297
  left: '0',
138
- width: '100%',
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: 'inline-block',
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 PercentButton = ({ percent, active, onClick }) => {
11
- const label = `${percent}%`;
13
+ const AlignmentButton = ({ alignment, active, onClick }) => {
12
14
  return (
13
- <MarkButton active={active} onToggle={() => onClick(percent)} label={label}>
14
- {label}
15
+ <MarkButton active={active} onToggle={() => onClick(alignment)} label={alignment}>
16
+ {alignment}
15
17
  </MarkButton>
16
18
  );
17
19
  };
18
20
 
19
- PercentButton.propTypes = {
20
- percent: PropTypes.number.isRequired,
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
- onPercentClick = percent => {
33
- log('[onPercentClick]: percent:', percent);
34
- this.props.onChange(percent);
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, percent } = this.props;
59
+ const { classes, alignment, imageLoaded } = this.props;
60
+
39
61
  return (
40
62
  <div className={classes.holder}>
41
- <PercentButton percent={25} active={percent === 25} onClick={this.onPercentClick} />
42
- <PercentButton percent={50} active={percent === 50} onClick={this.onPercentClick} />
43
- <PercentButton active={percent === 75} percent={75} onClick={this.onPercentClick} />
44
- <PercentButton
45
- percent={100}
46
- active={percent === 100 || !percent}
47
- onClick={this.onPercentClick}
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 percent = node.data.get('resizePercent');
33
-
34
- const onChange = resizePercent => {
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
- resizePercent
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 = () => <ImageToolbar percent={percent || 100} onChange={onChange} />;
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} />;
@@ -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
  };