@pie-lib/mask-markup 2.0.0-beta.2 → 2.1.0-next.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.
- package/CHANGELOG.json +1 -871
- package/CHANGELOG.md +296 -2
- package/LICENSE.md +5 -0
- package/NEXT.CHANGELOG.json +1 -0
- package/lib/__tests__/drag-in-the-blank.test.js +129 -0
- package/lib/__tests__/index.test.js +42 -0
- package/lib/__tests__/mask.test.js +163 -0
- package/lib/__tests__/serialization.test.js +44 -0
- package/lib/__tests__/utils.js +14 -0
- package/lib/__tests__/with-mask.test.js +110 -0
- package/lib/choices/__tests__/index.test.js +101 -0
- package/lib/choices/choice.js +100 -119
- package/lib/choices/choice.js.map +1 -1
- package/lib/choices/index.js +24 -20
- package/lib/choices/index.js.map +1 -1
- package/lib/componentize.js +2 -3
- package/lib/componentize.js.map +1 -1
- package/lib/components/__tests__/blank.test.js +189 -0
- package/lib/components/__tests__/correct-input.test.js +132 -0
- package/lib/components/__tests__/dropdown.test.js +134 -0
- package/lib/components/__tests__/input.test.js +129 -0
- package/lib/components/blank.js +316 -222
- package/lib/components/blank.js.map +1 -1
- package/lib/components/correct-input.js +40 -43
- package/lib/components/correct-input.js.map +1 -1
- package/lib/components/dropdown.js +394 -125
- package/lib/components/dropdown.js.map +1 -1
- package/lib/components/input.js +2 -3
- package/lib/components/input.js.map +1 -1
- package/lib/constructed-response.js +83 -27
- package/lib/constructed-response.js.map +1 -1
- package/lib/customizable.js +44 -0
- package/lib/customizable.js.map +1 -0
- package/lib/drag-in-the-blank.js +155 -62
- package/lib/drag-in-the-blank.js.map +1 -1
- package/lib/index.js +8 -1
- package/lib/index.js.map +1 -1
- package/lib/inline-dropdown.js +5 -4
- package/lib/inline-dropdown.js.map +1 -1
- package/lib/mask.js +90 -57
- package/lib/mask.js.map +1 -1
- package/lib/serialization.js +31 -43
- package/lib/serialization.js.map +1 -1
- package/lib/with-mask.js +49 -21
- package/lib/with-mask.js.map +1 -1
- package/package.json +18 -15
- package/src/__tests__/drag-in-the-blank.test.js +111 -0
- package/src/__tests__/index.test.js +39 -0
- package/src/__tests__/mask.test.js +187 -0
- package/src/__tests__/serialization.test.js +54 -0
- package/src/__tests__/utils.js +1 -0
- package/src/__tests__/with-mask.test.js +76 -0
- package/src/choices/__tests__/index.test.js +75 -0
- package/src/choices/choice.jsx +83 -96
- package/src/choices/index.jsx +11 -5
- package/src/components/__tests__/blank.test.js +138 -0
- package/src/components/__tests__/correct-input.test.js +90 -0
- package/src/components/__tests__/dropdown.test.js +93 -0
- package/src/components/__tests__/input.test.js +102 -0
- package/src/components/blank.jsx +316 -204
- package/src/components/correct-input.jsx +37 -38
- package/src/components/dropdown.jsx +371 -125
- package/src/constructed-response.jsx +71 -18
- package/src/customizable.jsx +35 -0
- package/src/drag-in-the-blank.jsx +152 -40
- package/src/index.js +10 -1
- package/src/inline-dropdown.jsx +2 -0
- package/src/mask.jsx +71 -25
- package/src/serialization.js +22 -34
- package/src/with-mask.jsx +43 -3
- package/README.md +0 -14
- package/lib/new-serialization.js +0 -267
- package/lib/new-serialization.js.map +0 -1
- package/lib/parse-html.js +0 -17
- package/lib/parse-html.js.map +0 -1
- package/lib/test-serializer.js +0 -164
- package/lib/test-serializer.js.map +0 -1
- package/src/new-serialization.jsx +0 -291
- package/src/parse-html.js +0 -8
- package/src/test-serializer.js +0 -163
|
@@ -1,16 +1,28 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
|
-
import {
|
|
3
|
+
import { DragProvider } from '@pie-lib/drag';
|
|
4
|
+
import { DragOverlay, closestCenter } from '@dnd-kit/core';
|
|
5
|
+
|
|
4
6
|
import Choices from './choices';
|
|
7
|
+
import Choice from './choices/choice';
|
|
5
8
|
import Blank from './components/blank';
|
|
6
9
|
import { withMask } from './with-mask';
|
|
7
10
|
|
|
8
|
-
// eslint-disable-next-line react/display-name
|
|
9
11
|
const Masked = withMask('blank', (props) => (node, data, onChange) => {
|
|
10
|
-
const dataset = node.data
|
|
12
|
+
const dataset = node.data?.dataset || {};
|
|
11
13
|
if (dataset.component === 'blank') {
|
|
12
14
|
// eslint-disable-next-line react/prop-types
|
|
13
|
-
const {
|
|
15
|
+
const {
|
|
16
|
+
disabled,
|
|
17
|
+
duplicates,
|
|
18
|
+
correctResponse,
|
|
19
|
+
feedback,
|
|
20
|
+
showCorrectAnswer,
|
|
21
|
+
emptyResponseAreaWidth,
|
|
22
|
+
emptyResponseAreaHeight,
|
|
23
|
+
instanceId,
|
|
24
|
+
isDragging
|
|
25
|
+
} = props;
|
|
14
26
|
const choiceId = showCorrectAnswer ? correctResponse[dataset.id] : data[dataset.id];
|
|
15
27
|
// eslint-disable-next-line react/prop-types
|
|
16
28
|
const choice = choiceId && props.choices.find((c) => c.id === choiceId);
|
|
@@ -23,18 +35,37 @@ const Masked = withMask('blank', (props) => (node, data, onChange) => {
|
|
|
23
35
|
duplicates={duplicates}
|
|
24
36
|
choice={choice}
|
|
25
37
|
id={dataset.id}
|
|
26
|
-
|
|
38
|
+
emptyResponseAreaWidth={emptyResponseAreaWidth}
|
|
39
|
+
emptyResponseAreaHeight={emptyResponseAreaHeight}
|
|
40
|
+
onChange={(id, choiceId) => {
|
|
41
|
+
const newData = { ...data };
|
|
42
|
+
if (choiceId === undefined) {
|
|
43
|
+
delete newData[id];
|
|
44
|
+
} else {
|
|
45
|
+
newData[id] = choiceId;
|
|
46
|
+
}
|
|
47
|
+
onChange(newData);
|
|
48
|
+
}}
|
|
49
|
+
instanceId={instanceId}
|
|
50
|
+
isDragging={isDragging}
|
|
27
51
|
/>
|
|
28
52
|
);
|
|
29
53
|
}
|
|
30
54
|
});
|
|
31
55
|
|
|
32
56
|
export default class DragInTheBlank extends React.Component {
|
|
57
|
+
constructor(props) {
|
|
58
|
+
super(props);
|
|
59
|
+
this.state = {
|
|
60
|
+
activeDragItem: null,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
33
64
|
static propTypes = {
|
|
34
65
|
markup: PropTypes.string,
|
|
35
66
|
layout: PropTypes.object,
|
|
36
67
|
choicesPosition: PropTypes.string,
|
|
37
|
-
choices: PropTypes.
|
|
68
|
+
choices: PropTypes.array,
|
|
38
69
|
value: PropTypes.object,
|
|
39
70
|
onChange: PropTypes.func,
|
|
40
71
|
duplicates: PropTypes.bool,
|
|
@@ -42,27 +73,93 @@ export default class DragInTheBlank extends React.Component {
|
|
|
42
73
|
feedback: PropTypes.object,
|
|
43
74
|
correctResponse: PropTypes.object,
|
|
44
75
|
showCorrectAnswer: PropTypes.bool,
|
|
76
|
+
emptyResponseAreaWidth: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
|
77
|
+
emptyResponseAreaHeight: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
|
78
|
+
instanceId: PropTypes.string,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
static defaultProps = {
|
|
82
|
+
instanceId: 'drag-in-the-blank',
|
|
45
83
|
};
|
|
46
84
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
85
|
+
handleDragStart = (event) => {
|
|
86
|
+
const { active } = event;
|
|
87
|
+
|
|
88
|
+
if (active?.data?.current) {
|
|
89
|
+
this.setState({
|
|
90
|
+
activeDragItem: active.data.current,
|
|
91
|
+
});
|
|
50
92
|
}
|
|
51
|
-
}
|
|
93
|
+
};
|
|
52
94
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
95
|
+
renderDragOverlay = () => {
|
|
96
|
+
const { activeDragItem } = this.state;
|
|
97
|
+
if (!activeDragItem) return null;
|
|
98
|
+
|
|
99
|
+
if (activeDragItem.type === 'MaskBlank') {
|
|
100
|
+
return (
|
|
101
|
+
<Choice
|
|
102
|
+
disabled={activeDragItem.disabled}
|
|
103
|
+
choice={activeDragItem.choice}
|
|
104
|
+
instanceId={activeDragItem.instanceId}
|
|
105
|
+
/>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return null;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
handleDragEnd = (event) => {
|
|
113
|
+
const { active, over } = event;
|
|
114
|
+
const { onChange, value } = this.props;
|
|
115
|
+
|
|
116
|
+
if (!over || !active || !onChange) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const draggedData = active.data.current;
|
|
121
|
+
const dropData = over.data.current;
|
|
122
|
+
|
|
123
|
+
if (draggedData?.type === 'MaskBlank' && dropData?.accepts?.includes('MaskBlank')) {
|
|
124
|
+
const draggedItem = draggedData;
|
|
125
|
+
const targetId = dropData.id;
|
|
126
|
+
|
|
127
|
+
// drop from choice to blank (placing choice into response)
|
|
128
|
+
if (draggedItem.fromChoice === true) {
|
|
129
|
+
const newValue = { ...value };
|
|
130
|
+
newValue[targetId] = draggedItem.choice.id;
|
|
131
|
+
onChange(newValue);
|
|
132
|
+
} else if (dropData.toChoiceBoard === true) {
|
|
133
|
+
// handle drop from blank to choice board (removal from blank)
|
|
134
|
+
const newValue = { ...value };
|
|
135
|
+
delete newValue[draggedItem.id];
|
|
136
|
+
onChange(newValue);
|
|
137
|
+
}
|
|
138
|
+
// handle drop from blank to blank (changing position)
|
|
139
|
+
else if (draggedItem.id !== targetId) {
|
|
140
|
+
const newValue = { ...value };
|
|
141
|
+
newValue[targetId] = draggedItem.choice.id;
|
|
142
|
+
delete newValue[draggedItem.id];
|
|
143
|
+
onChange(newValue);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
this.setState({ activeDragItem: null });
|
|
147
|
+
};
|
|
56
148
|
|
|
57
149
|
getPositionDirection = (choicePosition) => {
|
|
58
150
|
let flexDirection;
|
|
151
|
+
let justifyContent;
|
|
152
|
+
let alignItems;
|
|
59
153
|
|
|
60
154
|
switch (choicePosition) {
|
|
61
155
|
case 'left':
|
|
62
156
|
flexDirection = 'row';
|
|
157
|
+
alignItems = 'center';
|
|
63
158
|
break;
|
|
64
159
|
case 'right':
|
|
65
160
|
flexDirection = 'row-reverse';
|
|
161
|
+
justifyContent = 'flex-end';
|
|
162
|
+
alignItems = 'center';
|
|
66
163
|
break;
|
|
67
164
|
case 'below':
|
|
68
165
|
flexDirection = 'column-reverse';
|
|
@@ -73,14 +170,13 @@ export default class DragInTheBlank extends React.Component {
|
|
|
73
170
|
break;
|
|
74
171
|
}
|
|
75
172
|
|
|
76
|
-
return flexDirection;
|
|
173
|
+
return { flexDirection, justifyContent, alignItems };
|
|
77
174
|
};
|
|
78
175
|
|
|
79
176
|
render() {
|
|
80
177
|
const {
|
|
81
178
|
markup,
|
|
82
179
|
duplicates,
|
|
83
|
-
layout,
|
|
84
180
|
value,
|
|
85
181
|
onChange,
|
|
86
182
|
choicesPosition,
|
|
@@ -89,36 +185,52 @@ export default class DragInTheBlank extends React.Component {
|
|
|
89
185
|
disabled,
|
|
90
186
|
feedback,
|
|
91
187
|
showCorrectAnswer,
|
|
188
|
+
emptyResponseAreaWidth,
|
|
189
|
+
emptyResponseAreaHeight,
|
|
190
|
+
layout,
|
|
191
|
+
instanceId
|
|
92
192
|
} = this.props;
|
|
93
193
|
|
|
94
194
|
const choicePosition = choicesPosition || 'below';
|
|
95
|
-
const style = {
|
|
96
|
-
display: 'flex',
|
|
97
|
-
flexDirection: this.getPositionDirection(choicePosition),
|
|
98
|
-
};
|
|
195
|
+
const style = { display: 'flex', minWidth: '100px', ...this.getPositionDirection(choicePosition) };
|
|
99
196
|
|
|
100
197
|
return (
|
|
101
|
-
<
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
198
|
+
<DragProvider
|
|
199
|
+
onDragStart={this.handleDragStart}
|
|
200
|
+
onDragEnd={this.handleDragEnd}
|
|
201
|
+
collisionDetection={closestCenter}
|
|
202
|
+
>
|
|
203
|
+
<div ref={(ref) => (this.rootRef = ref)} style={style}>
|
|
204
|
+
<Choices
|
|
205
|
+
choicePosition={choicePosition}
|
|
206
|
+
choices={choices}
|
|
207
|
+
value={value}
|
|
208
|
+
duplicates={duplicates}
|
|
209
|
+
disabled={disabled}
|
|
210
|
+
instanceId={instanceId}
|
|
211
|
+
/>
|
|
212
|
+
<Masked
|
|
213
|
+
elementType="drag-in-the-blank"
|
|
214
|
+
markup={markup}
|
|
215
|
+
layout={layout}
|
|
216
|
+
value={value}
|
|
217
|
+
choices={choices}
|
|
218
|
+
onChange={onChange}
|
|
219
|
+
disabled={disabled}
|
|
220
|
+
duplicates={duplicates}
|
|
221
|
+
feedback={feedback}
|
|
222
|
+
correctResponse={correctResponse}
|
|
223
|
+
showCorrectAnswer={showCorrectAnswer}
|
|
224
|
+
emptyResponseAreaWidth={emptyResponseAreaWidth}
|
|
225
|
+
emptyResponseAreaHeight={emptyResponseAreaHeight}
|
|
226
|
+
instanceId={instanceId}
|
|
227
|
+
isDragging={!!this.state.activeDragItem}
|
|
228
|
+
/>
|
|
229
|
+
<DragOverlay style={{ pointerEvents: "none" }}>
|
|
230
|
+
{this.renderDragOverlay()}
|
|
231
|
+
</DragOverlay>
|
|
232
|
+
</div>
|
|
233
|
+
</DragProvider>
|
|
122
234
|
);
|
|
123
235
|
}
|
|
124
236
|
}
|
package/src/index.js
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
import { withMask, buildLayoutFromMarkup } from './with-mask';
|
|
2
2
|
import DragInTheBlank from './drag-in-the-blank';
|
|
3
3
|
import ConstructedResponse from './constructed-response';
|
|
4
|
+
import Customizable from './customizable';
|
|
4
5
|
import InlineDropdown from './inline-dropdown';
|
|
5
6
|
import componentize from './componentize';
|
|
6
7
|
|
|
7
|
-
export {
|
|
8
|
+
export {
|
|
9
|
+
withMask,
|
|
10
|
+
buildLayoutFromMarkup,
|
|
11
|
+
DragInTheBlank,
|
|
12
|
+
ConstructedResponse,
|
|
13
|
+
InlineDropdown,
|
|
14
|
+
componentize,
|
|
15
|
+
Customizable,
|
|
16
|
+
};
|
package/src/inline-dropdown.jsx
CHANGED
|
@@ -17,10 +17,12 @@ export default withMask('dropdown', (props) => (node, data, onChange) => {
|
|
|
17
17
|
correct={feedback && feedback[dataset.id] && feedback[dataset.id] === 'correct'}
|
|
18
18
|
disabled={disabled || showCorrectAnswer}
|
|
19
19
|
value={finalChoice}
|
|
20
|
+
correctValue={showCorrectAnswer ? correctAnswer && correctAnswer.label : undefined}
|
|
20
21
|
id={dataset.id}
|
|
21
22
|
onChange={onChange}
|
|
22
23
|
choices={choices[dataset.id]}
|
|
23
24
|
showCorrectAnswer={showCorrectAnswer}
|
|
25
|
+
singleQuery={Object.keys(choices).length == 1}
|
|
24
26
|
/>
|
|
25
27
|
);
|
|
26
28
|
}
|
package/src/mask.jsx
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
3
|
+
import get from 'lodash/get';
|
|
4
|
+
import { styled } from '@mui/material/styles';
|
|
5
|
+
import { renderMath } from '@pie-lib/math-rendering';
|
|
5
6
|
import { MARK_TAGS } from './serialization';
|
|
6
7
|
|
|
7
|
-
const Paragraph =
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
const Paragraph = styled('div')(({ theme }) => ({
|
|
9
|
+
paddingTop: theme.spacing(2),
|
|
10
|
+
paddingBottom: theme.spacing(2),
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
const Spacer = styled('span')(() => ({
|
|
14
|
+
display: 'inline-block',
|
|
15
|
+
width: '.75em',
|
|
16
|
+
}));
|
|
13
17
|
|
|
14
18
|
const restrictWhitespaceTypes = ['tbody', 'tr'];
|
|
15
19
|
|
|
@@ -25,24 +29,32 @@ const addText = (parentNode, text) => {
|
|
|
25
29
|
};
|
|
26
30
|
|
|
27
31
|
const getMark = (n) => {
|
|
28
|
-
const
|
|
32
|
+
const mark = n.leaves.find((leave) => get(leave, 'marks', []).length);
|
|
33
|
+
|
|
34
|
+
if (mark) {
|
|
35
|
+
return mark.marks[0];
|
|
36
|
+
}
|
|
29
37
|
|
|
30
|
-
return
|
|
38
|
+
return null;
|
|
31
39
|
};
|
|
32
40
|
|
|
33
|
-
export const renderChildren = (layout, value, onChange, rootRenderChildren, parentNode) => {
|
|
41
|
+
export const renderChildren = (layout, value, onChange, rootRenderChildren, parentNode, elementType) => {
|
|
34
42
|
if (!value) {
|
|
35
43
|
return null;
|
|
36
44
|
}
|
|
37
45
|
|
|
38
46
|
const children = [];
|
|
39
47
|
|
|
40
|
-
(layout.
|
|
41
|
-
const key = `${n.type}-${index}`;
|
|
48
|
+
(layout.nodes || []).forEach((n, index) => {
|
|
49
|
+
const key = n.type ? `${n.type}-${index}` : `${index}`;
|
|
42
50
|
|
|
43
51
|
if (n.isMath) {
|
|
44
52
|
children.push(
|
|
45
|
-
<span
|
|
53
|
+
<span
|
|
54
|
+
dangerouslySetInnerHTML={{
|
|
55
|
+
__html: `<math displaystyle="true">${n.nodes[0].innerHTML}</math>`,
|
|
56
|
+
}}
|
|
57
|
+
/>,
|
|
46
58
|
);
|
|
47
59
|
return children;
|
|
48
60
|
}
|
|
@@ -51,12 +63,19 @@ export const renderChildren = (layout, value, onChange, rootRenderChildren, pare
|
|
|
51
63
|
const c = rootRenderChildren(n, value, onChange);
|
|
52
64
|
if (c) {
|
|
53
65
|
children.push(c);
|
|
66
|
+
if (parentNode?.type !== 'td' && elementType === 'drag-in-the-blank') {
|
|
67
|
+
children.push(<Spacer key={`spacer-${index}`} />);
|
|
68
|
+
}
|
|
54
69
|
return;
|
|
55
70
|
}
|
|
56
71
|
}
|
|
57
72
|
|
|
58
|
-
if (
|
|
59
|
-
const content = n.
|
|
73
|
+
if (n.object === 'text') {
|
|
74
|
+
const content = n.leaves.reduce((acc, l) => {
|
|
75
|
+
const t = l.text;
|
|
76
|
+
const extraText = addText(parentNode, t);
|
|
77
|
+
return extraText ? acc + extraText : acc;
|
|
78
|
+
}, '');
|
|
60
79
|
const mark = getMark(n);
|
|
61
80
|
|
|
62
81
|
if (mark) {
|
|
@@ -72,14 +91,17 @@ export const renderChildren = (layout, value, onChange, rootRenderChildren, pare
|
|
|
72
91
|
}
|
|
73
92
|
} else if (content.length > 0) {
|
|
74
93
|
children.push(content);
|
|
94
|
+
if (parentNode?.type !== 'td' && elementType === 'drag-in-the-blank') {
|
|
95
|
+
children.push(<Spacer key={`spacer-${index}`} />);
|
|
96
|
+
}
|
|
75
97
|
}
|
|
76
98
|
} else {
|
|
77
|
-
const subNodes = renderChildren(n, value, onChange, rootRenderChildren, n);
|
|
99
|
+
const subNodes = renderChildren(n, value, onChange, rootRenderChildren, n, elementType);
|
|
78
100
|
if (n.type === 'p' || n.type === 'paragraph') {
|
|
79
101
|
children.push(<Paragraph key={key}>{subNodes}</Paragraph>);
|
|
80
102
|
} else {
|
|
81
103
|
const Tag = n.type;
|
|
82
|
-
if (
|
|
104
|
+
if (n.nodes && n.nodes.length > 0) {
|
|
83
105
|
children.push(
|
|
84
106
|
<Tag key={key} {...n.data.attributes}>
|
|
85
107
|
{subNodes}
|
|
@@ -94,32 +116,56 @@ export const renderChildren = (layout, value, onChange, rootRenderChildren, pare
|
|
|
94
116
|
return children;
|
|
95
117
|
};
|
|
96
118
|
|
|
97
|
-
const MaskContainer =
|
|
98
|
-
|
|
99
|
-
|
|
119
|
+
const MaskContainer = styled('div')(() => ({
|
|
120
|
+
display: 'initial',
|
|
121
|
+
'&:not(.MathJax) table': {
|
|
122
|
+
borderCollapse: 'collapse',
|
|
100
123
|
},
|
|
101
|
-
|
|
124
|
+
// align table content to left as per STAR requirement PD-3687
|
|
125
|
+
'&:not(.MathJax) table td, &:not(.MathJax) table th': {
|
|
126
|
+
padding: '8px 12px',
|
|
127
|
+
textAlign: 'left',
|
|
128
|
+
},
|
|
129
|
+
}));
|
|
102
130
|
|
|
103
131
|
/**
|
|
104
132
|
* Renders a layout that uses the slate.js Value model structure.
|
|
105
133
|
*/
|
|
106
134
|
export default class Mask extends React.Component {
|
|
135
|
+
constructor(props) {
|
|
136
|
+
super(props);
|
|
137
|
+
this.internalContainerRef = React.createRef();
|
|
138
|
+
}
|
|
139
|
+
|
|
107
140
|
static propTypes = {
|
|
108
141
|
renderChildren: PropTypes.func,
|
|
109
142
|
layout: PropTypes.object,
|
|
110
143
|
value: PropTypes.object,
|
|
111
144
|
onChange: PropTypes.func,
|
|
145
|
+
elementType: PropTypes.string,
|
|
146
|
+
containerRef: PropTypes.oneOfType([
|
|
147
|
+
PropTypes.func,
|
|
148
|
+
PropTypes.shape({ current: PropTypes.instanceOf(Element) }),
|
|
149
|
+
]),
|
|
112
150
|
};
|
|
113
151
|
|
|
152
|
+
componentDidMount() {
|
|
153
|
+
const containerRef = this.props.containerRef || this.internalContainerRef;
|
|
154
|
+
if (containerRef.current && typeof renderMath === 'function') {
|
|
155
|
+
renderMath(containerRef.current);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
114
159
|
handleChange = (id, value) => {
|
|
115
160
|
const data = { ...this.props.value, [id]: value };
|
|
116
161
|
this.props.onChange(data);
|
|
117
162
|
};
|
|
118
163
|
|
|
119
164
|
render() {
|
|
120
|
-
const { value, layout } = this.props;
|
|
121
|
-
const children = renderChildren(layout, value, this.handleChange, this.props.renderChildren);
|
|
165
|
+
const { value, layout, elementType, containerRef } = this.props;
|
|
166
|
+
const children = renderChildren(layout, value, this.handleChange, this.props.renderChildren, null, elementType);
|
|
167
|
+
const ref = containerRef || this.internalContainerRef;
|
|
122
168
|
|
|
123
|
-
return <MaskContainer>{children}</MaskContainer>;
|
|
169
|
+
return <MaskContainer ref={ref}>{children}</MaskContainer>;
|
|
124
170
|
}
|
|
125
171
|
}
|
package/src/serialization.js
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { jsx } from 'slate-hyperscript';
|
|
1
|
+
import Html from 'slate-html-serializer';
|
|
3
2
|
import { object as toStyleObject } from 'to-style';
|
|
4
3
|
import debug from 'debug';
|
|
5
4
|
|
|
6
|
-
import Html from './test-serializer';
|
|
7
|
-
|
|
8
5
|
const log = debug('@pie-lib:mask-markup:serialization');
|
|
9
6
|
|
|
10
7
|
const INLINE = ['span'];
|
|
@@ -108,13 +105,13 @@ export const MARK_TAGS = {
|
|
|
108
105
|
const marks = {
|
|
109
106
|
deserialize(el, next) {
|
|
110
107
|
const mark = MARK_TAGS[el.tagName.toLowerCase()];
|
|
111
|
-
|
|
112
|
-
if (!mark) {
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
|
|
108
|
+
if (!mark) return;
|
|
116
109
|
log('[deserialize] mark: ', mark);
|
|
117
|
-
return
|
|
110
|
+
return {
|
|
111
|
+
object: 'mark',
|
|
112
|
+
type: mark,
|
|
113
|
+
nodes: next(el.childNodes),
|
|
114
|
+
};
|
|
118
115
|
},
|
|
119
116
|
};
|
|
120
117
|
|
|
@@ -130,7 +127,10 @@ const rules = [
|
|
|
130
127
|
}
|
|
131
128
|
|
|
132
129
|
if (el.nodeType === TEXT_NODE) {
|
|
133
|
-
return
|
|
130
|
+
return {
|
|
131
|
+
object: 'text',
|
|
132
|
+
leaves: [{ text: el.textContent }],
|
|
133
|
+
};
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
const type = el.tagName.toLowerCase();
|
|
@@ -142,33 +142,21 @@ const rules = [
|
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
const allAttrs = attributes.reduce(attributesToMap(el), { ...normalAttrs });
|
|
145
|
+
const object = getObject(type);
|
|
145
146
|
|
|
146
147
|
if (el.tagName.toLowerCase() === 'math') {
|
|
147
|
-
return
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
},
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
// return {
|
|
155
|
-
// isMath: true,
|
|
156
|
-
// nodes: [el]
|
|
157
|
-
// };
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
if (el.tagName.toLowerCase() === 'br') {
|
|
161
|
-
return jsx('element', { type, data: {} });
|
|
148
|
+
return {
|
|
149
|
+
isMath: true,
|
|
150
|
+
nodes: [el],
|
|
151
|
+
};
|
|
162
152
|
}
|
|
163
153
|
|
|
164
|
-
return
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
next(el.childNodes),
|
|
171
|
-
);
|
|
154
|
+
return {
|
|
155
|
+
object,
|
|
156
|
+
type,
|
|
157
|
+
data: { dataset: { ...el.dataset }, attributes: { ...allAttrs } },
|
|
158
|
+
nodes: next(el.childNodes),
|
|
159
|
+
};
|
|
172
160
|
},
|
|
173
161
|
},
|
|
174
162
|
];
|
package/src/with-mask.jsx
CHANGED
|
@@ -7,7 +7,7 @@ import { deserialize } from './serialization';
|
|
|
7
7
|
export const buildLayoutFromMarkup = (markup, type) => {
|
|
8
8
|
const { markup: processed } = componentize(markup, type);
|
|
9
9
|
const value = deserialize(processed);
|
|
10
|
-
return value;
|
|
10
|
+
return value.document;
|
|
11
11
|
};
|
|
12
12
|
|
|
13
13
|
export const withMask = (type, renderChildren) => {
|
|
@@ -23,13 +23,53 @@ export const withMask = (type, renderChildren) => {
|
|
|
23
23
|
layout: PropTypes.object,
|
|
24
24
|
value: PropTypes.object,
|
|
25
25
|
onChange: PropTypes.func,
|
|
26
|
+
customMarkMarkupComponent: PropTypes.func,
|
|
27
|
+
elementType: PropTypes.string,
|
|
26
28
|
};
|
|
27
29
|
|
|
30
|
+
constructor(props) {
|
|
31
|
+
super(props);
|
|
32
|
+
this.containerRef = React.createRef();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
componentDidUpdate(prevProps) {
|
|
36
|
+
if (this.props.markup !== prevProps.markup) {
|
|
37
|
+
const domNode = this.containerRef.current;
|
|
38
|
+
const mathElements = domNode && domNode.querySelectorAll('[data-latex][data-math-handled="true"]');
|
|
39
|
+
|
|
40
|
+
// Clean up for fresh MathJax processing
|
|
41
|
+
(mathElements || []).forEach((el) => {
|
|
42
|
+
// Remove the MathJax container to allow for clean updates
|
|
43
|
+
const mjxContainer = el.querySelector('mjx-container');
|
|
44
|
+
|
|
45
|
+
if (mjxContainer) {
|
|
46
|
+
el.removeChild(mjxContainer);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Update the innerHTML to match the raw LaTeX data, ensuring it is reprocessed correctly
|
|
50
|
+
const latexCode = el.getAttribute('data-raw');
|
|
51
|
+
el.innerHTML = latexCode;
|
|
52
|
+
|
|
53
|
+
// Remove the attribute to signal that MathJax should reprocess this element
|
|
54
|
+
el.removeAttribute('data-math-handled');
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
28
59
|
render() {
|
|
29
|
-
const { markup, layout, value, onChange } = this.props;
|
|
60
|
+
const { markup, layout, value, onChange, elementType } = this.props;
|
|
30
61
|
|
|
31
62
|
const maskLayout = layout ? layout : buildLayoutFromMarkup(markup, type);
|
|
32
|
-
return
|
|
63
|
+
return (
|
|
64
|
+
<Mask
|
|
65
|
+
containerRef={this.containerRef}
|
|
66
|
+
elementType={elementType}
|
|
67
|
+
layout={maskLayout}
|
|
68
|
+
value={value}
|
|
69
|
+
onChange={onChange}
|
|
70
|
+
renderChildren={renderChildren(this.props)}
|
|
71
|
+
/>
|
|
72
|
+
);
|
|
33
73
|
}
|
|
34
74
|
};
|
|
35
75
|
};
|
package/README.md
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
# mask-markup
|
|
2
|
-
|
|
3
|
-
## issues
|
|
4
|
-
|
|
5
|
-
- dnd
|
|
6
|
-
- simple approach loses context due to stepping out of react tree to run a ReactDOM.render().
|
|
7
|
-
- Would need to pass drag parts as props (no context)
|
|
8
|
-
- Or do the entire tree render in react - like a simple slate
|
|
9
|
-
- HTML5Backend - ? going to be a an issue w/ multiple items using dnd?
|
|
10
|
-
|
|
11
|
-
* hey diddle sample
|
|
12
|
-
* add feedback
|
|
13
|
-
* check perf
|
|
14
|
-
* more complex html
|