@pie-lib/editable-html 11.1.1 → 11.2.1-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/CHANGELOG.md +43 -167
  2. package/NEXT.CHANGELOG.json +1 -0
  3. package/package.json +10 -6
  4. package/src/__tests__/editor.test.jsx +363 -0
  5. package/src/__tests__/serialization.test.js +291 -0
  6. package/src/__tests__/utils.js +36 -0
  7. package/src/block-tags.js +17 -0
  8. package/src/constants.js +7 -0
  9. package/src/editor.jsx +303 -49
  10. package/src/index.jsx +19 -10
  11. package/src/plugins/characters/index.jsx +11 -3
  12. package/src/plugins/characters/utils.js +12 -12
  13. package/src/plugins/css/icons/index.jsx +17 -0
  14. package/src/plugins/css/index.jsx +346 -0
  15. package/src/plugins/customPlugin/index.jsx +85 -0
  16. package/src/plugins/html/index.jsx +9 -6
  17. package/src/plugins/image/__tests__/__snapshots__/component.test.jsx.snap +51 -0
  18. package/src/plugins/image/__tests__/__snapshots__/image-toolbar-logic.test.jsx.snap +27 -0
  19. package/src/plugins/image/__tests__/__snapshots__/image-toolbar.test.jsx.snap +44 -0
  20. package/src/plugins/image/__tests__/component.test.jsx +41 -0
  21. package/src/plugins/image/__tests__/image-toolbar-logic.test.jsx +42 -0
  22. package/src/plugins/image/__tests__/image-toolbar.test.jsx +11 -0
  23. package/src/plugins/image/__tests__/index.test.js +95 -0
  24. package/src/plugins/image/__tests__/insert-image-handler.test.js +113 -0
  25. package/src/plugins/image/__tests__/mock-change.js +15 -0
  26. package/src/plugins/image/index.jsx +2 -1
  27. package/src/plugins/image/insert-image-handler.js +13 -6
  28. package/src/plugins/index.jsx +248 -5
  29. package/src/plugins/list/__tests__/index.test.js +54 -0
  30. package/src/plugins/list/index.jsx +130 -0
  31. package/src/plugins/math/__tests__/__snapshots__/index.test.jsx.snap +48 -0
  32. package/src/plugins/math/__tests__/index.test.jsx +245 -0
  33. package/src/plugins/math/index.jsx +87 -56
  34. package/src/plugins/media/__tests__/index.test.js +75 -0
  35. package/src/plugins/media/index.jsx +3 -2
  36. package/src/plugins/media/media-dialog.js +106 -57
  37. package/src/plugins/rendering/index.js +31 -0
  38. package/src/plugins/respArea/drag-in-the-blank/choice.jsx +4 -1
  39. package/src/plugins/respArea/explicit-constructed-response/index.jsx +10 -8
  40. package/src/plugins/respArea/index.jsx +53 -7
  41. package/src/plugins/respArea/inline-dropdown/index.jsx +13 -6
  42. package/src/plugins/respArea/math-templated/index.jsx +104 -0
  43. package/src/plugins/respArea/utils.jsx +11 -0
  44. package/src/plugins/table/CustomTablePlugin.js +113 -0
  45. package/src/plugins/table/__tests__/__snapshots__/table-toolbar.test.jsx.snap +44 -0
  46. package/src/plugins/table/__tests__/index.test.jsx +401 -0
  47. package/src/plugins/table/__tests__/table-toolbar.test.jsx +42 -0
  48. package/src/plugins/table/index.jsx +46 -59
  49. package/src/plugins/table/table-toolbar.jsx +39 -2
  50. package/src/plugins/textAlign/icons/index.jsx +139 -0
  51. package/src/plugins/textAlign/index.jsx +23 -0
  52. package/src/plugins/toolbar/__tests__/__snapshots__/default-toolbar.test.jsx.snap +923 -0
  53. package/src/plugins/toolbar/__tests__/__snapshots__/editor-and-toolbar.test.jsx.snap +20 -0
  54. package/src/plugins/toolbar/__tests__/__snapshots__/toolbar-buttons.test.jsx.snap +36 -0
  55. package/src/plugins/toolbar/__tests__/__snapshots__/toolbar.test.jsx.snap +46 -0
  56. package/src/plugins/toolbar/__tests__/default-toolbar.test.jsx +94 -0
  57. package/src/plugins/toolbar/__tests__/editor-and-toolbar.test.jsx +37 -0
  58. package/src/plugins/toolbar/__tests__/toolbar-buttons.test.jsx +51 -0
  59. package/src/plugins/toolbar/__tests__/toolbar.test.jsx +106 -0
  60. package/src/plugins/toolbar/default-toolbar.jsx +82 -20
  61. package/src/plugins/toolbar/done-button.jsx +3 -1
  62. package/src/plugins/toolbar/editor-and-toolbar.jsx +18 -13
  63. package/src/plugins/toolbar/toolbar-buttons.jsx +52 -11
  64. package/src/plugins/toolbar/toolbar.jsx +31 -8
  65. package/src/serialization.jsx +213 -38
  66. package/README.md +0 -45
  67. package/deploy.sh +0 -16
  68. package/lib/editor.js +0 -1094
  69. package/lib/editor.js.map +0 -1
  70. package/lib/index.js +0 -253
  71. package/lib/index.js.map +0 -1
  72. package/lib/parse-html.js +0 -16
  73. package/lib/parse-html.js.map +0 -1
  74. package/lib/plugins/characters/custom-popper.js +0 -73
  75. package/lib/plugins/characters/custom-popper.js.map +0 -1
  76. package/lib/plugins/characters/index.js +0 -300
  77. package/lib/plugins/characters/index.js.map +0 -1
  78. package/lib/plugins/characters/utils.js +0 -381
  79. package/lib/plugins/characters/utils.js.map +0 -1
  80. package/lib/plugins/html/icons/index.js +0 -38
  81. package/lib/plugins/html/icons/index.js.map +0 -1
  82. package/lib/plugins/html/index.js +0 -76
  83. package/lib/plugins/html/index.js.map +0 -1
  84. package/lib/plugins/image/alt-dialog.js +0 -129
  85. package/lib/plugins/image/alt-dialog.js.map +0 -1
  86. package/lib/plugins/image/component.js +0 -419
  87. package/lib/plugins/image/component.js.map +0 -1
  88. package/lib/plugins/image/image-toolbar.js +0 -177
  89. package/lib/plugins/image/image-toolbar.js.map +0 -1
  90. package/lib/plugins/image/index.js +0 -262
  91. package/lib/plugins/image/index.js.map +0 -1
  92. package/lib/plugins/image/insert-image-handler.js +0 -152
  93. package/lib/plugins/image/insert-image-handler.js.map +0 -1
  94. package/lib/plugins/index.js +0 -143
  95. package/lib/plugins/index.js.map +0 -1
  96. package/lib/plugins/list/index.js +0 -204
  97. package/lib/plugins/list/index.js.map +0 -1
  98. package/lib/plugins/math/index.js +0 -419
  99. package/lib/plugins/math/index.js.map +0 -1
  100. package/lib/plugins/media/index.js +0 -384
  101. package/lib/plugins/media/index.js.map +0 -1
  102. package/lib/plugins/media/media-dialog.js +0 -668
  103. package/lib/plugins/media/media-dialog.js.map +0 -1
  104. package/lib/plugins/media/media-toolbar.js +0 -101
  105. package/lib/plugins/media/media-toolbar.js.map +0 -1
  106. package/lib/plugins/media/media-wrapper.js +0 -93
  107. package/lib/plugins/media/media-wrapper.js.map +0 -1
  108. package/lib/plugins/respArea/drag-in-the-blank/choice.js +0 -251
  109. package/lib/plugins/respArea/drag-in-the-blank/choice.js.map +0 -1
  110. package/lib/plugins/respArea/drag-in-the-blank/index.js +0 -97
  111. package/lib/plugins/respArea/drag-in-the-blank/index.js.map +0 -1
  112. package/lib/plugins/respArea/explicit-constructed-response/index.js +0 -55
  113. package/lib/plugins/respArea/explicit-constructed-response/index.js.map +0 -1
  114. package/lib/plugins/respArea/icons/index.js +0 -95
  115. package/lib/plugins/respArea/icons/index.js.map +0 -1
  116. package/lib/plugins/respArea/index.js +0 -293
  117. package/lib/plugins/respArea/index.js.map +0 -1
  118. package/lib/plugins/respArea/inline-dropdown/index.js +0 -70
  119. package/lib/plugins/respArea/inline-dropdown/index.js.map +0 -1
  120. package/lib/plugins/respArea/utils.js +0 -110
  121. package/lib/plugins/respArea/utils.js.map +0 -1
  122. package/lib/plugins/table/icons/index.js +0 -69
  123. package/lib/plugins/table/icons/index.js.map +0 -1
  124. package/lib/plugins/table/index.js +0 -499
  125. package/lib/plugins/table/index.js.map +0 -1
  126. package/lib/plugins/table/table-toolbar.js +0 -158
  127. package/lib/plugins/table/table-toolbar.js.map +0 -1
  128. package/lib/plugins/toolbar/default-toolbar.js +0 -174
  129. package/lib/plugins/toolbar/default-toolbar.js.map +0 -1
  130. package/lib/plugins/toolbar/done-button.js +0 -50
  131. package/lib/plugins/toolbar/done-button.js.map +0 -1
  132. package/lib/plugins/toolbar/editor-and-toolbar.js +0 -287
  133. package/lib/plugins/toolbar/editor-and-toolbar.js.map +0 -1
  134. package/lib/plugins/toolbar/index.js +0 -34
  135. package/lib/plugins/toolbar/index.js.map +0 -1
  136. package/lib/plugins/toolbar/toolbar-buttons.js +0 -161
  137. package/lib/plugins/toolbar/toolbar-buttons.js.map +0 -1
  138. package/lib/plugins/toolbar/toolbar.js +0 -352
  139. package/lib/plugins/toolbar/toolbar.js.map +0 -1
  140. package/lib/plugins/utils.js +0 -62
  141. package/lib/plugins/utils.js.map +0 -1
  142. package/lib/serialization.js +0 -488
  143. package/lib/serialization.js.map +0 -1
  144. package/lib/theme.js +0 -9
  145. package/lib/theme.js.map +0 -1
  146. package/playground/image/data.js +0 -59
  147. package/playground/image/index.html +0 -22
  148. package/playground/image/index.jsx +0 -81
  149. package/playground/index.html +0 -25
  150. package/playground/mathquill/index.html +0 -22
  151. package/playground/mathquill/index.jsx +0 -155
  152. package/playground/package.json +0 -15
  153. package/playground/prod-test/index.html +0 -22
  154. package/playground/prod-test/index.jsx +0 -28
  155. package/playground/schema-override/data.js +0 -29
  156. package/playground/schema-override/image-plugin.jsx +0 -41
  157. package/playground/schema-override/index.html +0 -21
  158. package/playground/schema-override/index.jsx +0 -97
  159. package/playground/serialization/data.js +0 -29
  160. package/playground/serialization/image-plugin.jsx +0 -41
  161. package/playground/serialization/index.html +0 -22
  162. package/playground/serialization/index.jsx +0 -12
  163. package/playground/static.json +0 -3
  164. package/playground/table-examples.html +0 -70
  165. package/playground/webpack.config.js +0 -42
  166. package/static.json +0 -1
