@pie-lib/editable-html-tip-tap 1.0.1 → 1.0.3

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 (129) hide show
  1. package/lib/components/CharacterPicker.js +221 -0
  2. package/lib/components/EditableHtml.js +323 -0
  3. package/lib/components/MenuBar.js +693 -0
  4. package/lib/components/TiptapContainer.js +90 -0
  5. package/lib/components/buttons/done-button.js +53 -0
  6. package/lib/components/characters/characterUtils.js +112 -0
  7. package/lib/components/characters/custom-popper.js +73 -0
  8. package/lib/components/common/done-button.js +53 -0
  9. package/lib/components/icons/CssIcon.js +37 -0
  10. package/lib/components/icons/RespArea.js +95 -0
  11. package/lib/components/icons/TableIcons.js +69 -0
  12. package/lib/components/icons/TextAlign.js +194 -0
  13. package/lib/components/icons/index.js +194 -0
  14. package/lib/components/image/ImageToolbar.js +16 -0
  15. package/lib/components/image/InsertImageHandler.js +16 -0
  16. package/lib/components/media/MediaDialog.js +16 -0
  17. package/lib/components/media/MediaToolbar.js +16 -0
  18. package/lib/components/respArea/DragInTheBlank/DragInTheBlank.js +94 -0
  19. package/lib/components/respArea/DragInTheBlank/choice.js +289 -0
  20. package/lib/components/respArea/DragInTheBlank.js +94 -0
  21. package/lib/components/respArea/ExplicitConstructedResponse.js +120 -0
  22. package/lib/components/respArea/InlineDropdown.js +126 -0
  23. package/lib/components/respArea/ToolbarIcon.js +105 -0
  24. package/lib/components/respArea/choice.js +2 -0
  25. package/lib/extensions/component.js +5 -5
  26. package/lib/extensions/custom-toolbar-wrapper.js +2 -4
  27. package/lib/extensions/extended-table.js +30 -0
  28. package/lib/extensions/index.js +52 -0
  29. package/lib/extensions/media.js +5 -5
  30. package/lib/extensions/responseArea.js +7 -7
  31. package/lib/index.js +16 -1454
  32. package/lib/plugins/index.js +10 -82
  33. package/lib/styles/editorContainerStyles.js +200 -0
  34. package/lib/utils/size.js +34 -0
  35. package/package.json +1 -1
  36. package/src/components/CharacterPicker.jsx +185 -0
  37. package/src/components/EditableHtml.jsx +306 -0
  38. package/src/components/MenuBar.jsx +630 -0
  39. package/src/components/TiptapContainer.jsx +96 -0
  40. package/src/components/characters/characterUtils.js +127 -0
  41. package/src/components/image/ImageToolbar.jsx +1 -0
  42. package/src/components/image/InsertImageHandler.js +1 -0
  43. package/src/components/media/MediaDialog.js +1 -0
  44. package/src/components/media/MediaToolbar.jsx +1 -0
  45. package/src/{plugins/respArea/drag-in-the-blank → components/respArea/DragInTheBlank}/choice.jsx +1 -1
  46. package/src/{plugins/respArea/inline-dropdown/index.jsx → components/respArea/InlineDropdown.jsx} +1 -1
  47. package/src/components/respArea/ToolbarIcon.jsx +68 -0
  48. package/src/extensions/component.jsx +2 -2
  49. package/src/extensions/custom-toolbar-wrapper.jsx +6 -7
  50. package/src/extensions/extended-table.js +27 -0
  51. package/src/extensions/index.js +76 -0
  52. package/src/extensions/media.js +10 -4
  53. package/src/extensions/responseArea.js +7 -7
  54. package/src/index.jsx +3 -1409
  55. package/src/styles/editorContainerStyles.js +203 -0
  56. package/src/utils/size.js +32 -0
  57. package/src/__tests__/editor.test.jsx +0 -363
  58. package/src/__tests__/serialization.test.js +0 -291
  59. package/src/block-tags.js +0 -17
  60. package/src/editor.jsx +0 -1197
  61. package/src/extensions/characters.js +0 -46
  62. package/src/old-index.jsx +0 -162
  63. package/src/parse-html.js +0 -8
  64. package/src/plugins/README.md +0 -27
  65. package/src/plugins/characters/index.jsx +0 -284
  66. package/src/plugins/characters/utils.js +0 -447
  67. package/src/plugins/css/index.jsx +0 -340
  68. package/src/plugins/customPlugin/index.jsx +0 -85
  69. package/src/plugins/html/icons/index.jsx +0 -19
  70. package/src/plugins/html/index.jsx +0 -72
  71. package/src/plugins/image/__tests__/__snapshots__/component.test.jsx.snap +0 -51
  72. package/src/plugins/image/__tests__/__snapshots__/image-toolbar-logic.test.jsx.snap +0 -27
  73. package/src/plugins/image/__tests__/__snapshots__/image-toolbar.test.jsx.snap +0 -44
  74. package/src/plugins/image/__tests__/component.test.jsx +0 -41
  75. package/src/plugins/image/__tests__/image-toolbar-logic.test.jsx +0 -42
  76. package/src/plugins/image/__tests__/image-toolbar.test.jsx +0 -11
  77. package/src/plugins/image/__tests__/index.test.js +0 -95
  78. package/src/plugins/image/__tests__/insert-image-handler.test.js +0 -113
  79. package/src/plugins/image/__tests__/mock-change.js +0 -15
  80. package/src/plugins/image/alt-dialog.jsx +0 -82
  81. package/src/plugins/image/component.jsx +0 -343
  82. package/src/plugins/image/image-toolbar.jsx +0 -100
  83. package/src/plugins/image/index.jsx +0 -227
  84. package/src/plugins/image/insert-image-handler.js +0 -79
  85. package/src/plugins/index.jsx +0 -377
  86. package/src/plugins/list/__tests__/index.test.js +0 -54
  87. package/src/plugins/list/index.jsx +0 -305
  88. package/src/plugins/math/__tests__/__snapshots__/index.test.jsx.snap +0 -48
  89. package/src/plugins/math/__tests__/index.test.jsx +0 -245
  90. package/src/plugins/math/index.jsx +0 -379
  91. package/src/plugins/media/__tests__/index.test.js +0 -75
  92. package/src/plugins/media/index.jsx +0 -325
  93. package/src/plugins/media/media-dialog.js +0 -624
  94. package/src/plugins/media/media-toolbar.jsx +0 -56
  95. package/src/plugins/media/media-wrapper.jsx +0 -43
  96. package/src/plugins/rendering/index.js +0 -31
  97. package/src/plugins/respArea/index.jsx +0 -299
  98. package/src/plugins/respArea/math-templated/index.jsx +0 -104
  99. package/src/plugins/respArea/utils.jsx +0 -90
  100. package/src/plugins/table/CustomTablePlugin.js +0 -113
  101. package/src/plugins/table/__tests__/__snapshots__/table-toolbar.test.jsx.snap +0 -44
  102. package/src/plugins/table/__tests__/index.test.jsx +0 -401
  103. package/src/plugins/table/__tests__/table-toolbar.test.jsx +0 -42
  104. package/src/plugins/table/index.jsx +0 -427
  105. package/src/plugins/table/table-toolbar.jsx +0 -136
  106. package/src/plugins/textAlign/index.jsx +0 -23
  107. package/src/plugins/toolbar/__tests__/__snapshots__/default-toolbar.test.jsx.snap +0 -923
  108. package/src/plugins/toolbar/__tests__/__snapshots__/editor-and-toolbar.test.jsx.snap +0 -20
  109. package/src/plugins/toolbar/__tests__/__snapshots__/toolbar-buttons.test.jsx.snap +0 -36
  110. package/src/plugins/toolbar/__tests__/__snapshots__/toolbar.test.jsx.snap +0 -46
  111. package/src/plugins/toolbar/__tests__/default-toolbar.test.jsx +0 -94
  112. package/src/plugins/toolbar/__tests__/editor-and-toolbar.test.jsx +0 -37
  113. package/src/plugins/toolbar/__tests__/toolbar-buttons.test.jsx +0 -51
  114. package/src/plugins/toolbar/__tests__/toolbar.test.jsx +0 -106
  115. package/src/plugins/toolbar/default-toolbar.jsx +0 -206
  116. package/src/plugins/toolbar/editor-and-toolbar.jsx +0 -257
  117. package/src/plugins/toolbar/index.jsx +0 -23
  118. package/src/plugins/toolbar/toolbar-buttons.jsx +0 -138
  119. package/src/plugins/toolbar/toolbar.jsx +0 -338
  120. package/src/plugins/utils.js +0 -31
  121. package/src/serialization.jsx +0 -621
  122. /package/src/{plugins → components}/characters/custom-popper.js +0 -0
  123. /package/src/{plugins/toolbar → components/common}/done-button.jsx +0 -0
  124. /package/src/{plugins/css/icons/index.jsx → components/icons/CssIcon.jsx} +0 -0
  125. /package/src/{plugins/respArea/icons/index.jsx → components/icons/RespArea.jsx} +0 -0
  126. /package/src/{plugins/table/icons/index.jsx → components/icons/TableIcons.jsx} +0 -0
  127. /package/src/{plugins/textAlign/icons/index.jsx → components/icons/TextAlign.jsx} +0 -0
  128. /package/src/{plugins/respArea/drag-in-the-blank/index.jsx → components/respArea/DragInTheBlank/DragInTheBlank.jsx} +0 -0
  129. /package/src/{plugins/respArea/explicit-constructed-response/index.jsx → components/respArea/ExplicitConstructedResponse.jsx} +0 -0
