@pie-lib/editable-html-tip-tap 1.0.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 (167) hide show
  1. package/CHANGELOG.json +32 -0
  2. package/CHANGELOG.md +2280 -0
  3. package/lib/__tests__/editor.test.js +470 -0
  4. package/lib/__tests__/serialization.test.js +246 -0
  5. package/lib/__tests__/utils.js +106 -0
  6. package/lib/block-tags.js +25 -0
  7. package/lib/constants.js +16 -0
  8. package/lib/editor.js +1356 -0
  9. package/lib/extensions/MediaView.js +112 -0
  10. package/lib/extensions/characters.js +65 -0
  11. package/lib/extensions/component.js +325 -0
  12. package/lib/extensions/css.js +252 -0
  13. package/lib/extensions/custom-toolbar-wrapper.js +124 -0
  14. package/lib/extensions/image.js +106 -0
  15. package/lib/extensions/math.js +330 -0
  16. package/lib/extensions/media.js +276 -0
  17. package/lib/extensions/responseArea.js +278 -0
  18. package/lib/index.js +1213 -0
  19. package/lib/old-index.js +269 -0
  20. package/lib/parse-html.js +16 -0
  21. package/lib/plugins/characters/custom-popper.js +73 -0
  22. package/lib/plugins/characters/index.js +305 -0
  23. package/lib/plugins/characters/utils.js +381 -0
  24. package/lib/plugins/css/icons/index.js +37 -0
  25. package/lib/plugins/css/index.js +390 -0
  26. package/lib/plugins/customPlugin/index.js +114 -0
  27. package/lib/plugins/html/icons/index.js +38 -0
  28. package/lib/plugins/html/index.js +81 -0
  29. package/lib/plugins/image/__tests__/component.test.js +51 -0
  30. package/lib/plugins/image/__tests__/image-toolbar-logic.test.js +56 -0
  31. package/lib/plugins/image/__tests__/image-toolbar.test.js +26 -0
  32. package/lib/plugins/image/__tests__/index.test.js +98 -0
  33. package/lib/plugins/image/__tests__/insert-image-handler.test.js +125 -0
  34. package/lib/plugins/image/__tests__/mock-change.js +25 -0
  35. package/lib/plugins/image/alt-dialog.js +129 -0
  36. package/lib/plugins/image/component.js +419 -0
  37. package/lib/plugins/image/image-toolbar.js +177 -0
  38. package/lib/plugins/image/index.js +263 -0
  39. package/lib/plugins/image/insert-image-handler.js +117 -0
  40. package/lib/plugins/index.js +413 -0
  41. package/lib/plugins/list/__tests__/index.test.js +79 -0
  42. package/lib/plugins/list/index.js +334 -0
  43. package/lib/plugins/math/__tests__/index.test.js +300 -0
  44. package/lib/plugins/math/index.js +454 -0
  45. package/lib/plugins/media/__tests__/index.test.js +71 -0
  46. package/lib/plugins/media/index.js +387 -0
  47. package/lib/plugins/media/media-dialog.js +709 -0
  48. package/lib/plugins/media/media-toolbar.js +101 -0
  49. package/lib/plugins/media/media-wrapper.js +93 -0
  50. package/lib/plugins/rendering/index.js +46 -0
  51. package/lib/plugins/respArea/drag-in-the-blank/choice.js +289 -0
  52. package/lib/plugins/respArea/drag-in-the-blank/index.js +94 -0
  53. package/lib/plugins/respArea/explicit-constructed-response/index.js +120 -0
  54. package/lib/plugins/respArea/icons/index.js +95 -0
  55. package/lib/plugins/respArea/index.js +341 -0
  56. package/lib/plugins/respArea/inline-dropdown/index.js +126 -0
  57. package/lib/plugins/respArea/math-templated/index.js +130 -0
  58. package/lib/plugins/respArea/utils.js +125 -0
  59. package/lib/plugins/table/CustomTablePlugin.js +133 -0
  60. package/lib/plugins/table/__tests__/index.test.js +442 -0
  61. package/lib/plugins/table/__tests__/table-toolbar.test.js +54 -0
  62. package/lib/plugins/table/icons/index.js +69 -0
  63. package/lib/plugins/table/index.js +483 -0
  64. package/lib/plugins/table/table-toolbar.js +187 -0
  65. package/lib/plugins/textAlign/icons/index.js +194 -0
  66. package/lib/plugins/textAlign/index.js +34 -0
  67. package/lib/plugins/toolbar/__tests__/default-toolbar.test.js +128 -0
  68. package/lib/plugins/toolbar/__tests__/editor-and-toolbar.test.js +51 -0
  69. package/lib/plugins/toolbar/__tests__/toolbar-buttons.test.js +54 -0
  70. package/lib/plugins/toolbar/__tests__/toolbar.test.js +120 -0
  71. package/lib/plugins/toolbar/default-toolbar.js +229 -0
  72. package/lib/plugins/toolbar/done-button.js +53 -0
  73. package/lib/plugins/toolbar/editor-and-toolbar.js +286 -0
  74. package/lib/plugins/toolbar/index.js +34 -0
  75. package/lib/plugins/toolbar/toolbar-buttons.js +194 -0
  76. package/lib/plugins/toolbar/toolbar.js +376 -0
  77. package/lib/plugins/utils.js +62 -0
  78. package/lib/serialization.js +677 -0
  79. package/lib/shared/alert-dialog.js +75 -0
  80. package/lib/theme.js +9 -0
  81. package/package.json +69 -0
  82. package/src/__tests__/editor.test.jsx +363 -0
  83. package/src/__tests__/serialization.test.js +291 -0
  84. package/src/__tests__/utils.js +36 -0
  85. package/src/block-tags.js +17 -0
  86. package/src/constants.js +7 -0
  87. package/src/editor.jsx +1197 -0
  88. package/src/extensions/characters.js +46 -0
  89. package/src/extensions/component.jsx +294 -0
  90. package/src/extensions/css.js +217 -0
  91. package/src/extensions/custom-toolbar-wrapper.jsx +100 -0
  92. package/src/extensions/image.js +55 -0
  93. package/src/extensions/math.js +259 -0
  94. package/src/extensions/media.js +182 -0
  95. package/src/extensions/responseArea.js +205 -0
  96. package/src/index.jsx +1462 -0
  97. package/src/old-index.jsx +162 -0
  98. package/src/parse-html.js +8 -0
  99. package/src/plugins/README.md +27 -0
  100. package/src/plugins/characters/custom-popper.js +48 -0
  101. package/src/plugins/characters/index.jsx +284 -0
  102. package/src/plugins/characters/utils.js +447 -0
  103. package/src/plugins/css/icons/index.jsx +17 -0
  104. package/src/plugins/css/index.jsx +340 -0
  105. package/src/plugins/customPlugin/index.jsx +85 -0
  106. package/src/plugins/html/icons/index.jsx +19 -0
  107. package/src/plugins/html/index.jsx +72 -0
  108. package/src/plugins/image/__tests__/__snapshots__/component.test.jsx.snap +51 -0
  109. package/src/plugins/image/__tests__/__snapshots__/image-toolbar-logic.test.jsx.snap +27 -0
  110. package/src/plugins/image/__tests__/__snapshots__/image-toolbar.test.jsx.snap +44 -0
  111. package/src/plugins/image/__tests__/component.test.jsx +41 -0
  112. package/src/plugins/image/__tests__/image-toolbar-logic.test.jsx +42 -0
  113. package/src/plugins/image/__tests__/image-toolbar.test.jsx +11 -0
  114. package/src/plugins/image/__tests__/index.test.js +95 -0
  115. package/src/plugins/image/__tests__/insert-image-handler.test.js +113 -0
  116. package/src/plugins/image/__tests__/mock-change.js +15 -0
  117. package/src/plugins/image/alt-dialog.jsx +82 -0
  118. package/src/plugins/image/component.jsx +343 -0
  119. package/src/plugins/image/image-toolbar.jsx +100 -0
  120. package/src/plugins/image/index.jsx +227 -0
  121. package/src/plugins/image/insert-image-handler.js +79 -0
  122. package/src/plugins/index.jsx +377 -0
  123. package/src/plugins/list/__tests__/index.test.js +54 -0
  124. package/src/plugins/list/index.jsx +305 -0
  125. package/src/plugins/math/__tests__/__snapshots__/index.test.jsx.snap +48 -0
  126. package/src/plugins/math/__tests__/index.test.jsx +245 -0
  127. package/src/plugins/math/index.jsx +379 -0
  128. package/src/plugins/media/__tests__/index.test.js +75 -0
  129. package/src/plugins/media/index.jsx +325 -0
  130. package/src/plugins/media/media-dialog.js +624 -0
  131. package/src/plugins/media/media-toolbar.jsx +56 -0
  132. package/src/plugins/media/media-wrapper.jsx +43 -0
  133. package/src/plugins/rendering/index.js +31 -0
  134. package/src/plugins/respArea/drag-in-the-blank/choice.jsx +215 -0
  135. package/src/plugins/respArea/drag-in-the-blank/index.jsx +70 -0
  136. package/src/plugins/respArea/explicit-constructed-response/index.jsx +92 -0
  137. package/src/plugins/respArea/icons/index.jsx +71 -0
  138. package/src/plugins/respArea/index.jsx +299 -0
  139. package/src/plugins/respArea/inline-dropdown/index.jsx +108 -0
  140. package/src/plugins/respArea/math-templated/index.jsx +104 -0
  141. package/src/plugins/respArea/utils.jsx +90 -0
  142. package/src/plugins/table/CustomTablePlugin.js +113 -0
  143. package/src/plugins/table/__tests__/__snapshots__/table-toolbar.test.jsx.snap +44 -0
  144. package/src/plugins/table/__tests__/index.test.jsx +401 -0
  145. package/src/plugins/table/__tests__/table-toolbar.test.jsx +42 -0
  146. package/src/plugins/table/icons/index.jsx +53 -0
  147. package/src/plugins/table/index.jsx +427 -0
  148. package/src/plugins/table/table-toolbar.jsx +136 -0
  149. package/src/plugins/textAlign/icons/index.jsx +114 -0
  150. package/src/plugins/textAlign/index.jsx +23 -0
  151. package/src/plugins/toolbar/__tests__/__snapshots__/default-toolbar.test.jsx.snap +923 -0
  152. package/src/plugins/toolbar/__tests__/__snapshots__/editor-and-toolbar.test.jsx.snap +20 -0
  153. package/src/plugins/toolbar/__tests__/__snapshots__/toolbar-buttons.test.jsx.snap +36 -0
  154. package/src/plugins/toolbar/__tests__/__snapshots__/toolbar.test.jsx.snap +46 -0
  155. package/src/plugins/toolbar/__tests__/default-toolbar.test.jsx +94 -0
  156. package/src/plugins/toolbar/__tests__/editor-and-toolbar.test.jsx +37 -0
  157. package/src/plugins/toolbar/__tests__/toolbar-buttons.test.jsx +51 -0
  158. package/src/plugins/toolbar/__tests__/toolbar.test.jsx +106 -0
  159. package/src/plugins/toolbar/default-toolbar.jsx +206 -0
  160. package/src/plugins/toolbar/done-button.jsx +38 -0
  161. package/src/plugins/toolbar/editor-and-toolbar.jsx +257 -0
  162. package/src/plugins/toolbar/index.jsx +23 -0
  163. package/src/plugins/toolbar/toolbar-buttons.jsx +138 -0
  164. package/src/plugins/toolbar/toolbar.jsx +338 -0
  165. package/src/plugins/utils.js +31 -0
  166. package/src/serialization.jsx +621 -0
  167. package/src/theme.js +1 -0
