@khanacademy/perseus-editor 4.1.5 → 4.1.6
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/dist/es/index.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +9 -6
- package/.babelrc.js +0 -8
- package/.eslintrc.js +0 -12
- package/CHANGELOG.md +0 -1155
- package/src/README.md +0 -7
- package/src/__stories__/editor-page.stories.tsx +0 -65
- package/src/__stories__/editor.stories.tsx +0 -134
- package/src/__stories__/item-editor.stories.tsx +0 -41
- package/src/__stories__/tex-error-view.stories.tsx +0 -26
- package/src/__testdata__/input-number.testdata.ts +0 -27
- package/src/__tests__/editor.test.tsx +0 -142
- package/src/__tests__/hint-editor.test.tsx +0 -83
- package/src/__tests__/i18n.test.ts +0 -501
- package/src/__tests__/integration.test.ts +0 -5
- package/src/__tests__/item-extras-editor.test.tsx +0 -85
- package/src/__tests__/traversal.test.ts +0 -377
- package/src/all-editors.test.ts +0 -16
- package/src/all-editors.ts +0 -83
- package/src/all-widgets.ts +0 -7
- package/src/article-editor.tsx +0 -438
- package/src/components/__stories__/blur-input.stories.tsx +0 -22
- package/src/components/__stories__/section-control-button.stories.tsx +0 -26
- package/src/components/__tests__/blur-input.test.tsx +0 -67
- package/src/components/blur-input.tsx +0 -69
- package/src/components/checkbox.tsx +0 -194
- package/src/components/device-framer.tsx +0 -90
- package/src/components/drag-target.tsx +0 -87
- package/src/components/dropdown-option.tsx +0 -311
- package/src/components/form-wrapped-text-field.tsx +0 -245
- package/src/components/graph-points-count-selector.tsx +0 -31
- package/src/components/graph-settings.tsx +0 -627
- package/src/components/graph-type-selector.tsx +0 -31
- package/src/components/hover-behavior.ts +0 -177
- package/src/components/json-editor.tsx +0 -121
- package/src/components/link.ts +0 -173
- package/src/components/section-control-button.tsx +0 -53
- package/src/components/segment-count-selector.tsx +0 -27
- package/src/components/simple-button.tsx +0 -85
- package/src/components/sortable.tsx +0 -187
- package/src/components/util.ts +0 -38
- package/src/components/viewport-resizer.tsx +0 -72
- package/src/components/widget-editor.tsx +0 -222
- package/src/components/widget-select.tsx +0 -48
- package/src/diffs/__stories__/perseus-diff-wrapper.tsx +0 -11
- package/src/diffs/__stories__/structured-item-diff.stories.tsx +0 -855
- package/src/diffs/__stories__/tags-diff.stories.tsx +0 -36
- package/src/diffs/__stories__/text-diff.stories.tsx +0 -35
- package/src/diffs/__tests__/split-diff.test.ts +0 -33
- package/src/diffs/__tests__/string-array-diff.test.ts +0 -73
- package/src/diffs/__tests__/structured-item-diff.test.ts +0 -433
- package/src/diffs/__tests__/widget-diff-performer.test.ts +0 -43
- package/src/diffs/article-diff.tsx +0 -69
- package/src/diffs/item-diff.tsx +0 -76
- package/src/diffs/renderer-diff.tsx +0 -137
- package/src/diffs/split-diff.ts +0 -31
- package/src/diffs/string-array-diff.ts +0 -74
- package/src/diffs/structured-item-diff.tsx +0 -275
- package/src/diffs/tags-diff.tsx +0 -49
- package/src/diffs/text-diff.tsx +0 -193
- package/src/diffs/widget-diff-performer.ts +0 -69
- package/src/diffs/widget-diff.tsx +0 -290
- package/src/editor-page.tsx +0 -325
- package/src/editor.tsx +0 -1099
- package/src/hint-editor.tsx +0 -469
- package/src/i18n.ts +0 -140
- package/src/iframe-content-renderer.tsx +0 -188
- package/src/index.ts +0 -30
- package/src/item-editor.tsx +0 -158
- package/src/item-extras-editor.tsx +0 -230
- package/src/katex-mhchem/index.js +0 -73
- package/src/katex-mhchem/parser.js +0 -2659
- package/src/multirenderer-editor.tsx +0 -1061
- package/src/stateful-article-editor.tsx +0 -173
- package/src/stateful-editor-page.tsx +0 -88
- package/src/styles/perseus-editor.less +0 -1321
- package/src/testing-widgets.ts +0 -11
- package/src/tex-error-view.tsx +0 -90
- package/src/util/points.test.ts +0 -24
- package/src/util/points.ts +0 -13
- package/src/util/register-all-widgets-and-editors-for-testing.test.ts +0 -17
- package/src/util/register-all-widgets-and-editors-for-testing.ts +0 -17
- package/src/version.ts +0 -10
- package/src/widgets/__stories__/categorizer-editor.stories.tsx +0 -24
- package/src/widgets/__stories__/definition-editor.stories.tsx +0 -18
- package/src/widgets/__stories__/dropdown-editor.stories.tsx +0 -18
- package/src/widgets/__stories__/explanation-editor.stories.tsx +0 -18
- package/src/widgets/__stories__/expression-editor.stories.tsx +0 -122
- package/src/widgets/__stories__/image-editor.stories.tsx +0 -72
- package/src/widgets/__stories__/input-number-editor.stories.tsx +0 -18
- package/src/widgets/__stories__/interaction-editor.stories.tsx +0 -37
- package/src/widgets/__stories__/label-image-editor.stories.tsx +0 -99
- package/src/widgets/__stories__/matcher-editor.stories.tsx +0 -18
- package/src/widgets/__stories__/number-line-editor.stories.tsx +0 -18
- package/src/widgets/__stories__/numeric-input-editor.stories.tsx +0 -18
- package/src/widgets/__stories__/radio-editor.stories.tsx +0 -116
- package/src/widgets/__stories__/sorter-editor.stories.tsx +0 -18
- package/src/widgets/__tests__/categorizer-editor.test.tsx +0 -51
- package/src/widgets/__tests__/definition-editor.test.tsx +0 -41
- package/src/widgets/__tests__/dropdown-editor.test.tsx +0 -57
- package/src/widgets/__tests__/explanation-editor.test.tsx +0 -59
- package/src/widgets/__tests__/expression-editor.test.tsx +0 -403
- package/src/widgets/__tests__/input-number-editor.test.tsx +0 -128
- package/src/widgets/__tests__/matcher-editor.test.tsx +0 -47
- package/src/widgets/__tests__/number-line-editor.test.tsx +0 -144
- package/src/widgets/__tests__/numeric-input-editor.test.tsx +0 -177
- package/src/widgets/__tests__/radio-editor.test.tsx +0 -118
- package/src/widgets/__tests__/sorter-editor.test.tsx +0 -55
- package/src/widgets/categorizer-editor.tsx +0 -104
- package/src/widgets/cs-program-editor.tsx +0 -256
- package/src/widgets/definition-editor.tsx +0 -77
- package/src/widgets/deprecated-standin-editor.tsx +0 -29
- package/src/widgets/dropdown-editor.tsx +0 -211
- package/src/widgets/example-graphie-widget-editor.tsx +0 -70
- package/src/widgets/example-graphie-widget.tsx +0 -172
- package/src/widgets/example-widget-editor.tsx +0 -62
- package/src/widgets/example-widget.tsx +0 -154
- package/src/widgets/explanation-editor.tsx +0 -92
- package/src/widgets/expression-editor.tsx +0 -557
- package/src/widgets/graded-group-editor.tsx +0 -161
- package/src/widgets/graded-group-set-editor.tsx +0 -104
- package/src/widgets/grapher-editor.tsx +0 -155
- package/src/widgets/group-editor.tsx +0 -83
- package/src/widgets/iframe-editor.tsx +0 -202
- package/src/widgets/image-editor.tsx +0 -367
- package/src/widgets/input-number-editor.tsx +0 -288
- package/src/widgets/interaction-editor/constraint-editor.tsx +0 -134
- package/src/widgets/interaction-editor/function-editor.tsx +0 -108
- package/src/widgets/interaction-editor/index.ts +0 -3
- package/src/widgets/interaction-editor/interaction-editor.tsx +0 -749
- package/src/widgets/interaction-editor/label-editor.tsx +0 -87
- package/src/widgets/interaction-editor/line-editor.tsx +0 -125
- package/src/widgets/interaction-editor/mathquill-input.tsx +0 -81
- package/src/widgets/interaction-editor/movable-line-editor.tsx +0 -121
- package/src/widgets/interaction-editor/movable-point-editor.tsx +0 -89
- package/src/widgets/interaction-editor/parametric-editor.tsx +0 -117
- package/src/widgets/interaction-editor/point-editor.tsx +0 -72
- package/src/widgets/interaction-editor/rectangle-editor.tsx +0 -96
- package/src/widgets/interactive-graph-editor.tsx +0 -571
- package/src/widgets/label-image/__stories__/answer-choices.stories.tsx +0 -82
- package/src/widgets/label-image/__stories__/behavior.stories.tsx +0 -31
- package/src/widgets/label-image/__stories__/marker.stories.tsx +0 -75
- package/src/widgets/label-image/__stories__/question-markers.stories.tsx +0 -107
- package/src/widgets/label-image/__stories__/select-image.stories.tsx +0 -59
- package/src/widgets/label-image/answer-choices.tsx +0 -231
- package/src/widgets/label-image/behavior.tsx +0 -122
- package/src/widgets/label-image/marker.tsx +0 -290
- package/src/widgets/label-image/question-markers.tsx +0 -176
- package/src/widgets/label-image/select-image.tsx +0 -81
- package/src/widgets/label-image-editor.tsx +0 -252
- package/src/widgets/lights-puzzle-editor.tsx +0 -278
- package/src/widgets/matcher-editor.tsx +0 -149
- package/src/widgets/matrix-editor.tsx +0 -156
- package/src/widgets/measurer-editor.tsx +0 -253
- package/src/widgets/molecule-editor.tsx +0 -64
- package/src/widgets/number-line-editor.tsx +0 -512
- package/src/widgets/numeric-input-editor.tsx +0 -536
- package/src/widgets/orderer-editor.tsx +0 -172
- package/src/widgets/passage-editor.tsx +0 -117
- package/src/widgets/passage-ref-editor.tsx +0 -80
- package/src/widgets/passage-ref-target-editor.tsx +0 -49
- package/src/widgets/plotter-editor.tsx +0 -542
- package/src/widgets/radio/editor.tsx +0 -398
- package/src/widgets/reaction-diagram-editor.tsx +0 -131
- package/src/widgets/sequence-editor.tsx +0 -205
- package/src/widgets/simple-markdown-tester-editor.tsx +0 -86
- package/src/widgets/simple-markdown-tester.tsx +0 -92
- package/src/widgets/simulator-editor.tsx +0 -146
- package/src/widgets/sorter-editor.tsx +0 -103
- package/src/widgets/table-editor.tsx +0 -164
- package/src/widgets/unit-editor.tsx +0 -299
- package/src/widgets/video-editor.tsx +0 -73
- package/tsconfig-build.json +0 -22
- package/tsconfig-build.tsbuildinfo +0 -1
package/src/hint-editor.tsx
DELETED
|
@@ -1,469 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
2
|
-
/* eslint-disable @babel/no-invalid-this */
|
|
3
|
-
/**
|
|
4
|
-
* Collection of classes for rendering the hint editor area,
|
|
5
|
-
* hint editor boxes, and hint previews
|
|
6
|
-
*/
|
|
7
|
-
import {components, icons} from "@khanacademy/perseus";
|
|
8
|
-
import * as React from "react";
|
|
9
|
-
import _ from "underscore";
|
|
10
|
-
|
|
11
|
-
import DeviceFramer from "./components/device-framer";
|
|
12
|
-
import Editor from "./editor";
|
|
13
|
-
import IframeContentRenderer from "./iframe-content-renderer";
|
|
14
|
-
|
|
15
|
-
import type {
|
|
16
|
-
APIOptions,
|
|
17
|
-
WidgetDict,
|
|
18
|
-
ImageDict,
|
|
19
|
-
Hint,
|
|
20
|
-
ChangeHandler,
|
|
21
|
-
DeviceType,
|
|
22
|
-
} from "@khanacademy/perseus";
|
|
23
|
-
|
|
24
|
-
const {InfoTip, InlineIcon} = components;
|
|
25
|
-
const {iconCircleArrowDown, iconCircleArrowUp, iconPlus, iconTrash} = icons;
|
|
26
|
-
|
|
27
|
-
type ImageUploader = (
|
|
28
|
-
file: string,
|
|
29
|
-
callback: (url: string) => unknown,
|
|
30
|
-
) => unknown;
|
|
31
|
-
|
|
32
|
-
type HintEditorProps = {
|
|
33
|
-
itemId?: string;
|
|
34
|
-
apiOptions?: APIOptions;
|
|
35
|
-
className: string;
|
|
36
|
-
imageUploader?: ImageUploader;
|
|
37
|
-
showMoveButtons?: boolean;
|
|
38
|
-
showRemoveButton?: boolean;
|
|
39
|
-
showTitle?: boolean;
|
|
40
|
-
content?: string | null | undefined;
|
|
41
|
-
replace?: boolean | null | undefined;
|
|
42
|
-
widgets?: WidgetDict | null | undefined;
|
|
43
|
-
images?: ImageDict | null | undefined;
|
|
44
|
-
isLast: boolean;
|
|
45
|
-
isFirst: boolean;
|
|
46
|
-
onMove: (direction: number) => unknown;
|
|
47
|
-
onRemove: () => unknown;
|
|
48
|
-
onChange: ChangeHandler;
|
|
49
|
-
__type?: "hint";
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
/* Renders a hint editor box
|
|
53
|
-
*
|
|
54
|
-
* This includes:
|
|
55
|
-
* ~ A "Hint" title
|
|
56
|
-
* ~ the textarea for the hint
|
|
57
|
-
* ~ the "remove this hint" box
|
|
58
|
-
* ~ the move hint up/down arrows
|
|
59
|
-
*/
|
|
60
|
-
export class HintEditor extends React.Component<HintEditorProps> {
|
|
61
|
-
static defaultProps: {
|
|
62
|
-
className: string;
|
|
63
|
-
content: string;
|
|
64
|
-
replace: boolean;
|
|
65
|
-
showMoveButtons: boolean;
|
|
66
|
-
showRemoveButton: boolean;
|
|
67
|
-
showTitle: boolean;
|
|
68
|
-
} = {
|
|
69
|
-
className: "",
|
|
70
|
-
content: "",
|
|
71
|
-
replace: false,
|
|
72
|
-
showMoveButtons: true,
|
|
73
|
-
showTitle: true,
|
|
74
|
-
showRemoveButton: true,
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
editor = React.createRef<Editor>();
|
|
78
|
-
|
|
79
|
-
handleChange: (e: React.ChangeEvent<HTMLInputElement>) => void = (
|
|
80
|
-
e: React.ChangeEvent<HTMLInputElement>,
|
|
81
|
-
) => {
|
|
82
|
-
this.props.onChange({replace: e.target.checked});
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
focus: () => void = () => {
|
|
86
|
-
this.editor.current?.focus();
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
getSaveWarnings: () => any = () => {
|
|
90
|
-
return this.editor.current?.getSaveWarnings();
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
serialize: (options?: any) => any = (options: any) => {
|
|
94
|
-
return this.editor.current?.serialize(options);
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
render(): React.ReactNode {
|
|
98
|
-
return (
|
|
99
|
-
<div className={"perseus-hint-editor " + this.props.className}>
|
|
100
|
-
{this.props.showTitle && <div className="pod-title">Hint</div>}
|
|
101
|
-
<Editor
|
|
102
|
-
ref={this.editor}
|
|
103
|
-
// Using the AssessmentItem content ID as the key
|
|
104
|
-
// ensures that when the user navigates to another
|
|
105
|
-
// item in the Sidebar, the question editor is
|
|
106
|
-
// re-rendered by React.
|
|
107
|
-
key={this.props.itemId}
|
|
108
|
-
apiOptions={this.props.apiOptions}
|
|
109
|
-
widgets={this.props.widgets || undefined}
|
|
110
|
-
content={this.props.content || undefined}
|
|
111
|
-
images={this.props.images}
|
|
112
|
-
replace={this.props.replace}
|
|
113
|
-
placeholder="Type your hint here..."
|
|
114
|
-
imageUploader={this.props.imageUploader}
|
|
115
|
-
onChange={this.props.onChange}
|
|
116
|
-
/>
|
|
117
|
-
<div className="hint-controls-container clearfix">
|
|
118
|
-
{this.props.showMoveButtons && (
|
|
119
|
-
<span className="reorder-hints">
|
|
120
|
-
<button
|
|
121
|
-
type="button"
|
|
122
|
-
className={this.props.isLast ? "hidden" : ""}
|
|
123
|
-
onClick={_.partial(this.props.onMove, 1)}
|
|
124
|
-
>
|
|
125
|
-
<InlineIcon {...iconCircleArrowDown} />
|
|
126
|
-
</button>{" "}
|
|
127
|
-
<button
|
|
128
|
-
type="button"
|
|
129
|
-
className={this.props.isFirst ? "hidden" : ""}
|
|
130
|
-
onClick={_.partial(this.props.onMove, -1)}
|
|
131
|
-
>
|
|
132
|
-
<InlineIcon {...iconCircleArrowUp} />
|
|
133
|
-
</button>{" "}
|
|
134
|
-
{this.props.isLast && (
|
|
135
|
-
<InfoTip>
|
|
136
|
-
<p>
|
|
137
|
-
The last hint is automatically bolded.
|
|
138
|
-
</p>
|
|
139
|
-
</InfoTip>
|
|
140
|
-
)}
|
|
141
|
-
</span>
|
|
142
|
-
)}
|
|
143
|
-
<input
|
|
144
|
-
type="checkbox"
|
|
145
|
-
// @ts-expect-error - TS2322 - Type 'boolean | null | undefined' is not assignable to type 'boolean | undefined'.
|
|
146
|
-
checked={this.props.replace}
|
|
147
|
-
onChange={this.handleChange}
|
|
148
|
-
/>
|
|
149
|
-
Replace previous hint
|
|
150
|
-
{this.props.showRemoveButton && (
|
|
151
|
-
<button
|
|
152
|
-
type="button"
|
|
153
|
-
className="remove-hint simple-button orange"
|
|
154
|
-
onClick={this.props.onRemove}
|
|
155
|
-
>
|
|
156
|
-
<InlineIcon {...iconTrash} />
|
|
157
|
-
Remove this hint{" "}
|
|
158
|
-
</button>
|
|
159
|
-
)}
|
|
160
|
-
</div>
|
|
161
|
-
</div>
|
|
162
|
-
);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
type CombinedHintEditorProps = {
|
|
167
|
-
itemId?: string;
|
|
168
|
-
apiOptions?: APIOptions;
|
|
169
|
-
deviceType: DeviceType;
|
|
170
|
-
imageUploader?: ImageUploader;
|
|
171
|
-
highlightLint?: boolean;
|
|
172
|
-
isLast: boolean;
|
|
173
|
-
isFirst: boolean;
|
|
174
|
-
hint: Hint;
|
|
175
|
-
pos: number; // position,
|
|
176
|
-
contentPaths: ReadonlyArray<string>;
|
|
177
|
-
// URL of the route to show on initial load of the preview frames.
|
|
178
|
-
previewURL: string;
|
|
179
|
-
onMove: (direction: number) => unknown;
|
|
180
|
-
onRemove: () => unknown;
|
|
181
|
-
onChange: ChangeHandler;
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
/* A single hint-row containing a hint editor and preview */
|
|
185
|
-
class CombinedHintEditor extends React.Component<CombinedHintEditorProps> {
|
|
186
|
-
static defaultProps = {
|
|
187
|
-
highlightLint: false,
|
|
188
|
-
};
|
|
189
|
-
|
|
190
|
-
editor = React.createRef<HintEditor>();
|
|
191
|
-
frame = React.createRef<IframeContentRenderer>();
|
|
192
|
-
|
|
193
|
-
componentDidMount() {
|
|
194
|
-
this.updatePreview();
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
componentDidUpdate() {
|
|
198
|
-
this.updatePreview();
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
updatePreview = () => {
|
|
202
|
-
const shouldBold =
|
|
203
|
-
this.props.isLast && !/\*\*/.test(this.props.hint.content);
|
|
204
|
-
|
|
205
|
-
this.frame.current?.sendNewData({
|
|
206
|
-
type: "hint",
|
|
207
|
-
data: {
|
|
208
|
-
hint: this.props.hint,
|
|
209
|
-
bold: shouldBold,
|
|
210
|
-
pos: this.props.pos,
|
|
211
|
-
apiOptions: this.props.apiOptions,
|
|
212
|
-
linterContext: {
|
|
213
|
-
contentType: "hint",
|
|
214
|
-
highlightLint: this.props.highlightLint,
|
|
215
|
-
paths: this.props.contentPaths,
|
|
216
|
-
},
|
|
217
|
-
},
|
|
218
|
-
});
|
|
219
|
-
};
|
|
220
|
-
|
|
221
|
-
getSaveWarnings = () => {
|
|
222
|
-
return this.editor.current?.getSaveWarnings();
|
|
223
|
-
};
|
|
224
|
-
|
|
225
|
-
serialize = (options: any) => {
|
|
226
|
-
return this.editor.current?.serialize(options);
|
|
227
|
-
};
|
|
228
|
-
|
|
229
|
-
focus = () => {
|
|
230
|
-
this.editor.current?.focus();
|
|
231
|
-
};
|
|
232
|
-
|
|
233
|
-
render(): React.ReactNode {
|
|
234
|
-
const isMobile =
|
|
235
|
-
this.props.deviceType === "phone" ||
|
|
236
|
-
this.props.deviceType === "tablet";
|
|
237
|
-
return (
|
|
238
|
-
<div
|
|
239
|
-
className={
|
|
240
|
-
"perseus-combined-hint-editor " + "perseus-editor-row"
|
|
241
|
-
}
|
|
242
|
-
>
|
|
243
|
-
<div className="perseus-editor-left-cell">
|
|
244
|
-
<HintEditor
|
|
245
|
-
ref={this.editor}
|
|
246
|
-
itemId={this.props.itemId}
|
|
247
|
-
isFirst={this.props.isFirst}
|
|
248
|
-
isLast={this.props.isLast}
|
|
249
|
-
widgets={this.props.hint.widgets}
|
|
250
|
-
content={this.props.hint.content}
|
|
251
|
-
images={this.props.hint.images}
|
|
252
|
-
replace={this.props.hint.replace}
|
|
253
|
-
imageUploader={this.props.imageUploader}
|
|
254
|
-
onChange={this.props.onChange}
|
|
255
|
-
onRemove={this.props.onRemove}
|
|
256
|
-
onMove={this.props.onMove}
|
|
257
|
-
apiOptions={this.props.apiOptions}
|
|
258
|
-
/>
|
|
259
|
-
</div>
|
|
260
|
-
<div className="perseus-editor-right-cell">
|
|
261
|
-
<DeviceFramer
|
|
262
|
-
deviceType={this.props.deviceType}
|
|
263
|
-
nochrome={true}
|
|
264
|
-
>
|
|
265
|
-
<IframeContentRenderer
|
|
266
|
-
ref={this.frame}
|
|
267
|
-
datasetKey="mobile"
|
|
268
|
-
datasetValue={isMobile}
|
|
269
|
-
seamless={true}
|
|
270
|
-
url={this.props.previewURL}
|
|
271
|
-
/>
|
|
272
|
-
</DeviceFramer>
|
|
273
|
-
</div>
|
|
274
|
-
</div>
|
|
275
|
-
);
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
type CombinedHintsEditorProps = {
|
|
280
|
-
apiOptions?: APIOptions;
|
|
281
|
-
deviceType: DeviceType;
|
|
282
|
-
imageUploader?: ImageUploader;
|
|
283
|
-
highlightLint?: boolean;
|
|
284
|
-
hints: ReadonlyArray<Hint>;
|
|
285
|
-
// URL of the route to show on initial load of the preview frames.
|
|
286
|
-
previewURL: string;
|
|
287
|
-
onChange: ChangeHandler;
|
|
288
|
-
// The content ID of the AssessmentItem being edited. It may not be set
|
|
289
|
-
// for non-content library exercise questions.
|
|
290
|
-
itemId?: string;
|
|
291
|
-
};
|
|
292
|
-
|
|
293
|
-
/* The entire hints editing/preview area
|
|
294
|
-
*
|
|
295
|
-
* Includes:
|
|
296
|
-
* ~ All the hint edit boxes, move and remove buttons
|
|
297
|
-
* ~ All the hint previews
|
|
298
|
-
* ~ The "add a hint" button
|
|
299
|
-
*/
|
|
300
|
-
class CombinedHintsEditor extends React.Component<CombinedHintsEditorProps> {
|
|
301
|
-
static HintEditor: typeof HintEditor = HintEditor;
|
|
302
|
-
|
|
303
|
-
static defaultProps: {
|
|
304
|
-
highlightLint: boolean;
|
|
305
|
-
hints: ReadonlyArray<any>;
|
|
306
|
-
onChange: () => void;
|
|
307
|
-
} = {
|
|
308
|
-
onChange: () => {},
|
|
309
|
-
hints: [],
|
|
310
|
-
highlightLint: false,
|
|
311
|
-
};
|
|
312
|
-
|
|
313
|
-
handleHintChange: (
|
|
314
|
-
i: number,
|
|
315
|
-
newProps: CombinedHintsEditorProps,
|
|
316
|
-
cb: () => unknown,
|
|
317
|
-
silent: boolean,
|
|
318
|
-
) => void = (
|
|
319
|
-
i: number,
|
|
320
|
-
newProps: CombinedHintsEditorProps,
|
|
321
|
-
cb: () => unknown,
|
|
322
|
-
silent: boolean,
|
|
323
|
-
) => {
|
|
324
|
-
// TODO(joel) - lens
|
|
325
|
-
const hints = _(this.props.hints).clone();
|
|
326
|
-
hints[i] = _.extend(
|
|
327
|
-
{},
|
|
328
|
-
this.serializeHint(i, {keepDeletedWidgets: true}),
|
|
329
|
-
newProps,
|
|
330
|
-
);
|
|
331
|
-
|
|
332
|
-
// @ts-expect-error - TS2740 - Type 'Hint' is missing the following properties from type 'readonly Hint[]': length, concat, join, slice, and 18 more.
|
|
333
|
-
this.props.onChange({hints: hints}, cb, silent);
|
|
334
|
-
};
|
|
335
|
-
|
|
336
|
-
handleHintRemove: (i: number) => void = (i: number) => {
|
|
337
|
-
// eslint-disable-next-line no-alert
|
|
338
|
-
if (!confirm("Are you sure you want to delete this hint?")) {
|
|
339
|
-
return;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
const hints = _(this.props.hints).clone();
|
|
343
|
-
// @ts-expect-error - TS2339 - Property 'splice' does not exist on type 'Hint'.
|
|
344
|
-
hints.splice(i, 1);
|
|
345
|
-
// @ts-expect-error - TS2322 - Type 'Hint' is not assignable to type 'readonly Hint[]'.
|
|
346
|
-
this.props.onChange({hints: hints});
|
|
347
|
-
};
|
|
348
|
-
|
|
349
|
-
handleHintMove: (i: number, dir: number) => void = (
|
|
350
|
-
i: number,
|
|
351
|
-
dir: number,
|
|
352
|
-
) => {
|
|
353
|
-
const hints = _(this.props.hints).clone();
|
|
354
|
-
// @ts-expect-error - TS2339 - Property 'splice' does not exist on type 'Hint'.
|
|
355
|
-
const hint = hints.splice(i, 1)[0];
|
|
356
|
-
// @ts-expect-error - TS2339 - Property 'splice' does not exist on type 'Hint'.
|
|
357
|
-
hints.splice(i + dir, 0, hint);
|
|
358
|
-
// @ts-expect-error - TS2322 - Type 'Hint' is not assignable to type 'readonly Hint[]'.
|
|
359
|
-
this.props.onChange({hints: hints}, () => {
|
|
360
|
-
// eslint-disable-next-line react/no-string-refs
|
|
361
|
-
// @ts-expect-error - TS2339 - Property 'focus' does not exist on type 'ReactInstance'.
|
|
362
|
-
this.refs["hintEditor" + (i + dir)].focus();
|
|
363
|
-
});
|
|
364
|
-
};
|
|
365
|
-
|
|
366
|
-
addHint: () => void = () => {
|
|
367
|
-
const hints = _(this.props.hints)
|
|
368
|
-
.clone()
|
|
369
|
-
// @ts-expect-error - TS2339 - Property 'concat' does not exist on type 'Hint'.
|
|
370
|
-
.concat([{content: ""}]);
|
|
371
|
-
this.props.onChange({hints: hints}, () => {
|
|
372
|
-
const i = hints.length - 1;
|
|
373
|
-
// eslint-disable-next-line react/no-string-refs
|
|
374
|
-
// @ts-expect-error - TS2339 - Property 'focus' does not exist on type 'ReactInstance'.
|
|
375
|
-
this.refs["hintEditor" + i].focus();
|
|
376
|
-
});
|
|
377
|
-
};
|
|
378
|
-
|
|
379
|
-
getSaveWarnings: () => any = () => {
|
|
380
|
-
return _.chain(this.props.hints)
|
|
381
|
-
.map((hint, i) => {
|
|
382
|
-
return _.map(
|
|
383
|
-
// eslint-disable-next-line react/no-string-refs
|
|
384
|
-
// @ts-expect-error - TS2339 - Property 'getSaveWarnings' does not exist on type 'ReactInstance'.
|
|
385
|
-
this.refs["hintEditor" + i].getSaveWarnings(),
|
|
386
|
-
(issue) => "Hint " + (i + 1) + ": " + issue,
|
|
387
|
-
);
|
|
388
|
-
})
|
|
389
|
-
.flatten(true)
|
|
390
|
-
.value();
|
|
391
|
-
};
|
|
392
|
-
|
|
393
|
-
serialize: (options?: any) => ReadonlyArray<string> = (options: any) => {
|
|
394
|
-
return this.props.hints.map((hint, i) => {
|
|
395
|
-
return this.serializeHint(i, options);
|
|
396
|
-
});
|
|
397
|
-
};
|
|
398
|
-
|
|
399
|
-
serializeHint: (index: number, options?: any) => string = (
|
|
400
|
-
index: number,
|
|
401
|
-
options: any,
|
|
402
|
-
): string => {
|
|
403
|
-
// eslint-disable-next-line react/no-string-refs
|
|
404
|
-
// @ts-expect-error - TS2339 - Property 'serialize' does not exist on type 'ReactInstance'.
|
|
405
|
-
return this.refs["hintEditor" + index].serialize(options);
|
|
406
|
-
};
|
|
407
|
-
|
|
408
|
-
render(): React.ReactNode {
|
|
409
|
-
const {itemId, hints} = this.props;
|
|
410
|
-
const hintElems = _.map(
|
|
411
|
-
hints,
|
|
412
|
-
function (hint, i) {
|
|
413
|
-
return (
|
|
414
|
-
<CombinedHintEditor
|
|
415
|
-
ref={"hintEditor" + i}
|
|
416
|
-
key={"hintEditor" + i}
|
|
417
|
-
isFirst={i === 0}
|
|
418
|
-
isLast={i + 1 === hints.length}
|
|
419
|
-
itemId={itemId}
|
|
420
|
-
hint={hint}
|
|
421
|
-
pos={i}
|
|
422
|
-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
423
|
-
imageUploader={this.props.imageUploader}
|
|
424
|
-
// eslint-disable-next-line react/jsx-no-bind
|
|
425
|
-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation. | TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
426
|
-
onChange={this.handleHintChange.bind(this, i)}
|
|
427
|
-
// eslint-disable-next-line react/jsx-no-bind
|
|
428
|
-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation. | TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
429
|
-
onRemove={this.handleHintRemove.bind(this, i)}
|
|
430
|
-
// eslint-disable-next-line react/jsx-no-bind
|
|
431
|
-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation. | TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
432
|
-
onMove={this.handleHintMove.bind(this, i)}
|
|
433
|
-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
434
|
-
deviceType={this.props.deviceType}
|
|
435
|
-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
436
|
-
apiOptions={this.props.apiOptions}
|
|
437
|
-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
438
|
-
highlightLint={this.props.highlightLint}
|
|
439
|
-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
440
|
-
previewURL={this.props.previewURL}
|
|
441
|
-
// TODO(CP-4838): what should be passed here?
|
|
442
|
-
contentPaths={[]}
|
|
443
|
-
/>
|
|
444
|
-
);
|
|
445
|
-
},
|
|
446
|
-
this,
|
|
447
|
-
);
|
|
448
|
-
|
|
449
|
-
return (
|
|
450
|
-
<div className="perseus-hints-editor perseus-editor-table">
|
|
451
|
-
{hintElems}
|
|
452
|
-
<div className="perseus-editor-row">
|
|
453
|
-
<div className="add-hint-container perseus-editor-left-cell">
|
|
454
|
-
<button
|
|
455
|
-
type="button"
|
|
456
|
-
className="add-hint simple-button orange"
|
|
457
|
-
onClick={this.addHint}
|
|
458
|
-
>
|
|
459
|
-
<InlineIcon {...iconPlus} /> Add a hint
|
|
460
|
-
</button>
|
|
461
|
-
</div>
|
|
462
|
-
</div>
|
|
463
|
-
</div>
|
|
464
|
-
);
|
|
465
|
-
/* eslint-enable max-len */
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
export default CombinedHintsEditor;
|
package/src/i18n.ts
DELETED
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Functions for extracting data from items for use in i18n.
|
|
3
|
-
*/
|
|
4
|
-
import {traverse, MultiItems, PerseusMarkdown} from "@khanacademy/perseus";
|
|
5
|
-
import _ from "underscore";
|
|
6
|
-
|
|
7
|
-
const {findContentNodesInItem, findHintNodesInItem, inferItemShape} =
|
|
8
|
-
MultiItems;
|
|
9
|
-
|
|
10
|
-
// Takes a renderer content and parses the markdown for images
|
|
11
|
-
function findImagesInContent(content: any, images: Array<any>) {
|
|
12
|
-
// @ts-expect-error - TS2554 - Expected 2 arguments, but got 1.
|
|
13
|
-
const parsed = PerseusMarkdown.parse(content);
|
|
14
|
-
|
|
15
|
-
PerseusMarkdown.traverseContent(parsed, function (node) {
|
|
16
|
-
if (node.type === "image") {
|
|
17
|
-
images.push(node.target);
|
|
18
|
-
}
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// Background images in some widgets are annoying to deal with because
|
|
23
|
-
// sometimes the objects aren't full when there isn't an image. So, we do some
|
|
24
|
-
// extra checking to make sure we don't cause an error or push an empty image.
|
|
25
|
-
function handleBackgroundImage(graph, images: Array<any>) {
|
|
26
|
-
if (graph && graph.backgroundImage && graph.backgroundImage.url) {
|
|
27
|
-
images.push(graph.backgroundImage.url);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// The callback called for each widget. We check each of the areas of each
|
|
32
|
-
// widget where they contain a renderer for images by calling
|
|
33
|
-
// findImagesInContent. We don't have to recurse through child widgets, because
|
|
34
|
-
// traverseRendererDeep does that for us.
|
|
35
|
-
function widgetCallback(widgetInfo: any, images: Array<any>) {
|
|
36
|
-
if (!widgetInfo.options) {
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// TODO(emily/aria): Move this into the widget files, so we don't have the
|
|
41
|
-
// logic out here.
|
|
42
|
-
if (widgetInfo.type === "categorizer") {
|
|
43
|
-
_.each(widgetInfo.options.items, function (item) {
|
|
44
|
-
findImagesInContent(item, images);
|
|
45
|
-
});
|
|
46
|
-
_.each(widgetInfo.options.categories, function (category) {
|
|
47
|
-
findImagesInContent(category, images);
|
|
48
|
-
});
|
|
49
|
-
} else if (widgetInfo.type === "image") {
|
|
50
|
-
findImagesInContent(widgetInfo.options.title, images);
|
|
51
|
-
findImagesInContent(widgetInfo.options.caption, images);
|
|
52
|
-
} else if (widgetInfo.type === "matcher") {
|
|
53
|
-
_.each(widgetInfo.options.left, function (option) {
|
|
54
|
-
findImagesInContent(option, images);
|
|
55
|
-
});
|
|
56
|
-
_.each(widgetInfo.options.right, function (option) {
|
|
57
|
-
findImagesInContent(option, images);
|
|
58
|
-
});
|
|
59
|
-
_.each(widgetInfo.options.labels, function (label) {
|
|
60
|
-
findImagesInContent(label, images);
|
|
61
|
-
});
|
|
62
|
-
} else if (widgetInfo.type === "matrix") {
|
|
63
|
-
findImagesInContent(widgetInfo.options.prefix, images);
|
|
64
|
-
findImagesInContent(widgetInfo.options.suffix, images);
|
|
65
|
-
} else if (widgetInfo.type === "orderer") {
|
|
66
|
-
_.each(widgetInfo.options.options, function (option) {
|
|
67
|
-
findImagesInContent(option.content, images);
|
|
68
|
-
});
|
|
69
|
-
} else if (widgetInfo.type === "passage") {
|
|
70
|
-
findImagesInContent(widgetInfo.options.passageTitle, images);
|
|
71
|
-
} else if (widgetInfo.type === "radio") {
|
|
72
|
-
_.each(widgetInfo.options.choices, function (choice) {
|
|
73
|
-
findImagesInContent(choice.content, images);
|
|
74
|
-
});
|
|
75
|
-
} else if (widgetInfo.type === "sorter") {
|
|
76
|
-
_.each(widgetInfo.options.correct, function (option) {
|
|
77
|
-
findImagesInContent(option, images);
|
|
78
|
-
});
|
|
79
|
-
} else if (widgetInfo.type === "table") {
|
|
80
|
-
_.each(widgetInfo.options.headers, function (header) {
|
|
81
|
-
findImagesInContent(header, images);
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (widgetInfo.type === "grapher") {
|
|
86
|
-
handleBackgroundImage(widgetInfo.options.graph, images);
|
|
87
|
-
} else if (widgetInfo.type === "image") {
|
|
88
|
-
handleBackgroundImage(widgetInfo.options, images);
|
|
89
|
-
} else if (widgetInfo.type === "interactive-graph") {
|
|
90
|
-
handleBackgroundImage(widgetInfo.options, images);
|
|
91
|
-
} else if (widgetInfo.type === "measurer" && widgetInfo.options.image) {
|
|
92
|
-
images.push(widgetInfo.options.image.url);
|
|
93
|
-
} else if (widgetInfo.type === "plotter") {
|
|
94
|
-
images.push(widgetInfo.options.picUrl);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function findImagesInRenderers(renderers) {
|
|
99
|
-
const images = [];
|
|
100
|
-
|
|
101
|
-
_.each(renderers, (renderer) => {
|
|
102
|
-
traverse(
|
|
103
|
-
renderer,
|
|
104
|
-
(content) => {
|
|
105
|
-
findImagesInContent(content, images);
|
|
106
|
-
},
|
|
107
|
-
(widget) => widgetCallback(widget, images),
|
|
108
|
-
);
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
return images;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Calls findImagesInContent on all of the different content areas for
|
|
115
|
-
// assessment items
|
|
116
|
-
function findImagesInItemData(itemData: any): any {
|
|
117
|
-
let renderers = [];
|
|
118
|
-
if (itemData._multi) {
|
|
119
|
-
const shape = inferItemShape(itemData);
|
|
120
|
-
// @ts-expect-error - TS2345 - Argument of type 'ContentNode' is not assignable to parameter of type 'never'.
|
|
121
|
-
findContentNodesInItem(itemData, shape, (node) => renderers.push(node));
|
|
122
|
-
// @ts-expect-error - TS2345 - Argument of type 'HintNode' is not assignable to parameter of type 'never'.
|
|
123
|
-
findHintNodesInItem(itemData, shape, (node) => renderers.push(node));
|
|
124
|
-
} else {
|
|
125
|
-
// @ts-expect-error - TS2322 - Type 'any' is not assignable to type 'never'. | TS2322 - Type 'any' is not assignable to type 'never'.
|
|
126
|
-
renderers = [itemData.question, ...itemData.hints];
|
|
127
|
-
}
|
|
128
|
-
return findImagesInRenderers(renderers);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Calls findImagesInContent on all of the different content areas for
|
|
132
|
-
// articles
|
|
133
|
-
function findImagesInArticles(perseusContent: any): any {
|
|
134
|
-
return findImagesInRenderers(perseusContent);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
export default {
|
|
138
|
-
findImagesInArticles: findImagesInArticles,
|
|
139
|
-
findImagesInItemData: findImagesInItemData,
|
|
140
|
-
};
|