@@ -36,6 +36,9 @@ const matchVimeoUrl = (url) =>
36
36
  url,
37
37
  );
38
38
 
39
+ const matchDriveUrl = (url) =>
40
+ url && /^https:\/\/drive\.google\.com\/(?:file\/d\/|drive\/(?:u\/\d+\/)?folders\/|uc\?id=)([a-zA-Z0-9_-]+)/.test(url);
41
+
39
42
  const matchSoundCloudUrl = (url) => {
40
43
  if (!url) {
41
44
  return false;
@@ -74,6 +77,11 @@ const typeMap = {
74
77
  audio: 'Audio',
75
78
  };
76
79
 
80
+ const tabsTypeMap = {
81
+ uploadFile: 'upload-file',
82
+ insertUrl: 'insert-url',
83
+ };
84
+
77
85
  export class MediaDialog extends React.Component {
78
86
  static propTypes = {
79
87
  classes: PropTypes.object.isRequired,
@@ -98,7 +106,8 @@ export class MediaDialog extends React.Component {
98
106
  constructor(props) {
99
107
  super(props);
100
108
 
101
- const { src, starts, ends, height, url, urlToUse, width } = props;
109
+ const { ends, height, src, starts, type, uploadSoundSupport, url, urlToUse, width } = props;
110
+ const showUploadFile = uploadSoundSupport?.add && uploadSoundSupport?.delete && type !== 'video';
102
111
 
103
112
  this.state = {
104
113
  ends: ends || 0,
@@ -109,12 +118,14 @@ export class MediaDialog extends React.Component {
109
118
  invalid: false,
110
119
  starts: starts || 0,
111
120
  width: width || 560,
112
- tabValue: 0,
121
+ // default selected tab should be upload file if available
122
+ tabValue: showUploadFile ? tabsTypeMap.uploadFile : tabsTypeMap.insertUrl,
113
123
  fileUpload: {
114
124
  error: null,
115
125
  loading: false,
116
126
  scheduled: false,
117
127
  url: '',
128
+ mimeType: '',
118
129
  },
119
130
  };
120
131
  }
@@ -173,57 +184,79 @@ export class MediaDialog extends React.Component {
173
184
 
174
185
  handleStateChange = (newState) => this.setState(newState, this.formatUrl);
175
186
 
176
- urlChange = (e) => {
177
- const { value } = e.target || {};
178
- const { type } = this.props;
187
+ checkAudio = (value) => {
188
+ if (matchSoundCloudUrl(value)) {
189
+ makeApiRequest(value)
190
+ .then((urlToUse) => {
191
+ this.handleStateChange({
192
+ urlToUse,
193
+ invalid: !urlToUse,
194
+ url: value,
195
+ });
196
+ })
197
+ .catch(log);
198
+ }
199
+ };
179
200
 
180
- if (type && type === 'audio') {
181
- if (matchSoundCloudUrl(value)) {
182
- makeApiRequest(value)
183
- .then((urlToUse) => {
184
- this.handleStateChange({
185
- urlToUse,
186
- invalid: !urlToUse,
187
- url: value,
188
- });
189
- })
190
- .catch(log);
191
-
192
- return;
193
- }
194
- } else if (type && type === 'video') {
195
- if (matchYoutubeUrl(value)) {
196
- const regExp = /^.*(youtu\.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*/;
197
- const match = value.match(regExp);
198
- const id = match[2];
199
- const urlToUse = `https://youtube.com/embed/${id}`;
200
-
201
- log('is youtube');
202
-
203
- this.handleStateChange({
204
- urlToUse,
205
- url: value,
206
- invalid: false,
207
- });
201
+ checkVideo = (value) => {
202
+ if (matchYoutubeUrl(value)) {
203
+ const regExp = /^.*(youtu\.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*/;
204
+ const match = value.match(regExp);
205
+ const id = match[2];
206
+ const urlToUse = `https://youtube.com/embed/${id}`;
208
207
 
209
- return;
210
- }
208
+ log('is youtube');
211
209
 
212
- if (matchVimeoUrl(value)) {
213
- const id = value.replace(/.*vimeo.com\/(.*)/g, '$1');
214
- const urlToUse = `https://player.vimeo.com/video/${id}`;
210
+ this.handleStateChange({
211
+ urlToUse,
212
+ url: value,
213
+ invalid: false,
214
+ });
215
+ }
215
216
 
216
- log('is vimeo');
217
+ if (matchVimeoUrl(value)) {
218
+ const id = value.replace(/.*vimeo.com\/(.*)/g, '$1');
219
+ const urlToUse = `https://player.vimeo.com/video/${id}`;
217
220
 
218
- this.handleStateChange({
219
- urlToUse,
220
- url: value,
221
- ends: null,
222
- invalid: false,
223
- });
221
+ log('is vimeo');
224
222
 
225
- return;
226
- }
223
+ this.handleStateChange({
224
+ urlToUse,
225
+ url: value,
226
+ ends: null,
227
+ invalid: false,
228
+ });
229
+ }
230
+
231
+ if (matchDriveUrl(value)) {
232
+ // https://drive.google.com/file/d/11QkK_gUO068amNvZBm9cxZpKX74WYb8q/view
233
+ const id = value.replace(
234
+ /^https:\/\/drive\.google\.com\/(?:file\/d\/|drive\/(?:u\/\d+\/)?folders\/|uc\?id=)([a-zA-Z0-9_-]+)\/.*/,
235
+ '$1',
236
+ );
237
+ const urlToUse = `https://drive.google.com/file/d/${id}/preview`;
238
+
239
+ log('is google drive');
240
+
241
+ this.handleStateChange({
242
+ urlToUse,
243
+ url: value,
244
+ ends: null,
245
+ invalid: false,
246
+ });
247
+ }
248
+ };
249
+
250
+ urlChange = (e) => {
251
+ const { value } = e.target || {};
252
+ const { type } = this.props;
253
+
254
+ if (type && type === 'audio') {
255
+ this.checkAudio();
256
+ return;
257
+ } else if (type && type === 'video') {
258
+ this.checkVideo(value);
259
+ return;
227
260
  }
228
261
 
229
262
  this.handleStateChange({
@@ -238,7 +271,7 @@ export class MediaDialog extends React.Component {
238
271
  handleDone = (val) => {
239
272
  const { handleClose } = this.props;
240
273
  const { tabValue, fileUpload } = this.state;
241
- const isInsertURL = tabValue === 0;
274
+ const isInsertURL = tabValue === tabsTypeMap.insertUrl;
242
275
 
243
276
  if (!val) {
244
277
  if (fileUpload.url) {
@@ -303,6 +336,7 @@ export class MediaDialog extends React.Component {
303
336
  fileUpload: {
304
337
  ...this.state.fileUpload,
305
338
  url: dataURL,
339
+ mimeType: fileChosen.type,
306
340
  },
307
341
  });
308
342
  };
@@ -364,19 +398,33 @@ export class MediaDialog extends React.Component {
364
398
  ...this.state.fileUpload,
365
399
  loading: false,
366
400
  url: '',
401
+ mimeType: '',
367
402
  },
368
403
  });
369
404
  };
370
405
 
371
406
  render() {
372
407
  const { classes, open, disablePortal, type, edit, uploadSoundSupport } = this.props;
373
- const { ends, height, invalid, starts, width, url, formattedUrl, updating, tabValue, fileUpload } = this.state;
408
+ const {
409
+ ends,
410
+ height,
411
+ invalid,
412
+ starts,
413
+ width,
414
+ url,
415
+ mimeType,
416
+ formattedUrl,
417
+ updating,
418
+ tabValue,
419
+ fileUpload,
420
+ } = this.state;
374
421
  const isYoutube = matchYoutubeUrl(url);
375
- const isInsertURL = tabValue === 0;
376
- const isUploadMedia = tabValue === 1;
422
+ const isInsertURL = tabValue === tabsTypeMap.insertUrl;
423
+ const isUploadMedia = tabValue === tabsTypeMap.uploadFile;
377
424
  const submitIsDisabled = isInsertURL
378
425
  ? invalid || url === null || url === undefined
379
426
  : !fileUpload.url || fileUpload.scheduled;
427
+ const showUploadFile = uploadSoundSupport?.add && uploadSoundSupport?.delete && type !== 'video';
380
428
 
381
429
  return (
382
430
  <Dialog
@@ -399,10 +447,11 @@ export class MediaDialog extends React.Component {
399
447
  this.setState({ tabValue: value });
400
448
  }}
401
449
  >
402
- <MuiTab label={type === 'video' ? 'Insert YouTube or Vimeo URL' : 'Insert SoundCloud URL'} />
403
- {uploadSoundSupport?.add && uploadSoundSupport?.delete && type !== 'video' ? (
404
- <MuiTab label="Upload file" />
405
- ) : null}
450
+ {showUploadFile ? <MuiTab value={tabsTypeMap.uploadFile} label="Upload file" /> : null}
451
+ <MuiTab
452
+ value={tabsTypeMap.insertUrl}
453
+ label={type === 'video' ? 'Insert YouTube, Vimeo, or Google Drive URL' : 'Insert SoundCloud URL'}
454
+ />
406
455
  </MuiTabs>
407
456
  </div>
408
457
  {isInsertURL && (
@@ -499,8 +548,8 @@ export class MediaDialog extends React.Component {
499
548
  {fileUpload.url ? (
500
549
  <>
501
550
  <div className={classes.row}>
502
- <audio controls="controls">
503
- <source type="audio/mp3" src={fileUpload.url} />
551
+ <audio controls="controls" controlsList="nodownload">
552
+ <source type={mimeType} src={fileUpload.url} />
504
553
  </audio>
505
554
  <IconButton aria-label="delete" className={classes.deleteIcon} onClick={this.handleRemoveFile}>
506
555
  <ActionDelete />
@@ -0,0 +1,31 @@
1
+ import React from 'react';
2
+
3
+ /**
4
+ * Plugin to render the normal divs and spans with attributes (if they are provided)
5
+ */
6
+ export default () => {
7
+ return {
8
+ name: 'renderingPlugin',
9
+ renderNode: (props) => {
10
+ const { attributes, children, node } = props;
11
+
12
+ if (node.object !== 'block' && node.object !== 'inline') {
13
+ return;
14
+ }
15
+
16
+ const Tag = node.object === 'block' ? 'div' : 'span';
17
+ const style = { position: 'relative' };
18
+ const extraAttributes = node.data.get('attributes');
19
+
20
+ return React.createElement(
21
+ Tag,
22
+ {
23
+ ...attributes,
24
+ ...extraAttributes,
25
+ style: style,
26
+ },
27
+ children,
28
+ );
29
+ },
30
+ };
31
+ };
@@ -7,11 +7,14 @@ import { renderMath } from '@pie-lib/math-rendering';
7
7
  import { withStyles } from '@material-ui/core/styles';
8
8
  import classnames from 'classnames';
9
9
 
10
- import { GripIcon } from '../icons';
10
+ import { GripIcon } from '@pie-lib/icons';
11
11
 
12
12
  const useStyles = withStyles((theme) => ({
13
13
  content: {
14
14
  border: `solid 0px ${theme.palette.primary.main}`,
15
+ '& mjx-frac': {
16
+ fontSize: '120% !important',
17
+ },
15
18
  },
16
19
  chip: {
17
20
  minWidth: '90px',
@@ -9,25 +9,26 @@ const ExplicitConstructedResponse = (props) => {
9
9
  {...attributes}
10
10
  style={{
11
11
  display: 'inline-flex',
12
- minHeight: '50px',
12
+ visibility: props.isFocused ? 'hidden' : 'visible',
13
+ minHeight: '55px',
13
14
  minWidth: '178px',
14
15
  position: 'relative',
15
- margin: '0 5px',
16
16
  cursor: 'pointer',
17
17
  }}
18
18
  >
19
19
  <div
20
20
  style={{
21
21
  display: 'inline-flex',
22
- minWidth: '178px',
23
- minHeight: '36px',
24
- height: '36px',
25
- background: '#FFF',
22
+ width: '100%',
23
+ minHeight: '46px',
24
+ height: '46px',
25
+ backgroundColor: '#FFF',
26
26
  border: `1px solid ${error ? 'red' : '#C0C3CF'}`,
27
27
  boxSizing: 'border-box',
28
- borderRadius: '3px',
28
+ borderRadius: '4px',
29
29
  overflow: 'hidden',
30
- padding: '8px',
30
+ padding: '12px 21px',
31
+ marginLeft: '4px',
31
32
  }}
32
33
  dangerouslySetInnerHTML={{
33
34
  __html: value || '<div>&nbsp;</div>',
@@ -41,6 +42,7 @@ ExplicitConstructedResponse.propTypes = {
41
42
  attributes: PropTypes.object,
42
43
  error: PropTypes.any,
43
44
  value: PropTypes.string,
45
+ isFocused: PropTypes.bool,
44
46
  };
45
47
 
46
48
  export default ExplicitConstructedResponse;
@@ -5,6 +5,7 @@ import isUndefined from 'lodash/isUndefined';
5
5
  import InlineDropdown from './inline-dropdown';
6
6
  import DragInTheBlank from './drag-in-the-blank';
7
7
  import ExplicitConstructedResponse from './explicit-constructed-response';
8
+ import MathTemplated from './math-templated';
8
9
  import { getDefaultElement } from './utils';
9
10
  import { ToolbarIcon } from './icons';
10
11
 
@@ -14,6 +15,7 @@ const lastIndexMap = {};
14
15
  const elTypesMap = {
15
16
  'inline-dropdown': 'inline_dropdown',
16
17
  'explicit-constructed-response': 'explicit_constructed_response',
18
+ 'math-templated': 'math_templated',
17
19
  'drag-in-the-blank': 'drag_in_the_blank',
18
20
  };
19
21
  const elTypesArray = Object.values(elTypesMap);
@@ -70,6 +72,13 @@ export default function ResponseAreaPlugin(opts) {
70
72
  change.moveFocusTo(nextText.key, 0).moveAnchorTo(nextText.key, 0);
71
73
  }
72
74
  }
75
+ if (newInline.type === 'math_templated') {
76
+ const nextText = change.value.document.getNextText(newInline.key);
77
+
78
+ if (nextText) {
79
+ change.moveFocusTo(nextText.key, 0).moveAnchorTo(nextText.key, 0);
80
+ }
81
+ }
73
82
 
74
83
  onChange(change);
75
84
  }
@@ -83,7 +92,11 @@ export default function ResponseAreaPlugin(opts) {
83
92
  name: 'response_area',
84
93
  toolbar,
85
94
  filterPlugins: (node, plugins) => {
86
- if (node.type === 'explicit_constructed_response' || node.type === 'drag_in_the_blank') {
95
+ if (
96
+ node.type === 'explicit_constructed_response' ||
97
+ node.type === 'math_templated' ||
98
+ node.type === 'drag_in_the_blank'
99
+ ) {
87
100
  return [];
88
101
  }
89
102
 
@@ -97,7 +110,7 @@ export default function ResponseAreaPlugin(opts) {
97
110
  onChange(change);
98
111
  },
99
112
  renderNode(props) {
100
- const { attributes, node: n } = props;
113
+ const { attributes, node: n, isFocused } = props;
101
114
 
102
115
  if (n.type === 'explicit_constructed_response') {
103
116
  const data = n.data.toJSON();
@@ -110,12 +123,34 @@ export default function ResponseAreaPlugin(opts) {
110
123
  return (
111
124
  <ExplicitConstructedResponse
112
125
  attributes={attributes}
126
+ isFocused={isFocused}
113
127
  value={data.value}
114
128
  error={error && error[data.index] && error[data.index][0]}
115
129
  />
116
130
  );
117
131
  }
118
132
 
133
+ if (n.type === 'math_templated') {
134
+ const data = n.data.toJSON();
135
+ let error;
136
+
137
+ if (opts.error) {
138
+ error = opts.error();
139
+ }
140
+
141
+ // add 1 to index to display R 1 instead of R 0
142
+ const keyToDisplay = `R ${parseInt(data.index) + 1}`;
143
+
144
+ return (
145
+ <MathTemplated
146
+ attributes={attributes}
147
+ keyToDisplay={keyToDisplay}
148
+ value={data.value || ''}
149
+ error={error && error[data.index] && error[data.index][0]}
150
+ />
151
+ );
152
+ }
153
+
119
154
  if (n.type === 'drag_in_the_blank') {
120
155
  const data = n.data.toJSON();
121
156
 
@@ -152,11 +187,7 @@ export default function ResponseAreaPlugin(opts) {
152
187
  const currentRespAreaList = change.value.document.filterDescendants(isOfCurrentType);
153
188
  const oldRespAreaList = editor.value.document.filterDescendants(isOfCurrentType);
154
189
 
155
- if (currentRespAreaList.size >= opts.maxResponseAreas) {
156
- toolbar.disabled = true;
157
- } else {
158
- toolbar.disabled = false;
159
- }
190
+ toolbar.disabled = currentRespAreaList.size >= opts.maxResponseAreas;
160
191
 
161
192
  const arrayToFilter = oldRespAreaList.size > currentRespAreaList.size ? oldRespAreaList : currentRespAreaList;
162
193
  const arrayToUseForFilter = arrayToFilter === oldRespAreaList ? currentRespAreaList : oldRespAreaList;
@@ -205,6 +236,16 @@ export const serialization = {
205
236
  value: el.dataset.value,
206
237
  },
207
238
  };
239
+ case 'math_templated':
240
+ return {
241
+ object: 'inline',
242
+ type: 'math_templated',
243
+ isVoid: true,
244
+ data: {
245
+ index: el.dataset.index,
246
+ value: el.dataset.value,
247
+ },
248
+ };
208
249
  case 'drag_in_the_blank':
209
250
  return {
210
251
  object: 'inline',
@@ -235,6 +276,11 @@ export const serialization = {
235
276
 
236
277
  return <span data-type="explicit_constructed_response" data-index={data.index} data-value={data.value} />;
237
278
  }
279
+ case 'math_templated': {
280
+ const data = object.data.toJSON();
281
+
282
+ return <span data-type="math_templated" data-index={data.index} data-value={data.value} />;
283
+ }
238
284
  case 'drag_in_the_blank': {
239
285
  const data = object.data.toJSON();
240
286
 
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
- import { Chevron } from '../icons';
3
+ import { Chevron } from '@pie-lib/icons';
4
4
 
5
5
  const InlineDropdown = ({ attributes, selectedItem }) => {
6
6
  // TODO: Investigate
@@ -27,6 +27,7 @@ const InlineDropdown = ({ attributes, selectedItem }) => {
27
27
  boxSizing: 'border-box',
28
28
  borderRadius: '3px',
29
29
  position: 'relative',
30
+ alignItems: 'center',
30
31
  }}
31
32
  >
32
33
  <div
@@ -36,12 +37,18 @@ const InlineDropdown = ({ attributes, selectedItem }) => {
36
37
  padding: '0 25px 0 8px',
37
38
  whiteSpace: 'nowrap',
38
39
  textOverflow: 'ellipsis',
39
- lineHeight: '35px',
40
40
  }}
41
- dangerouslySetInnerHTML={{
42
- __html: html,
43
- }}
44
- />
41
+ >
42
+ <span
43
+ style={{
44
+ display: 'inline-block',
45
+ verticalAlign: 'middle',
46
+ }}
47
+ dangerouslySetInnerHTML={{
48
+ __html: html,
49
+ }}
50
+ />
51
+ </div>
45
52
  <Chevron
46
53
  direction="down"
47
54
  style={{
@@ -0,0 +1,104 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { withStyles } from '@material-ui/core/styles';
4
+ import { mq } from '@pie-lib/math-input';
5
+
6
+ const MathTemplated = ({ attributes, value, classes, keyToDisplay }) => (
7
+ <span {...attributes} className={classes.spanContainer}>
8
+ <div className={classes.responseBox}>{keyToDisplay}</div>
9
+ <div className={classes.mathBlock}>
10
+ <mq.Static latex={value} />
11
+ </div>
12
+ </span>
13
+ );
14
+
15
+ MathTemplated.propTypes = {
16
+ attributes: PropTypes.object,
17
+ classes: PropTypes.object.isRequired,
18
+ value: PropTypes.string,
19
+ keyToDisplay: PropTypes.string,
20
+ };
21
+
22
+ const styles = (theme) => ({
23
+ responseBox: {
24
+ background: theme.palette.grey['A100'],
25
+ color: theme.palette.grey['A700'],
26
+ display: 'inline-flex',
27
+ borderRight: '2px solid #C0C3CF',
28
+ boxSizing: 'border-box',
29
+ overflow: 'hidden',
30
+ fontSize: '12px',
31
+ minHeight: '36px',
32
+ height: '100%',
33
+ alignItems: 'center',
34
+ fontFamily: 'Symbola, Times New Roman, serif',
35
+ padding: '0 2px',
36
+ },
37
+ spanContainer: {
38
+ display: 'inline-flex',
39
+ border: '1px solid #C0C3CF',
40
+ margin: '1px 5px',
41
+ cursor: 'pointer',
42
+ alignItems: 'center',
43
+ justifyContent: 'center',
44
+ minWidth: '50px',
45
+ minHeight: '36px',
46
+ height: 'fit-content',
47
+ },
48
+ mathBlock: {
49
+ flex: 8,
50
+ color: 'var(--pie-text, black)',
51
+ padding: '4px !important',
52
+ display: 'flex',
53
+ alignItems: 'center',
54
+ justifyContent: 'center',
55
+ backgroundColor: 'var(--pie-background, rgba(255, 255, 255, 0))',
56
+ '& > .mq-math-mode sup.mq-nthroot': {
57
+ fontSize: '70% !important',
58
+ verticalAlign: '1em !important',
59
+ },
60
+ '& > .mq-math-mode .mq-sqrt-stem': {
61
+ borderTop: '0.07em solid',
62
+ marginLeft: '-1.5px',
63
+ marginTop: '-2px !important',
64
+ paddingTop: '5px !important',
65
+ },
66
+ '& .mq-overarrow-inner': {
67
+ paddingTop: '0 !important',
68
+ border: 'none !important',
69
+ },
70
+ '& .mq-overarrow.mq-arrow-both': {
71
+ marginTop: '0px',
72
+ minWidth: '1.23em',
73
+ '& *': {
74
+ lineHeight: '1 !important',
75
+ },
76
+ '&:before': {
77
+ top: '-0.4em',
78
+ left: '-1px',
79
+ },
80
+ '&:after': {
81
+ top: '0px !important',
82
+ position: 'absolute !important',
83
+ right: '-2px',
84
+ },
85
+ '&.mq-empty:after': {
86
+ top: '-0.45em',
87
+ },
88
+ },
89
+ '& .mq-overarrow.mq-arrow-right': {
90
+ '&:before': {
91
+ top: '-0.4em',
92
+ right: '-1px',
93
+ },
94
+ },
95
+ '& .mq-overarrow-inner-right': {
96
+ display: 'none !important',
97
+ },
98
+ '& .mq-overarrow-inner-left': {
99
+ display: 'none !important',
100
+ },
101
+ },
102
+ });
103
+
104
+ export default withStyles(styles)(MathTemplated);
@@ -42,6 +42,14 @@ export const defaultECR = (index) =>
42
42
  index,
43
43
  },
44
44
  });
45
+ export const defaultMT = (index) =>
46
+ Inline.create({
47
+ type: 'math_templated',
48
+ isVoid: true,
49
+ data: {
50
+ index,
51
+ },
52
+ });
45
53
 
46
54
  export const defaultDIB = (opts, index) =>
47
55
  Inline.create({
@@ -69,6 +77,9 @@ export const getDefaultElement = (opts, index) => {
69
77
  case 'explicit-constructed-response':
70
78
  return defaultECR(index);
71
79
 
80
+ case 'math-templated':
81
+ return defaultMT(index);
82
+
72
83
  case 'drag-in-the-blank':
73
84
  return defaultDIB(opts, index);
74
85