@@ -0,0 +1,299 @@
1
+ import React from 'react';
2
+ import debug from 'debug';
3
+ import isUndefined from 'lodash/isUndefined';
4
+
5
+ import InlineDropdown from './inline-dropdown';
6
+ import DragInTheBlank from './drag-in-the-blank';
7
+ import ExplicitConstructedResponse from './explicit-constructed-response';
8
+ import MathTemplated from './math-templated';
9
+ import { getDefaultElement } from './utils';
10
+ import { ToolbarIcon } from './icons';
11
+
12
+ const log = debug('@pie-lib:editable-html:plugins:respArea');
13
+
14
+ const lastIndexMap = {};
15
+ const elTypesMap = {
16
+ 'inline-dropdown': 'inline_dropdown',
17
+ 'explicit-constructed-response': 'explicit_constructed_response',
18
+ 'math-templated': 'math_templated',
19
+ 'drag-in-the-blank': 'drag_in_the_blank',
20
+ };
21
+ const elTypesArray = Object.values(elTypesMap);
22
+
23
+ export default function ResponseAreaPlugin(opts) {
24
+ const isOfCurrentType = (d) => d.type === opts.type || d.type === elTypesMap[opts.type];
25
+
26
+ const toolbar = {
27
+ icon: <ToolbarIcon />,
28
+ buttonStyles: {
29
+ margin: '0 20px 0 auto',
30
+ },
31
+ onClick: (value, onChange) => {
32
+ log('[toolbar] onClick');
33
+ const change = value.change();
34
+ const currentRespAreaList = change.value.document.filterDescendants(isOfCurrentType);
35
+
36
+ if (currentRespAreaList.size >= opts.maxResponseAreas) {
37
+ return;
38
+ }
39
+
40
+ const type = opts.type.replace(/-/g, '_');
41
+ const prevIndex = lastIndexMap[type];
42
+ const newIndex = prevIndex === 0 ? prevIndex : prevIndex + 1;
43
+ const newInline = getDefaultElement(opts, newIndex);
44
+
45
+ lastIndexMap[type] += 1;
46
+
47
+ if (newInline) {
48
+ if (change.value.selection.startKey || change.value.selection.endKey) {
49
+ change.insertInline(newInline);
50
+ } else {
51
+ // If the markup is empty and there's no focus
52
+ const lastText = value.document.getLastText();
53
+
54
+ if (!lastText) {
55
+ return;
56
+ }
57
+ const parentNode = value.document.getParent(lastText.key);
58
+
59
+ if (parentNode) {
60
+ const index = parentNode.nodes.indexOf(lastText.key);
61
+
62
+ if (parentNode.isVoid) return;
63
+
64
+ change.insertNodeByKey(parentNode.key, index + 1, newInline);
65
+ }
66
+ }
67
+
68
+ if (newInline.type === 'drag_in_the_blank') {
69
+ const nextText = change.value.document.getNextText(newInline.key);
70
+
71
+ if (nextText) {
72
+ change.moveFocusTo(nextText.key, 0).moveAnchorTo(nextText.key, 0);
73
+ }
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
+ }
82
+
83
+ onChange(change);
84
+ }
85
+ },
86
+ customToolbar: opts.respAreaToolbar,
87
+ supports: (node) => node.object === 'inline' && elTypesArray.indexOf(node.type) >= 0,
88
+ showDone: false,
89
+ };
90
+
91
+ return {
92
+ name: 'response_area',
93
+ toolbar,
94
+ filterPlugins: (node, plugins) => {
95
+ if (
96
+ node.type === 'explicit_constructed_response' ||
97
+ node.type === 'math_templated' ||
98
+ node.type === 'drag_in_the_blank'
99
+ ) {
100
+ return [];
101
+ }
102
+
103
+ return plugins.filter((p) => p.name !== 'response_area');
104
+ },
105
+ deleteNode: (e, node, value, onChange) => {
106
+ e.preventDefault();
107
+
108
+ const change = value.change().removeNodeByKey(node.key);
109
+
110
+ onChange(change);
111
+ },
112
+ renderNode(props) {
113
+ const { attributes, node: n, isFocused } = props;
114
+
115
+ if (n.type === 'explicit_constructed_response') {
116
+ const data = n.data.toJSON();
117
+ let error;
118
+
119
+ if (opts.error) {
120
+ error = opts.error();
121
+ }
122
+
123
+ return (
124
+ <ExplicitConstructedResponse
125
+ attributes={attributes}
126
+ isFocused={isFocused}
127
+ value={data.value}
128
+ error={error && error[data.index] && error[data.index][0]}
129
+ />
130
+ );
131
+ }
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
+
154
+ if (n.type === 'drag_in_the_blank') {
155
+ const data = n.data.toJSON();
156
+
157
+ return <DragInTheBlank attributes={attributes} data={data} n={n} nodeProps={props} opts={opts} />;
158
+ }
159
+
160
+ if (n.type === 'inline_dropdown') {
161
+ const data = n.data.toJSON();
162
+
163
+ return <InlineDropdown attributes={attributes} selectedItem={data.value} />;
164
+ }
165
+ },
166
+ onChange(change, editor) {
167
+ const type = opts.type.replace(/-/g, '_');
168
+
169
+ if (isUndefined(lastIndexMap[type])) {
170
+ lastIndexMap[type] = 0;
171
+
172
+ change.value.document.forEachDescendant((d) => {
173
+ if (d.type === type) {
174
+ const newIndex = parseInt(d.data.get('index'), 10);
175
+
176
+ if (newIndex > lastIndexMap[type]) {
177
+ lastIndexMap[type] = newIndex;
178
+ }
179
+ }
180
+ });
181
+ }
182
+
183
+ if (!editor.value) {
184
+ return;
185
+ }
186
+
187
+ const currentRespAreaList = change.value.document.filterDescendants(isOfCurrentType);
188
+ const oldRespAreaList = editor.value.document.filterDescendants(isOfCurrentType);
189
+
190
+ toolbar.disabled = currentRespAreaList.size >= opts.maxResponseAreas;
191
+
192
+ const arrayToFilter = oldRespAreaList.size > currentRespAreaList.size ? oldRespAreaList : currentRespAreaList;
193
+ const arrayToUseForFilter = arrayToFilter === oldRespAreaList ? currentRespAreaList : oldRespAreaList;
194
+
195
+ const elementsWithChangedStatus = arrayToFilter.filter(
196
+ (d) => !arrayToUseForFilter.find((e) => e.data.get('index') === d.data.get('index')),
197
+ );
198
+
199
+ if (elementsWithChangedStatus.size && oldRespAreaList.size > currentRespAreaList.size) {
200
+ opts.onHandleAreaChange(elementsWithChangedStatus);
201
+ }
202
+ },
203
+ onDrop(event, change, editor) {
204
+ const closestEl = event.target.closest('[data-key]');
205
+ const inline = editor.value.document.findDescendant((d) => d.key === closestEl.dataset.key);
206
+
207
+ if (inline.type === 'drag_in_the_blank') {
208
+ return false;
209
+ }
210
+ },
211
+ };
212
+ }
213
+
214
+ export const serialization = {
215
+ deserialize(el) {
216
+ const type = el.dataset && el.dataset.type;
217
+
218
+ switch (type) {
219
+ case 'inline_dropdown':
220
+ return {
221
+ object: 'inline',
222
+ type: 'inline_dropdown',
223
+ isVoid: true,
224
+ data: {
225
+ index: el.dataset.index,
226
+ value: el.dataset.value,
227
+ },
228
+ };
229
+ case 'explicit_constructed_response':
230
+ return {
231
+ object: 'inline',
232
+ type: 'explicit_constructed_response',
233
+ isVoid: true,
234
+ data: {
235
+ index: el.dataset.index,
236
+ value: el.dataset.value,
237
+ },
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
+ };
249
+ case 'drag_in_the_blank':
250
+ return {
251
+ object: 'inline',
252
+ type: 'drag_in_the_blank',
253
+ isVoid: true,
254
+ data: {
255
+ index: el.dataset.index,
256
+ id: el.dataset.id,
257
+ value: el.dataset.value,
258
+ inTable: el.dataset.inTable,
259
+ },
260
+ };
261
+ }
262
+ },
263
+ serialize(object) {
264
+ if (object.object !== 'inline') {
265
+ return;
266
+ }
267
+
268
+ switch (object.type) {
269
+ case 'inline_dropdown': {
270
+ const data = object.data.toJSON();
271
+
272
+ return <span data-type="inline_dropdown" data-index={data.index} data-value={data.value} />;
273
+ }
274
+ case 'explicit_constructed_response': {
275
+ const data = object.data.toJSON();
276
+
277
+ return <span data-type="explicit_constructed_response" data-index={data.index} data-value={data.value} />;
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
+ }
284
+ case 'drag_in_the_blank': {
285
+ const data = object.data.toJSON();
286
+
287
+ return (
288
+ <span
289
+ data-type="drag_in_the_blank"
290
+ data-index={data.index}
291
+ data-id={data.id}
292
+ data-value={data.value}
293
+ data-in-table={data.inTable}
294
+ />
295
+ );
296
+ }
297
+ }
298
+ },
299
+ };
@@ -0,0 +1,108 @@
1
+ import React, { useEffect, useRef, useState } from "react";
2
+ import PropTypes from 'prop-types';
3
+ import { NodeViewWrapper } from '@tiptap/react';
4
+ import { Chevron } from '../icons';
5
+
6
+ const InlineDropdown = (props) => {
7
+ const { editor, node, getPos, options, selected } = props;
8
+ const { attrs: attributes } = node;
9
+ const { value, error } = attributes;
10
+ // TODO: Investigate
11
+ // Needed because items with values inside have different positioning for some reason
12
+ const html = value || '<div>&nbsp</div>';
13
+ const toolbarRef = useRef(null);
14
+ const [showToolbar, setShowToolbar] = useState(false);
15
+ const InlineDropdownToolbar = options.respAreaToolbar(node, editor, () => {});
16
+
17
+ useEffect(() => {
18
+ setShowToolbar(selected);
19
+ }, [selected]);
20
+
21
+ useEffect(() => {
22
+ const handleClickOutside = (event) => {
23
+ if (
24
+ toolbarRef.current &&
25
+ !toolbarRef.current.contains(event.target) &&
26
+ !event.target.closest('[data-inline-node]')
27
+ ) {
28
+ setShowToolbar(false);
29
+ }
30
+ };
31
+
32
+ if (showToolbar) {
33
+ document.addEventListener('mousedown', handleClickOutside);
34
+ } else {
35
+ document.removeEventListener('mousedown', handleClickOutside);
36
+ }
37
+
38
+ return () => document.removeEventListener('mousedown', handleClickOutside);
39
+ }, [showToolbar]);
40
+
41
+ return (
42
+ <NodeViewWrapper
43
+ className="inline-dropdown"
44
+ data-selected={selected}
45
+ style={{
46
+ display: 'inline-flex',
47
+ height: '50px',
48
+ margin: '0 5px',
49
+ cursor: 'pointer',
50
+ }}
51
+ >
52
+ <div
53
+ style={{
54
+ display: 'inline-flex',
55
+ minWidth: '178px',
56
+ height: '36px',
57
+ background: '#FFF',
58
+ border: '1px solid #C0C3CF',
59
+ boxSizing: 'border-box',
60
+ borderRadius: '3px',
61
+ margin: '0 4px',
62
+ position: 'relative',
63
+ alignItems: 'center',
64
+ }}
65
+ >
66
+ <div
67
+ style={{
68
+ flex: 1,
69
+ overflow: 'hidden',
70
+ padding: '0 25px 0 8px',
71
+ whiteSpace: 'nowrap',
72
+ textOverflow: 'ellipsis',
73
+ }}
74
+ >
75
+ <span
76
+ style={{
77
+ display: 'inline-block',
78
+ verticalAlign: 'middle',
79
+ }}
80
+ dangerouslySetInnerHTML={{
81
+ __html: html,
82
+ }}
83
+ />
84
+ </div>
85
+ <Chevron
86
+ direction="down"
87
+ style={{
88
+ position: 'absolute',
89
+ top: '5px',
90
+ right: '5px',
91
+ }}
92
+ />
93
+ </div>
94
+ {showToolbar && (
95
+ <div ref={toolbarRef} className="absolute z-50 bg-white shadow-lg rounded p-2" style={{ zIndex: 1 }}>
96
+ <InlineDropdownToolbar />
97
+ </div>
98
+ )}
99
+ </NodeViewWrapper>
100
+ );
101
+ };
102
+
103
+ InlineDropdown.propTypes = {
104
+ attributes: PropTypes.object,
105
+ selectedItem: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
106
+ };
107
+
108
+ export default InlineDropdown;
@@ -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);
@@ -0,0 +1,90 @@
1
+ import React from 'react';
2
+ import ReactDOM from 'react-dom';
3
+ import { Inline } from 'slate';
4
+ import Snackbar from '@material-ui/core/Snackbar';
5
+
6
+ export const isNumber = (val) => !isNaN(parseFloat(val)) && isFinite(val);
7
+
8
+ export const insertSnackBar = (message) => {
9
+ const prevSnacks = document.querySelectorAll('.response-area-alert');
10
+
11
+ prevSnacks.forEach((s) => s.remove());
12
+
13
+ const newEl = document.createElement('div');
14
+
15
+ newEl.className = 'response-area-alert';
16
+
17
+ const el = (
18
+ <Snackbar
19
+ anchorOrigin={{ vertical: 'top', horizontal: 'center' }}
20
+ open={true}
21
+ ContentProps={{
22
+ 'aria-describedby': 'message-id',
23
+ }}
24
+ message={<span id="message-id">{message}</span>}
25
+ />
26
+ );
27
+
28
+ ReactDOM.render(el, newEl);
29
+
30
+ document.body.appendChild(newEl);
31
+
32
+ setTimeout(() => {
33
+ newEl.remove();
34
+ }, 2000);
35
+ };
36
+
37
+ export const defaultECR = (index) =>
38
+ Inline.create({
39
+ type: 'explicit_constructed_response',
40
+ isVoid: true,
41
+ data: {
42
+ index,
43
+ },
44
+ });
45
+ export const defaultMT = (index) =>
46
+ Inline.create({
47
+ type: 'math_templated',
48
+ isVoid: true,
49
+ data: {
50
+ index,
51
+ },
52
+ });
53
+
54
+ export const defaultDIB = (opts, index) =>
55
+ Inline.create({
56
+ type: 'drag_in_the_blank',
57
+ isVoid: true,
58
+ data: {
59
+ index,
60
+ duplicates: opts.options.duplicates,
61
+ value: null,
62
+ },
63
+ });
64
+
65
+ export const defaultIDD = (index) =>
66
+ Inline.create({
67
+ object: 'inline',
68
+ type: 'inline_dropdown',
69
+ isVoid: true,
70
+ data: {
71
+ index,
72
+ },
73
+ });
74
+
75
+ export const getDefaultElement = (opts, index) => {
76
+ switch (opts.type) {
77
+ case 'explicit-constructed-response':
78
+ return defaultECR(index);
79
+
80
+ case 'math-templated':
81
+ return defaultMT(index);
82
+
83
+ case 'drag-in-the-blank':
84
+ return defaultDIB(opts, index);
85
+
86
+ default:
87
+ // inline-dropdown
88
+ return defaultIDD(index);
89
+ }
90
+ };
@@ -0,0 +1,113 @@
1
+ import EditTable from 'slate-edit-table';
2
+ import { isSelectionInTable } from 'slate-edit-table/dist/utils';
3
+ import { onEnter, onModEnter, onTab, onUpDown } from 'slate-edit-table/dist/handlers';
4
+ import TableOptions from 'slate-edit-table/dist/options';
5
+ import { clearCell } from 'slate-edit-table/dist/changes';
6
+
7
+ function onBackspace(event, change, editor, opts) {
8
+ const { value } = change;
9
+ const { startBlock, endBlock, selection, document } = value;
10
+
11
+ const startCell = document.getClosest(startBlock.key, opts.isCell);
12
+ const endCell = document.getClosest(endBlock.key, opts.isCell);
13
+
14
+ const startBlockIndex = startCell?.nodes?.findIndex((block) => block.key == startBlock.key);
15
+
16
+ // If a cursor is collapsed at the start of the first block, do nothing
17
+ if (startBlockIndex === 0 && selection.isAtStartOf(startBlock)) {
18
+ if (startBlock.isVoid) {
19
+ // Delete the block normally if it is a void block
20
+ return undefined;
21
+ }
22
+
23
+ event.preventDefault();
24
+ return change;
25
+ }
26
+
27
+ // If "normal" deletion, we continue
28
+ if (startCell === endCell) {
29
+ return undefined;
30
+ }
31
+
32
+ // If cursor is between multiple blocks,
33
+ // we clear the content of the cells.
34
+ event.preventDefault();
35
+
36
+ const { blocks } = value;
37
+
38
+ // Get all cells that contains the selection
39
+ const cells = blocks
40
+ .map((node) =>
41
+ node.type === opts.typeCell ? node : document.getClosest(node.key, (a) => a.type === opts.typeCell),
42
+ )
43
+ .toSet();
44
+
45
+ // If the cursor is at the very end of the first cell, ignore it.
46
+ // If the cursor is at the very start of the last cell, ignore it.
47
+ // This behavior is to compensate hanging selection behaviors:
48
+ // https://github.com/ianstormtaylor/slate/pull/1605
49
+ const ignoreFirstCell = value.selection.collapseToStart().isAtEndOf(cells.first());
50
+ const ignoreLastCell = value.selection.collapseToEnd().isAtStartOf(cells.last());
51
+
52
+ let cellsToClear = cells;
53
+ if (ignoreFirstCell) {
54
+ cellsToClear = cellsToClear.rest();
55
+ }
56
+ if (ignoreLastCell) {
57
+ cellsToClear = cellsToClear.butLast();
58
+ }
59
+
60
+ // Clear all the selection
61
+ cellsToClear.forEach((cell) => clearCell(opts, change, cell));
62
+
63
+ // Update the selection properly, and avoid reset of selection
64
+ const updatedStartCell = change.value.document.getDescendant(cellsToClear.first().key);
65
+ return change.collapseToStartOf(updatedStartCell);
66
+ }
67
+
68
+ const KEY_ENTER = 'Enter';
69
+ const KEY_TAB = 'Tab';
70
+ const KEY_BACKSPACE = 'Backspace';
71
+ const KEY_DOWN = 'ArrowDown';
72
+ const KEY_UP = 'ArrowUp';
73
+
74
+ /**
75
+ * User is pressing a key in the editor
76
+ */
77
+ function onKeyDown(opts, event, change, editor) {
78
+ // Only handle events in cells
79
+ if (!isSelectionInTable(opts, change.value)) {
80
+ return undefined;
81
+ }
82
+
83
+ // Build arguments list
84
+ const args = [event, change, editor, opts];
85
+
86
+ switch (event.key) {
87
+ case KEY_ENTER:
88
+ if (event.metaKey && opts.exitBlockType) {
89
+ return onModEnter(...args);
90
+ }
91
+ return onEnter(...args);
92
+
93
+ case KEY_TAB:
94
+ return onTab(...args);
95
+ case KEY_BACKSPACE:
96
+ return onBackspace(...args);
97
+ case KEY_DOWN:
98
+ case KEY_UP:
99
+ return onUpDown(...args);
100
+ default:
101
+ return undefined;
102
+ }
103
+ }
104
+
105
+ export default (opts) => {
106
+ const core = EditTable(opts);
107
+
108
+ const tableOpts = new TableOptions(opts);
109
+
110
+ core.onKeyDown = onKeyDown.bind(null, tableOpts);
111
+
112
+ return core;
113
+ };