package/src/editor.jsx DELETED
@@ -1,1197 +0,0 @@
1
- import React from 'react';
2
- import { Editor as SlateEditor, findNode, getEventRange, getEventTransfer } from 'slate-react';
3
- import SlateTypes from 'slate-prop-types';
4
- import { Value, Block, Inline } from 'slate';
5
- import Plain from 'slate-plain-serializer';
6
- import PropTypes from 'prop-types';
7
- import debounce from 'lodash/debounce';
8
- import isEqual from 'lodash/isEqual';
9
- import classNames from 'classnames';
10
- import debug from 'debug';
11
- import { withStyles } from '@material-ui/core/styles';
12
-
13
- import { color } from '@pie-lib/render-ui';
14
- import AlertDialog from '../../config-ui/src/alert-dialog';
15
- import { PreviewPrompt } from '@pie-lib/render-ui';
16
-
17
- import { getBase64, htmlToValue } from './serialization';
18
- import InsertImageHandler from './plugins/image/insert-image-handler';
19
- import * as serialization from './serialization';
20
- import { buildPlugins, ALL_PLUGINS, DEFAULT_PLUGINS } from './plugins';
21
-
22
- export { ALL_PLUGINS, DEFAULT_PLUGINS, serialization };
23
-
24
- const log = debug('editable-html:editor');
25
-
26
- const defaultToolbarOpts = {
27
- position: 'bottom',
28
- alignment: 'left',
29
- alwaysVisible: false,
30
- showDone: true,
31
- doneOn: 'blur',
32
- };
33
-
34
- const defaultResponseAreaProps = {
35
- options: {},
36
- respAreaToolbar: () => {},
37
- onHandleAreaChange: () => {},
38
- };
39
-
40
- const defaultLanguageCharactersProps = [];
41
-
42
- const createToolbarOpts = (toolbarOpts, error, isHtmlMode) => {
43
- return {
44
- ...defaultToolbarOpts,
45
- ...toolbarOpts,
46
- error,
47
- isHtmlMode,
48
- };
49
- };
50
-
51
- /**
52
- * The maximum number of characters the editor can support
53
- * @type {number}
54
- */
55
- const MAX_CHARACTERS_LIMIT = 1000000;
56
-
57
- export class Editor extends React.Component {
58
- static propTypes = {
59
- autoFocus: PropTypes.bool,
60
- editorRef: PropTypes.func.isRequired,
61
- error: PropTypes.any,
62
- onRef: PropTypes.func.isRequired,
63
- onChange: PropTypes.func.isRequired,
64
- onFocus: PropTypes.func,
65
- onBlur: PropTypes.func,
66
- onKeyDown: PropTypes.func,
67
- focus: PropTypes.func.isRequired,
68
- value: SlateTypes.value.isRequired,
69
- imageSupport: PropTypes.object,
70
- mathMlOptions: PropTypes.shape({
71
- mmlOutput: PropTypes.bool,
72
- mmlEditing: PropTypes.bool,
73
- }),
74
- disableImageAlignmentButtons: PropTypes.bool,
75
- uploadSoundSupport: PropTypes.shape({
76
- add: PropTypes.func,
77
- delete: PropTypes.func,
78
- }),
79
- charactersLimit: PropTypes.number,
80
- width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
81
- minWidth: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
82
- maxWidth: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
83
- height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
84
- minHeight: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
85
- maxHeight: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
86
- classes: PropTypes.object.isRequired,
87
- highlightShape: PropTypes.bool,
88
- disabled: PropTypes.bool,
89
- spellCheck: PropTypes.bool,
90
- nonEmpty: PropTypes.bool,
91
- disableScrollbar: PropTypes.bool,
92
- disableUnderline: PropTypes.bool,
93
- autoWidthToolbar: PropTypes.bool,
94
- pluginProps: PropTypes.any,
95
- // customPlugins should be inside pluginProps (a property inside pluginProps)
96
- // customPlugins: PropTypes.arrayOf(
97
- // PropTypes.shape({
98
- // event: PropTypes.string,
99
- // icon: PropTypes.string,
100
- // iconType: PropTypes.string,
101
- // iconAlt: PropTypes.string
102
- // }),
103
- // ),
104
- placeholder: PropTypes.string,
105
- isEditor: PropTypes.bool,
106
- responseAreaProps: PropTypes.shape({
107
- type: PropTypes.oneOf([
108
- 'explicit-constructed-response',
109
- 'inline-dropdown',
110
- 'drag-in-the-blank',
111
- 'math-templated',
112
- ]),
113
- options: PropTypes.object,
114
- respAreaToolbar: PropTypes.func,
115
- onHandleAreaChange: PropTypes.func,
116
- maxResponseAreas: PropTypes.number,
117
- error: PropTypes.any,
118
- }),
119
- extraCSSRules: PropTypes.shape({
120
- names: PropTypes.arrayOf(PropTypes.string),
121
- rules: PropTypes.string,
122
- }),
123
- languageCharactersProps: PropTypes.arrayOf(
124
- PropTypes.shape({
125
- language: PropTypes.string,
126
- characterIcon: PropTypes.string,
127
- characters: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.string)),
128
- }),
129
- ),
130
- runSerializationOnMarkup: PropTypes.func,
131
- toolbarOpts: PropTypes.shape({
132
- position: PropTypes.oneOf(['bottom', 'top']),
133
- alignment: PropTypes.oneOf(['left', 'right']),
134
- alwaysVisible: PropTypes.bool,
135
- showDone: PropTypes.bool,
136
- doneOn: PropTypes.string,
137
- minWidth: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
138
- }),
139
- activePlugins: PropTypes.arrayOf((values) => {
140
- const allValid = values.every((v) => ALL_PLUGINS.includes(v));
141
-
142
- return !allValid && new Error(`Invalid values: ${values}, values must be one of [${ALL_PLUGINS.join(',')}]`);
143
- }),
144
- className: PropTypes.string,
145
- maxImageWidth: PropTypes.number,
146
- maxImageHeight: PropTypes.number,
147
- };
148
-
149
- static defaultProps = {
150
- disableUnderline: true,
151
- onFocus: () => {},
152
- onBlur: () => {},
153
- onKeyDown: () => {},
154
- runSerializationOnMarkup: () => {},
155
- mathMlOptions: {
156
- mmlOutput: false,
157
- mmlEditing: false,
158
- },
159
- toolbarOpts: defaultToolbarOpts,
160
- responseAreaProps: defaultResponseAreaProps,
161
- languageCharactersProps: defaultLanguageCharactersProps,
162
- extraCSSRules: null,
163
- isEditor: false,
164
- };
165
-
166
- constructor(props) {
167
- super(props);
168
- this.state = {
169
- value: props.value,
170
- toolbarOpts: createToolbarOpts(props.toolbarOpts, props.error),
171
- pendingImages: [],
172
- isHtmlMode: false,
173
- isEditedInHtmlMode: false,
174
- focusToolbar: false,
175
- dialog: {
176
- open: false,
177
- },
178
- };
179
-
180
- this.keyPadCharacterRef = React.createRef();
181
- this.doneButtonRef = React.createRef();
182
- this.keypadInteractionDetected = false;
183
-
184
- this.toggleHtmlMode = this.toggleHtmlMode.bind(this);
185
- this.handleToolbarFocus = this.handleToolbarFocus.bind(this);
186
- this.handleToolbarBlur = this.handleToolbarBlur.bind(this);
187
-
188
- this.onResize = debounce(() => {
189
- if (!this.state.isHtmlMode && props.onChange) {
190
- props.onChange(this.state.value, true);
191
- }
192
- }, 50);
193
-
194
- this.handlePlugins(this.props);
195
- }
196
-
197
- handleToolbarFocus() {
198
- if (this.state.focusToolbar) {
199
- return;
200
- }
201
-
202
- this.setState({ focusToolbar: true });
203
- }
204
-
205
- setKeypadInteraction = (interacted) => {
206
- this.keypadInteractionDetected = interacted;
207
- };
208
-
209
- handleToolbarBlur() {
210
- setTimeout(() => {
211
- if (!this.toolbarContainsFocus()) {
212
- this.setState({ focusToolbar: false });
213
- }
214
- }, 0);
215
- }
216
-
217
- toolbarContainsFocus() {
218
- if (!this.toolbarRef) return false;
219
- const toolbarElement = this.toolbarRef;
220
- const activeElement = document.activeElement;
221
-
222
- return toolbarElement && toolbarElement.contains(activeElement);
223
- }
224
-
225
- handleDialog = (open, extraDialogProps = {}, callback) => {
226
- this.setState(
227
- {
228
- dialog: {
229
- open,
230
- ...extraDialogProps,
231
- },
232
- },
233
- callback,
234
- );
235
- };
236
-
237
- toggleHtmlMode = () => {
238
- this.setState(
239
- (prevState) => ({
240
- isHtmlMode: !prevState.isHtmlMode,
241
- isEditedInHtmlMode: false,
242
- }),
243
- () => {
244
- const { error } = this.props;
245
- const { toolbarOpts } = this.state;
246
- const newToolbarOpts = createToolbarOpts(toolbarOpts, error, this.state.isHtmlMode);
247
- this.setState({
248
- toolbarOpts: newToolbarOpts,
249
- });
250
- },
251
- );
252
- };
253
-
254
- handlePlugins = (props) => {
255
- const normalizedResponseAreaProps = {
256
- ...defaultResponseAreaProps,
257
- ...props.responseAreaProps,
258
- };
259
-
260
- const htmlPluginOpts = {
261
- currentValue: this.props.value,
262
- isHtmlMode: this.state.isHtmlMode,
263
- isEditedInHtmlMode: this.state.isEditedInHtmlMode,
264
- toggleHtmlMode: this.toggleHtmlMode,
265
- handleAlertDialog: this.handleDialog,
266
- };
267
- let { customPlugins } = props.pluginProps || {};
268
- customPlugins = customPlugins || [];
269
-
270
- this.plugins = buildPlugins(props.activePlugins, customPlugins, {
271
- math: {
272
- onClick: this.onMathClick,
273
- onFocus: this.onPluginFocus,
274
- onBlur: this.onPluginBlur,
275
- ...props.mathMlOptions,
276
- },
277
- textAlign: {
278
- getValue: () => this.state.value,
279
- onChange: this.onChange,
280
- },
281
- html: htmlPluginOpts,
282
- extraCSSRules: props.extraCSSRules || {},
283
- image: {
284
- disableImageAlignmentButtons: props.disableImageAlignmentButtons,
285
- onDelete:
286
- props.imageSupport &&
287
- props.imageSupport.delete &&
288
- ((node, done) => {
289
- const src = node.data.get('src');
290
-
291
- props.imageSupport.delete(src, (e) => {
292
- const newPendingImages = this.state.pendingImages.filter((img) => img.key !== node.key);
293
- const { scheduled: oldScheduled } = this.state;
294
- const newState = {
295
- pendingImages: newPendingImages,
296
- scheduled: oldScheduled && newPendingImages.length === 0 ? false : oldScheduled,
297
- };
298
-
299
- this.setState(newState, () => done(e, this.state.value));
300
- });
301
- }),
302
- insertImageRequested:
303
- props.imageSupport &&
304
- ((addedImage, getHandler) => {
305
- const { pendingImages } = this.state;
306
- const onFinish = (result) => {
307
- let cb;
308
-
309
- if (this.state.scheduled && result) {
310
- // finish editing only on success
311
- cb = this.onEditingDone.bind(this);
312
- }
313
-
314
- const newPendingImages = this.state.pendingImages.filter((img) => img.key !== addedImage.key);
315
- const newState = {
316
- pendingImages: newPendingImages,
317
- };
318
-
319
- if (newPendingImages.length === 0) {
320
- newState.scheduled = false;
321
- }
322
-
323
- this.setState(newState, cb);
324
- };
325
- const callback = () => {
326
- /**
327
- * The handler is the object through which the outer context
328
- * communicates file upload events like: fileChosen, cancel, progress
329
- */
330
- const handler = getHandler(onFinish, () => this.state.value);
331
- props.imageSupport.add(handler);
332
- };
333
-
334
- this.setState(
335
- {
336
- pendingImages: [...pendingImages, addedImage],
337
- },
338
- callback,
339
- );
340
- }),
341
- onFocus: this.onPluginFocus,
342
- onBlur: this.onPluginBlur,
343
- maxImageWidth: props.maxImageWidth,
344
- maxImageHeight: props.maxImageHeight,
345
- },
346
- toolbar: {
347
- /**
348
- * To minimize converting html -> state -> html
349
- * We only emit markup once 'done' is clicked.
350
- */
351
- disableScrollbar: !!props.disableScrollbar,
352
- disableUnderline: props.disableUnderline,
353
- autoWidth: props.autoWidthToolbar,
354
- onDone: () => {
355
- const { nonEmpty } = props;
356
-
357
- log('[onDone]');
358
- this.setState({ toolbarInFocus: false, focusedNode: null, focusToolbar: false });
359
- this.editor.blur();
360
-
361
- if (nonEmpty && this.state.value.startText?.text?.length === 0) {
362
- this.resetValue(true).then(() => {
363
- this.onEditingDone();
364
- });
365
- } else {
366
- this.onEditingDone();
367
- }
368
- },
369
- },
370
- table: {
371
- onFocus: () => {
372
- log('[table:onFocus]...');
373
- this.onPluginFocus();
374
- },
375
- onBlur: () => {
376
- log('[table:onBlur]...');
377
- this.onPluginBlur();
378
- },
379
- },
380
- responseArea: {
381
- type: normalizedResponseAreaProps.type,
382
- options: normalizedResponseAreaProps.options,
383
- maxResponseAreas: normalizedResponseAreaProps.maxResponseAreas,
384
- respAreaToolbar: normalizedResponseAreaProps.respAreaToolbar,
385
- onHandleAreaChange: normalizedResponseAreaProps.onHandleAreaChange,
386
- error: normalizedResponseAreaProps.error,
387
- onFocus: () => {
388
- log('[table:onFocus]...');
389
- this.onPluginFocus();
390
- },
391
- onBlur: () => {
392
- log('[table:onBlur]...');
393
- this.onPluginBlur();
394
- },
395
- },
396
- languageCharacters: props.languageCharactersProps,
397
- keyPadCharacterRef: this.keyPadCharacterRef,
398
- setKeypadInteraction: this.setKeypadInteraction,
399
- media: {
400
- focus: this.focus,
401
- createChange: () => this.state.value.change(),
402
- onChange: this.onChange,
403
- uploadSoundSupport: props.uploadSoundSupport,
404
- },
405
- });
406
-
407
- if (props.mathMlOptions.mmlOutput || props.mathMlOptions.mmlEditing) {
408
- this.props.runSerializationOnMarkup();
409
- }
410
- };
411
-
412
- componentDidMount() {
413
- // onRef is needed to get the ref of the component because we export it using withStyles
414
- this.props.onRef(this);
415
-
416
- window.addEventListener('resize', this.onResize);
417
-
418
- const isResponseAreaEditor = this.props.className?.includes('response-area-editor');
419
-
420
- if (isResponseAreaEditor && this.editor) {
421
- const responseAreaEditor = document.querySelector(`[data-key="${this.editor.value.document.key}"]`);
422
-
423
- if (responseAreaEditor) {
424
- responseAreaEditor.setAttribute('aria-label', 'Answer');
425
- }
426
- }
427
-
428
- if (this.editor && this.props.autoFocus) {
429
- Promise.resolve().then(() => {
430
- if (this.editor) {
431
- const editorDOM = document.querySelector(`[data-key="${this.editor.value.document.key}"]`);
432
-
433
- this.editor.focus();
434
-
435
- if (editorDOM) {
436
- editorDOM.focus();
437
- }
438
- }
439
- });
440
- }
441
- }
442
-
443
- UNSAFE_componentWillReceiveProps(nextProps) {
444
- const { isHtmlMode, toolbarOpts } = this.state;
445
- const newToolbarOpts = createToolbarOpts(nextProps.toolbarOpts, nextProps.error, isHtmlMode);
446
-
447
- if (!isEqual(newToolbarOpts, toolbarOpts)) {
448
- this.setState({
449
- toolbarOpts: newToolbarOpts,
450
- });
451
- }
452
-
453
- const differentCharacterProps = !isEqual(nextProps.languageCharactersProps, this.props.languageCharactersProps);
454
- const differentMathMlProps = !isEqual(nextProps.mathMlOptions, this.props.mathMlOptions);
455
- const differentImageMaxDimensionsProps =
456
- !isEqual(nextProps.maxImageWidth, this.props.maxImageWidth) ||
457
- !isEqual(nextProps.maxImageHeight, this.props.maxImageHeight);
458
-
459
- if (differentCharacterProps || differentMathMlProps || differentImageMaxDimensionsProps) {
460
- this.handlePlugins(nextProps);
461
- }
462
-
463
- if (!nextProps.value?.document?.equals(this.props.value?.document)) {
464
- this.setState({
465
- focus: false,
466
- value: nextProps.value,
467
- });
468
- }
469
- }
470
-
471
- componentDidUpdate(prevProps, prevState) {
472
- // The cursor is on a zero width element and when that is placed near void elements, it is not visible
473
- // so we increase the width to at least 2px in order for the user to see it
474
-
475
- // Trigger plugins and finish editing if:
476
- // 1. The 'isHtmlMode' state has been toggled.
477
- // 2. We're currently in 'isHtmlMode' and the editor value has been modified.
478
- if (
479
- this.state.isHtmlMode !== prevState.isHtmlMode ||
480
- (this.state.isHtmlMode && !prevState.isEditedInHtmlMode && this.state.isEditedInHtmlMode)
481
- ) {
482
- this.handlePlugins(this.props);
483
- }
484
-
485
- const zeroWidthEls = document.querySelectorAll('[data-slate-zero-width="z"]');
486
-
487
- Array.from(zeroWidthEls).forEach((el) => {
488
- el.style.minWidth = '2px';
489
- el.style.display = 'inline-block';
490
- });
491
- }
492
-
493
- onPluginBlur = (e) => {
494
- log('[onPluginBlur]', e && e.relatedTarget);
495
- const target = e && e.relatedTarget;
496
-
497
- const node = target ? findNode(target, this.state.value) : null;
498
- log('[onPluginBlur] node: ', node);
499
- this.setState({ focusedNode: node }, () => {
500
- this.resetValue();
501
- });
502
- };
503
-
504
- onPluginFocus = (e) => {
505
- log('[onPluginFocus]', e && e.target);
506
- const target = e && e.target;
507
- if (target) {
508
- const node = findNode(target, this.state.value);
509
- log('[onPluginFocus] node: ', node);
510
-
511
- const stashedValue = this.state.stashedValue || this.state.value;
512
- this.setState({ focusedNode: node, stashedValue });
513
- } else {
514
- this.setState({ focusedNode: null });
515
- }
516
- this.stashValue();
517
- };
518
-
519
- onMathClick = (node) => {
520
- this.editor.change((c) => c.collapseToStartOf(node));
521
- this.setState({ selectedNode: node });
522
- };
523
-
524
- onEditingDone = () => {
525
- const { isHtmlMode, dialog, value, pendingImages } = this.state;
526
-
527
- // Handling HTML mode and dialog state
528
- if (isHtmlMode) {
529
- // Early return if HTML mode is enabled
530
- if (dialog?.open) return;
531
-
532
- const currentValue = htmlToValue(value.document.text);
533
- const previewText = this.renderHtmlPreviewContent();
534
-
535
- this.openHtmlModeConfirmationDialog(currentValue, previewText);
536
- return;
537
- }
538
-
539
- if (pendingImages.length) {
540
- // schedule image processing
541
- this.setState({ scheduled: true });
542
- return;
543
- }
544
-
545
- // Finalizing editing
546
- log('[onEditingDone]');
547
- this.setState({ pendingImages: [], stashedValue: null, focusedNode: null });
548
- log('[onEditingDone] value: ', this.state.value);
549
- this.props.onChange(this.state.value, true);
550
- };
551
-
552
- /**
553
- * Renders the HTML preview content to be displayed inside the dialog.
554
- * This content includes the edited HTML and a prompt for the user.
555
- */
556
- renderHtmlPreviewContent = () => {
557
- const { classes } = this.props;
558
- return (
559
- <div ref={(ref) => (this.elementRef = ref)}>
560
- <div>Preview of Edited Html:</div>
561
- <PreviewPrompt defaultClassName={classes.previewText} prompt={this.state.value.document.text} />
562
- <div>Would you like to save these changes ?</div>
563
- </div>
564
- );
565
- };
566
-
567
- /**
568
- * Opens a confirmation dialog in HTML mode, displaying the preview of the current HTML content
569
- * and offering options to save or continue editing.
570
- */
571
- openHtmlModeConfirmationDialog = (currentValue, previewText) => {
572
- this.setState({
573
- dialog: {
574
- open: true,
575
- title: 'Content Preview & Save',
576
- text: previewText,
577
- onConfirmText: 'Save changes',
578
- onCloseText: 'Continue editing',
579
- onConfirm: () => {
580
- this.handleHtmlModeSaveConfirmation(currentValue);
581
- },
582
- onClose: this.htmlModeContinueEditing,
583
- },
584
- });
585
- };
586
-
587
- /**
588
- * Handles the save confirmation action in HTML mode. This updates the value to the confirmed
589
- * content, updates value on props, and exits the HTML mode.
590
- * @param {string} currentValue - The confirmed value of the HTML content to save.
591
- */
592
- handleHtmlModeSaveConfirmation = (currentValue) => {
593
- this.setState({ value: currentValue });
594
- this.props.onChange(currentValue, true);
595
- this.handleDialog(false);
596
- this.toggleHtmlMode();
597
- };
598
-
599
- /**
600
- * Closes the dialog in HTML mode and allows the user to continue editing the html content.
601
- * This function is invoked when the user opts to not save the current changes.
602
- */
603
- htmlModeContinueEditing = () => {
604
- this.handleDialog(false);
605
- };
606
-
607
- /**
608
- * Remove onResize event listener
609
- */
610
- componentWillUnmount() {
611
- window.removeEventListener('resize', this.onResize);
612
- }
613
-
614
- // Allowing time for onChange to take effect if it is called
615
- handleBlur = (resolve) => {
616
- const { nonEmpty } = this.props;
617
- const {
618
- toolbarOpts: { doneOn },
619
- } = this.state;
620
-
621
- this.setState({ toolbarInFocus: false, focusedNode: null });
622
-
623
- if (this.editor) {
624
- this.editor.blur();
625
- }
626
-
627
- if (doneOn === 'blur') {
628
- if (nonEmpty && this.state.value.startText?.text?.length === 0) {
629
- this.resetValue(true).then(() => {
630
- this.onEditingDone();
631
- resolve();
632
- });
633
- } else {
634
- this.onEditingDone();
635
- resolve();
636
- }
637
- }
638
- };
639
-
640
- onBlur = (event) => {
641
- log('[onBlur]');
642
- const relatedTarget = event.relatedTarget;
643
- const toolbarElement = this.toolbarRef && relatedTarget?.closest(`[class*="${this.toolbarRef.className}"]`);
644
-
645
- // Check if relatedTarget is a done button
646
- const isRawDoneButton =
647
- this.doneButtonRef && relatedTarget?.closest(`[class*="${this.doneButtonRef.current?.className}"]`);
648
-
649
- // Skip onBlur handling if relatedTarget is a button from the KeyPad characters
650
- this.skipBlurHandling = this.keypadInteractionDetected && relatedTarget !== null;
651
-
652
- if (toolbarElement && !isRawDoneButton && !this.state.focusToolbar) {
653
- this.setState({
654
- focusToolbar: true,
655
- });
656
- }
657
-
658
- const node = relatedTarget ? findNode(relatedTarget, this.state.value) : null;
659
-
660
- log('[onBlur] node: ', node);
661
-
662
- return new Promise((resolve) => {
663
- if (!this.skipBlurHandling) {
664
- this.setKeypadInteraction(false);
665
- this.setState(
666
- { preBlurValue: this.state.value, focusedNode: !node ? null : node },
667
- this.handleBlur.bind(this, resolve),
668
- );
669
- }
670
-
671
- this.props.onBlur(event);
672
- });
673
- };
674
-
675
- handleDomBlur = (e) => {
676
- const editorDOM = document.querySelector(`[data-key="${this.state.value.document.key}"]`);
677
-
678
- setTimeout(() => {
679
- const { value: stateValue } = this.state;
680
-
681
- if (!this.wrapperRef) {
682
- return;
683
- }
684
-
685
- const editorElement = !editorDOM || document.activeElement.closest(`[class*="${editorDOM.className}"]`);
686
- const toolbarElement =
687
- !this.toolbarRef || document.activeElement.closest(`[class*="${this.toolbarRef.className}"]`);
688
- const isInCurrentComponent = this.wrapperRef.contains(editorElement) || this.wrapperRef.contains(toolbarElement);
689
-
690
- if (!isInCurrentComponent) {
691
- editorDOM.removeEventListener('blur', this.handleDomBlur);
692
-
693
- if (stateValue.isFocused) {
694
- this.onBlur(e);
695
- }
696
- }
697
- }, 50);
698
- };
699
-
700
- /*
701
- * Needs to be wrapped otherwise it causes issues because of race conditions
702
- * Known issue for slatejs. See: https://github.com/ianstormtaylor/slate/issues/2097
703
- * Using timeout I wasn't able to test this
704
- *
705
- * Note: The use of promises has been causing issues with MathQuill
706
- * */
707
- onFocus = (event, change) =>
708
- new Promise((resolve) => {
709
- const editorDOM = document.querySelector(`[data-key="${this.state.value.document.key}"]`);
710
- const isTouchDevice =
711
- typeof window !== 'undefined' && ('ontouchstart' in window || navigator?.maxTouchPoints > 0);
712
-
713
- log('[onFocus]', document.activeElement);
714
-
715
- if (this.keypadInteractionDetected && this.__TEMPORARY_CHANGE_DATA) {
716
- this.__TEMPORARY_CHANGE_DATA = null;
717
- }
718
-
719
- /**
720
- * This is a temporary hack - @see changeData below for some more information.
721
- */
722
- if (this.__TEMPORARY_CHANGE_DATA) {
723
- const { key, data } = this.__TEMPORARY_CHANGE_DATA;
724
- const domEl = document.querySelector(`[data-key="${key}"]`);
725
-
726
- if (domEl) {
727
- let change = this.state.value.change().setNodeByKey(key, { data });
728
- this.setState({ value: change.value }, () => {
729
- this.__TEMPORARY_CHANGE_DATA = null;
730
- });
731
- }
732
- }
733
-
734
- /**
735
- * This is needed just in case the browser decides to make the editor
736
- * lose focus without triggering the onBlur event (can happen in a few cases).
737
- * This will also trigger onBlur if the user clicks outside of the page when the editor
738
- * is focused.
739
- */
740
- if (editorDOM === document.activeElement) {
741
- editorDOM.removeEventListener('blur', this.handleDomBlur);
742
- editorDOM.addEventListener('blur', this.handleDomBlur);
743
- }
744
-
745
- this.stashValue();
746
- this.props.onFocus();
747
-
748
- // Added for accessibility: Ensures the editor gains focus when tabbed to for improved keyboard navigation
749
- const shouldFocusEditor = !this.keypadInteractionDetected && !isTouchDevice;
750
-
751
- if (shouldFocusEditor) {
752
- change?.focus();
753
- }
754
-
755
- resolve();
756
- });
757
-
758
- stashValue = () => {
759
- log('[stashValue]');
760
-
761
- if (!this.state.stashedValue) {
762
- this.setState({ stashedValue: this.state.value });
763
- }
764
- };
765
-
766
- /**
767
- * Reset the value if the user didn't click done.
768
- */
769
- resetValue = (force) => {
770
- const { value, focusedNode } = this.state;
771
-
772
- const stopReset = this.plugins.reduce((s, p) => {
773
- return s || (p.stopReset && p.stopReset(this.state.value));
774
- }, false);
775
-
776
- log('[resetValue]', value.isFocused, focusedNode, 'stopReset: ', stopReset);
777
- if ((this.state.stashedValue && !value.isFocused && !focusedNode && !stopReset) || force) {
778
- log('[resetValue] resetting...');
779
- log('stashed', this.state.stashedValue.document.toObject());
780
- log('current', this.state.value.document.toObject());
781
-
782
- const newValue = Value.fromJSON(this.state.stashedValue.toJSON());
783
-
784
- log('newValue: ', newValue.document);
785
- return new Promise((resolve) => {
786
- setTimeout(() => {
787
- this.setState({ value: newValue, stashedValue: null }, () => {
788
- log('value now: ', this.state.value.document.toJSON());
789
- resolve();
790
- });
791
- }, 50);
792
- });
793
- } else {
794
- return Promise.resolve({});
795
- }
796
- };
797
-
798
- onChange = (change, done) => {
799
- log('[onChange]');
800
- window.me = this;
801
-
802
- const { value } = change;
803
- const { charactersLimit } = this.props;
804
- let limit = charactersLimit;
805
- if (!limit || limit > MAX_CHARACTERS_LIMIT) {
806
- limit = MAX_CHARACTERS_LIMIT;
807
- }
808
-
809
- if (value && value.document && value.document.text && value.document.text.length > limit) {
810
- return;
811
- }
812
-
813
- // Mark the editor as edited when in HTML mode and its content has changed.
814
- // This status will later be used to decide whether to prompt a warning to the user when exiting HTML mode.
815
- const isEditedInHtmlMode = !this.state.isHtmlMode
816
- ? false
817
- : this.state.value.document.text !== value.document.text
818
- ? true
819
- : this.state.isEditedInHtmlMode;
820
-
821
- if (isEditedInHtmlMode != this.state.isEditedInHtmlMode) {
822
- this.handlePlugins(this.props);
823
- }
824
-
825
- this.setState({ value, isEditedInHtmlMode }, () => {
826
- log('[onChange], call done()');
827
-
828
- if (done) {
829
- done();
830
- }
831
- });
832
- };
833
-
834
- getFocusedValue = () => {
835
- if (this.state.value.isFocused) {
836
- return this.state.value;
837
- }
838
-
839
- return this.state.preBlurValue;
840
- };
841
-
842
- valueToSize = (v) => {
843
- if (!v) {
844
- return;
845
- }
846
- const calcRegex = /^calc\((.*)\)$/;
847
-
848
- if (typeof v === 'string') {
849
- if (v.endsWith('%')) {
850
- return undefined;
851
- } else if (
852
- v.endsWith('px') ||
853
- v.endsWith('vh') ||
854
- v.endsWith('vw') ||
855
- v.endsWith('ch') ||
856
- v.endsWith('em') ||
857
- v.match(calcRegex)
858
- ) {
859
- return v;
860
- } else {
861
- const value = parseInt(v, 10);
862
- return isNaN(value) ? value : `${value}px`;
863
- }
864
- }
865
- if (typeof v === 'number') {
866
- return `${v}px`;
867
- }
868
- };
869
-
870
- buildSizeStyle() {
871
- const { minWidth, width, maxWidth, minHeight, height, maxHeight } = this.props;
872
-
873
- return {
874
- width: this.valueToSize(width),
875
- minWidth: this.valueToSize(minWidth),
876
- maxWidth: this.valueToSize(maxWidth),
877
- height: this.valueToSize(height),
878
- minHeight: this.valueToSize(minHeight),
879
- maxHeight: this.valueToSize(maxHeight),
880
- };
881
- }
882
-
883
- validateNode = (node) => {
884
- if (node.object !== 'block') return;
885
-
886
- const last = node.nodes.last();
887
- if (!last) return;
888
-
889
- if (last.type !== 'image') return;
890
-
891
- log('[validateNode] last is image..');
892
-
893
- const parent = last.getParent(last.key);
894
- const p = Block.getParent(last.key);
895
- log('[validateNode] parent:', parent, p);
896
-
897
- return undefined;
898
- };
899
-
900
- changeData = (key, data) => {
901
- log('[changeData]. .. ', key, data);
902
-
903
- /**
904
- * HACK ALERT: We should be calling setState here and storing the change data:
905
- *
906
- * <code>this.setState({changeData: { key, data}})</code>
907
- * However this is causing issues with the Mathquill instance. The 'input' event stops firing on the element and no
908
- * more changes get through. The issues seem to be related to the promises in onBlur/onFocus. But removing these
909
- * brings it's own problems. A major clean up is planned for this component so I've decided to temporarily settle
910
- * on this hack rather than spend more time on this.
911
- */
912
-
913
- // Uncomment this line to see the bug described above.
914
- // this.setState({changeData: {key, data}})
915
-
916
- this.__TEMPORARY_CHANGE_DATA = { key, data };
917
- };
918
-
919
- focus = (pos, node) => {
920
- const position = pos || 'end';
921
-
922
- this.props.focus(position, node);
923
- };
924
-
925
- onDropPaste = async (event, change, dropContext) => {
926
- const editor = change.editor;
927
- const transfer = getEventTransfer(event);
928
- const file = transfer.files && transfer.files[0];
929
-
930
- const type = transfer.type;
931
- const fragment = transfer.fragment;
932
- const text = transfer.text;
933
-
934
- if (file && (file.type === 'image/jpeg' || file.type === 'image/jpg' || file.type === 'image/png')) {
935
- if (!this.props.imageSupport) {
936
- return;
937
- }
938
- try {
939
- log('[onDropPaste]');
940
- const src = await getBase64(file);
941
- const inline = Inline.create({
942
- type: 'image',
943
- isVoid: true,
944
- data: {
945
- loading: false,
946
- src,
947
- },
948
- });
949
-
950
- if (dropContext) {
951
- this.focus();
952
- } else {
953
- const range = getEventRange(event, editor);
954
- if (range) {
955
- change.select(range);
956
- }
957
- }
958
-
959
- const ch = change.insertInline(inline);
960
- this.onChange(ch);
961
- const handler = new InsertImageHandler(
962
- inline,
963
- () => {},
964
- () => this.state.value,
965
- this.onChange,
966
- true,
967
- );
968
- handler.fileChosen(file);
969
- this.props.imageSupport.add(handler);
970
- } catch (err) {
971
- log('[onDropPaste] error: ', err);
972
- }
973
- } else if (type === 'fragment') {
974
- change.insertFragment(fragment);
975
- } else if (type === 'text' || type === 'html') {
976
- if (!text) {
977
- return;
978
- }
979
- const {
980
- value: { document, selection, startBlock },
981
- } = change;
982
-
983
- if (startBlock.isVoid) {
984
- return;
985
- }
986
-
987
- const defaultBlock = startBlock;
988
- const defaultMarks = document.getInsertMarksAtRange(selection);
989
- const frag = Plain.deserialize(text, {
990
- defaultBlock,
991
- defaultMarks,
992
- }).document;
993
- change.insertFragment(frag);
994
- }
995
- };
996
-
997
- renderPlaceholder = (props) => {
998
- const { editor } = props;
999
- const { document } = editor.value;
1000
-
1001
- if (!editor.props.placeholder || document.text !== '' || document.nodes.size !== 1 || !document.isEmpty) {
1002
- return false;
1003
- }
1004
-
1005
- return (
1006
- <span
1007
- contentEditable={false}
1008
- style={{
1009
- display: 'inline-block',
1010
- width: 'fit-content', // for centering the placeholder if text-align is set to center
1011
- maxWidth: '100%',
1012
- whiteSpace: 'nowrap',
1013
- opacity: '0.33',
1014
- pointerEvents: 'none',
1015
- userSelect: 'none',
1016
- }}
1017
- >
1018
- {editor.props.placeholder}
1019
- </span>
1020
- );
1021
- };
1022
-
1023
- render() {
1024
- const {
1025
- disabled,
1026
- spellCheck,
1027
- highlightShape,
1028
- classes,
1029
- className,
1030
- isEditor,
1031
- placeholder,
1032
- pluginProps,
1033
- onKeyDown,
1034
- } = this.props;
1035
- // We don't want to send customPlugins to slate.
1036
- // Not sure if they would do any harm, but I think it's better to not send them.
1037
- // We use custom plugins to be able to add custom buttons
1038
- // eslint-disable-next-line no-unused-vars
1039
- const { customPlugins, showParagraphs, separateParagraphs, ...otherPluginProps } = pluginProps || {};
1040
-
1041
- const { value, focusedNode, toolbarOpts, dialog, scheduled } = this.state;
1042
-
1043
- log('[render] value: ', value);
1044
- const sizeStyle = this.buildSizeStyle();
1045
- const names = classNames(
1046
- {
1047
- [classes.withBg]: highlightShape,
1048
- [classes.toolbarOnTop]: toolbarOpts.alwaysVisible && toolbarOpts.position === 'top',
1049
- [classes.scheduled]: scheduled,
1050
- },
1051
- className,
1052
- );
1053
-
1054
- return (
1055
- <div
1056
- ref={(ref) => (this.wrapperRef = ref)}
1057
- style={{ width: sizeStyle.width, minWidth: sizeStyle.minWidth, maxWidth: sizeStyle.maxWidth }}
1058
- className={names}
1059
- id={`editor-${value?.document?.key}`}
1060
- >
1061
- {scheduled && <div className={classes.uploading}>Uploading image and then saving...</div>}
1062
- <SlateEditor
1063
- plugins={this.plugins}
1064
- innerRef={(r) => {
1065
- if (r) {
1066
- this.slateEditor = r;
1067
- }
1068
- }}
1069
- ref={(r) => (this.editor = r && this.props.editorRef(r))}
1070
- toolbarRef={(r) => {
1071
- if (r) {
1072
- this.toolbarRef = r;
1073
- }
1074
- }}
1075
- doneButtonRef={this.doneButtonRef}
1076
- value={value}
1077
- focusToolbar={this.state.focusToolbar}
1078
- onToolbarFocus={this.handleToolbarFocus}
1079
- onToolbarBlur={this.handleToolbarBlur}
1080
- focus={this.focus}
1081
- onKeyDown={onKeyDown}
1082
- onChange={this.onChange}
1083
- getFocusedValue={this.getFocusedValue}
1084
- onBlur={this.onBlur}
1085
- onDrop={(event, editor) => this.onDropPaste(event, editor, true)}
1086
- onPaste={(event, editor) => this.onDropPaste(event, editor)}
1087
- onFocus={this.onFocus}
1088
- onEditingDone={this.onEditingDone}
1089
- focusedNode={focusedNode}
1090
- normalize={this.normalize}
1091
- readOnly={disabled}
1092
- spellCheck={spellCheck}
1093
- autoCorrect={spellCheck}
1094
- className={classNames(
1095
- {
1096
- [classes.noPadding]: toolbarOpts?.noPadding,
1097
- [classes.showParagraph]: showParagraphs && !showParagraphs.disabled,
1098
- [classes.separateParagraph]: separateParagraphs && !separateParagraphs.disabled,
1099
- },
1100
- classes.slateEditor,
1101
- )}
1102
- style={{
1103
- minHeight: sizeStyle.minHeight,
1104
- height: sizeStyle.height,
1105
- maxHeight: sizeStyle.maxHeight,
1106
- }}
1107
- pluginProps={otherPluginProps}
1108
- toolbarOpts={toolbarOpts}
1109
- placeholder={placeholder}
1110
- renderPlaceholder={this.renderPlaceholder}
1111
- onDataChange={this.changeData}
1112
- />
1113
- <AlertDialog
1114
- open={dialog.open}
1115
- title={dialog.title}
1116
- text={dialog.text}
1117
- onClose={dialog.onClose}
1118
- onConfirm={dialog.onConfirm}
1119
- onConfirmText={dialog.onConfirmText}
1120
- onCloseText={dialog.onCloseText}
1121
- />
1122
- </div>
1123
- );
1124
- }
1125
- }
1126
-
1127
- // TODO color - hardcoded gray background and keypad colors will need to change too
1128
- const styles = {
1129
- withBg: {
1130
- backgroundColor: 'rgba(0,0,0,0.06)',
1131
- },
1132
- scheduled: {
1133
- opacity: 0.5,
1134
- pointerEvents: 'none',
1135
- position: 'relative',
1136
- },
1137
- uploading: {
1138
- position: 'absolute',
1139
- top: '50%',
1140
- left: '50%',
1141
- transform: 'translate(-50%, -50%)',
1142
- },
1143
- slateEditor: {
1144
- fontFamily: 'Roboto, sans-serif',
1145
-
1146
- '& table': {
1147
- tableLayout: 'fixed',
1148
- width: '100%',
1149
- borderCollapse: 'collapse',
1150
- color: color.text(),
1151
- backgroundColor: color.background(),
1152
- },
1153
- '& table:not([border="1"]) tr': {
1154
- borderTop: '1px solid #dfe2e5',
1155
- // TODO perhaps secondary color for background, for now disable
1156
- // '&:nth-child(2n)': {
1157
- // backgroundColor: '#f6f8fa'
1158
- // }
1159
- },
1160
- '& td, th': {
1161
- padding: '.6em 1em',
1162
- textAlign: 'center',
1163
- },
1164
- '& table:not([border="1"]) td, th': {
1165
- border: '1px solid #dfe2e5',
1166
- },
1167
- },
1168
- showParagraph: {
1169
- // a div that has a div after it
1170
- '& > div:has(+ div)::after': {
1171
- display: 'block',
1172
- content: '"¶"',
1173
- fontSize: '1em',
1174
- color: '#146EB3',
1175
- },
1176
- },
1177
- separateParagraph: {
1178
- // a div that has a div after it
1179
- '& > div:has(+ div)': {
1180
- marginBottom: '1em',
1181
- },
1182
- },
1183
- toolbarOnTop: {
1184
- marginTop: '45px',
1185
- },
1186
- noPadding: {
1187
- padding: '0 !important',
1188
- },
1189
- previewText: {
1190
- marginBottom: '36px',
1191
- marginTop: '6px',
1192
- padding: '20px',
1193
- backgroundColor: 'rgba(0,0,0,0.06)',
1194
- },
1195
- };
1196
-
1197
- export default withStyles(styles)(Editor);