@khanacademy/perseus-editor 4.1.4 → 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 -1146
- 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/article-editor.tsx
DELETED
|
@@ -1,438 +0,0 @@
|
|
|
1
|
-
/* eslint-disable jsx-a11y/anchor-is-valid */
|
|
2
|
-
/**
|
|
3
|
-
* An article editor. Articles are long-form pieces of content, composed of
|
|
4
|
-
* multiple (Renderer) sections concatenated together.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
components,
|
|
9
|
-
icons,
|
|
10
|
-
ApiOptions,
|
|
11
|
-
Errors,
|
|
12
|
-
PerseusError,
|
|
13
|
-
} from "@khanacademy/perseus";
|
|
14
|
-
import * as React from "react";
|
|
15
|
-
import _ from "underscore";
|
|
16
|
-
|
|
17
|
-
import DeviceFramer from "./components/device-framer";
|
|
18
|
-
import JsonEditor from "./components/json-editor";
|
|
19
|
-
import SectionControlButton from "./components/section-control-button";
|
|
20
|
-
import Editor from "./editor";
|
|
21
|
-
import IframeContentRenderer from "./iframe-content-renderer";
|
|
22
|
-
|
|
23
|
-
import type {APIOptions, Changeable} from "@khanacademy/perseus";
|
|
24
|
-
|
|
25
|
-
const {HUD, InlineIcon} = components;
|
|
26
|
-
const {iconCircleArrowDown, iconCircleArrowUp, iconPlus, iconTrash} = icons;
|
|
27
|
-
|
|
28
|
-
type RendererProps = {
|
|
29
|
-
content?: string;
|
|
30
|
-
widgets?: any;
|
|
31
|
-
images?: any;
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
type JsonType = RendererProps | ReadonlyArray<RendererProps>;
|
|
35
|
-
type DefaultProps = {
|
|
36
|
-
contentPaths?: ReadonlyArray<string>;
|
|
37
|
-
json: JsonType;
|
|
38
|
-
mode: "diff" | "edit" | "json" | "preview";
|
|
39
|
-
screen: "phone" | "tablet" | "desktop";
|
|
40
|
-
sectionImageUploadGenerator: (
|
|
41
|
-
i: number,
|
|
42
|
-
) => React.ReactElement<React.ComponentProps<"span">>;
|
|
43
|
-
useNewStyles: boolean;
|
|
44
|
-
};
|
|
45
|
-
type Props = DefaultProps & {
|
|
46
|
-
apiOptions?: APIOptions;
|
|
47
|
-
imageUploader?: (arg1: string, arg2: (arg1: string) => unknown) => unknown;
|
|
48
|
-
// URL of the route to show on initial load of the preview frames.
|
|
49
|
-
previewURL: string;
|
|
50
|
-
} & Changeable.ChangeableProps;
|
|
51
|
-
|
|
52
|
-
type State = {
|
|
53
|
-
highlightLint: boolean;
|
|
54
|
-
};
|
|
55
|
-
export default class ArticleEditor extends React.Component<Props, State> {
|
|
56
|
-
static defaultProps: DefaultProps = {
|
|
57
|
-
contentPaths: [],
|
|
58
|
-
json: [{}],
|
|
59
|
-
mode: "edit",
|
|
60
|
-
screen: "desktop",
|
|
61
|
-
sectionImageUploadGenerator: () => <span />,
|
|
62
|
-
useNewStyles: false,
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
state: State = {
|
|
66
|
-
highlightLint: true,
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
componentDidMount() {
|
|
70
|
-
this._updatePreviewFrames();
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
componentDidUpdate() {
|
|
74
|
-
this._updatePreviewFrames();
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
_updatePreviewFrames() {
|
|
78
|
-
if (this.props.mode === "preview") {
|
|
79
|
-
// eslint-disable-next-line react/no-string-refs
|
|
80
|
-
// @ts-expect-error - TS2339 - Property 'sendNewData' does not exist on type 'ReactInstance'.
|
|
81
|
-
this.refs["frame-all"].sendNewData({
|
|
82
|
-
type: "article-all",
|
|
83
|
-
data: this._sections().map((section, i) => {
|
|
84
|
-
return this._apiOptionsForSection(section, i);
|
|
85
|
-
}),
|
|
86
|
-
});
|
|
87
|
-
} else if (this.props.mode === "edit") {
|
|
88
|
-
this._sections().forEach((section, i) => {
|
|
89
|
-
// eslint-disable-next-line react/no-string-refs
|
|
90
|
-
// @ts-expect-error - TS2339 - Property 'sendNewData' does not exist on type 'ReactInstance'.
|
|
91
|
-
this.refs["frame-" + i].sendNewData({
|
|
92
|
-
type: "article",
|
|
93
|
-
data: this._apiOptionsForSection(section, i),
|
|
94
|
-
});
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
_apiOptionsForSection(section: RendererProps, sectionIndex: number): any {
|
|
100
|
-
// eslint-disable-next-line react/no-string-refs
|
|
101
|
-
const editor = this.refs[`editor${sectionIndex}`];
|
|
102
|
-
return {
|
|
103
|
-
apiOptions: {
|
|
104
|
-
...ApiOptions.defaults,
|
|
105
|
-
...this.props.apiOptions,
|
|
106
|
-
|
|
107
|
-
// Alignment options are always available in article
|
|
108
|
-
// editors
|
|
109
|
-
showAlignmentOptions: true,
|
|
110
|
-
isArticle: true,
|
|
111
|
-
},
|
|
112
|
-
json: section,
|
|
113
|
-
useNewStyles: this.props.useNewStyles,
|
|
114
|
-
linterContext: {
|
|
115
|
-
contentType: "article",
|
|
116
|
-
highlightLint: this.state.highlightLint,
|
|
117
|
-
paths: this.props.contentPaths,
|
|
118
|
-
},
|
|
119
|
-
// @ts-expect-error - TS2339 - Property 'getSaveWarnings' does not exist on type 'ReactInstance'.
|
|
120
|
-
legacyPerseusLint: editor ? editor.getSaveWarnings() : [],
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
_sections(): ReadonlyArray<RendererProps> {
|
|
125
|
-
return Array.isArray(this.props.json)
|
|
126
|
-
? this.props.json
|
|
127
|
-
: [this.props.json];
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
_renderEditor(): React.ReactElement<React.ComponentProps<"div">> {
|
|
131
|
-
const {imageUploader, sectionImageUploadGenerator} = this.props;
|
|
132
|
-
|
|
133
|
-
const apiOptions = {
|
|
134
|
-
...ApiOptions.defaults,
|
|
135
|
-
...this.props.apiOptions,
|
|
136
|
-
|
|
137
|
-
// Alignment options are always available in article editors
|
|
138
|
-
showAlignmentOptions: true,
|
|
139
|
-
isArticle: true,
|
|
140
|
-
} as const;
|
|
141
|
-
|
|
142
|
-
const sections = this._sections();
|
|
143
|
-
|
|
144
|
-
return (
|
|
145
|
-
<div className="perseus-editor-table">
|
|
146
|
-
{sections.map((section, i) => {
|
|
147
|
-
return [
|
|
148
|
-
<div className="perseus-editor-row" key={i}>
|
|
149
|
-
<div className="perseus-editor-left-cell">
|
|
150
|
-
<div className="pod-title">
|
|
151
|
-
Section {i + 1}
|
|
152
|
-
<div
|
|
153
|
-
style={{
|
|
154
|
-
display: "inline-block",
|
|
155
|
-
float: "right",
|
|
156
|
-
}}
|
|
157
|
-
>
|
|
158
|
-
{sectionImageUploadGenerator(i)}
|
|
159
|
-
<SectionControlButton
|
|
160
|
-
icon={iconPlus}
|
|
161
|
-
onClick={() => {
|
|
162
|
-
this._handleAddSectionAfter(i);
|
|
163
|
-
}}
|
|
164
|
-
title="Add a new section after this one"
|
|
165
|
-
/>
|
|
166
|
-
{i + 1 < sections.length && (
|
|
167
|
-
<SectionControlButton
|
|
168
|
-
icon={iconCircleArrowDown}
|
|
169
|
-
onClick={() => {
|
|
170
|
-
this._handleMoveSectionLater(
|
|
171
|
-
i,
|
|
172
|
-
);
|
|
173
|
-
}}
|
|
174
|
-
title="Move this section down"
|
|
175
|
-
/>
|
|
176
|
-
)}
|
|
177
|
-
{i > 0 && (
|
|
178
|
-
<SectionControlButton
|
|
179
|
-
icon={iconCircleArrowUp}
|
|
180
|
-
onClick={() => {
|
|
181
|
-
this._handleMoveSectionEarlier(
|
|
182
|
-
i,
|
|
183
|
-
);
|
|
184
|
-
}}
|
|
185
|
-
title="Move this section up"
|
|
186
|
-
/>
|
|
187
|
-
)}
|
|
188
|
-
<SectionControlButton
|
|
189
|
-
icon={iconTrash}
|
|
190
|
-
onClick={() => {
|
|
191
|
-
const msg =
|
|
192
|
-
"Are you sure you " +
|
|
193
|
-
"want to delete section " +
|
|
194
|
-
(i + 1) +
|
|
195
|
-
"?";
|
|
196
|
-
/* eslint-disable no-alert */
|
|
197
|
-
if (confirm(msg)) {
|
|
198
|
-
this._handleRemoveSection(
|
|
199
|
-
i,
|
|
200
|
-
);
|
|
201
|
-
}
|
|
202
|
-
/* eslint-enable no-alert */
|
|
203
|
-
}}
|
|
204
|
-
title="Delete this section"
|
|
205
|
-
/>
|
|
206
|
-
</div>
|
|
207
|
-
</div>
|
|
208
|
-
<Editor
|
|
209
|
-
{...section}
|
|
210
|
-
apiOptions={apiOptions}
|
|
211
|
-
imageUploader={imageUploader}
|
|
212
|
-
onChange={_.partial(
|
|
213
|
-
this._handleEditorChange,
|
|
214
|
-
i,
|
|
215
|
-
)}
|
|
216
|
-
placeholder="Type your section text here..."
|
|
217
|
-
ref={"editor" + i}
|
|
218
|
-
/>
|
|
219
|
-
</div>
|
|
220
|
-
|
|
221
|
-
<div className="editor-preview">
|
|
222
|
-
{this._renderIframePreview(i, true)}
|
|
223
|
-
</div>
|
|
224
|
-
</div>,
|
|
225
|
-
];
|
|
226
|
-
})}
|
|
227
|
-
{this._renderAddSection()}
|
|
228
|
-
{this._renderLinterHUD()}
|
|
229
|
-
</div>
|
|
230
|
-
);
|
|
231
|
-
/* eslint-enable max-len */
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
_renderAddSection(): React.ReactElement<React.ComponentProps<"div">> {
|
|
235
|
-
return (
|
|
236
|
-
<div className="perseus-editor-row">
|
|
237
|
-
<div className="perseus-editor-left-cell">
|
|
238
|
-
<a
|
|
239
|
-
href="#"
|
|
240
|
-
className="simple-button orange"
|
|
241
|
-
onClick={() => {
|
|
242
|
-
this._handleAddSectionAfter(
|
|
243
|
-
this._sections().length - 1,
|
|
244
|
-
);
|
|
245
|
-
}}
|
|
246
|
-
>
|
|
247
|
-
<InlineIcon {...iconPlus} /> Add a section
|
|
248
|
-
</a>
|
|
249
|
-
</div>
|
|
250
|
-
</div>
|
|
251
|
-
);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
_renderLinterHUD(): React.ReactElement<any> {
|
|
255
|
-
return (
|
|
256
|
-
<HUD
|
|
257
|
-
message="Style warnings"
|
|
258
|
-
enabled={this.state.highlightLint}
|
|
259
|
-
onClick={() => {
|
|
260
|
-
this.setState({
|
|
261
|
-
highlightLint: !this.state.highlightLint,
|
|
262
|
-
});
|
|
263
|
-
}}
|
|
264
|
-
/>
|
|
265
|
-
);
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
_renderIframePreview(
|
|
269
|
-
i: number | string,
|
|
270
|
-
nochrome: boolean,
|
|
271
|
-
): React.ReactElement<any> {
|
|
272
|
-
const isMobile =
|
|
273
|
-
this.props.screen === "phone" || this.props.screen === "tablet";
|
|
274
|
-
|
|
275
|
-
return (
|
|
276
|
-
<DeviceFramer deviceType={this.props.screen} nochrome={nochrome}>
|
|
277
|
-
<IframeContentRenderer
|
|
278
|
-
ref={"frame-" + i}
|
|
279
|
-
key={this.props.screen}
|
|
280
|
-
datasetKey="mobile"
|
|
281
|
-
datasetValue={isMobile}
|
|
282
|
-
seamless={nochrome}
|
|
283
|
-
url={this.props.previewURL}
|
|
284
|
-
/>
|
|
285
|
-
</DeviceFramer>
|
|
286
|
-
);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
_renderPreviewMode(): React.ReactElement<React.ComponentProps<"div">> {
|
|
290
|
-
return (
|
|
291
|
-
<div className="standalone-preview">
|
|
292
|
-
{this._renderIframePreview("all", false)}
|
|
293
|
-
</div>
|
|
294
|
-
);
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
_handleJsonChange: (newJson: JsonType) => void = (newJson) => {
|
|
298
|
-
this.props.onChange({json: newJson});
|
|
299
|
-
};
|
|
300
|
-
|
|
301
|
-
_handleEditorChange: (i: number, newProps: RendererProps) => void = (
|
|
302
|
-
i,
|
|
303
|
-
newProps,
|
|
304
|
-
) => {
|
|
305
|
-
const sections = _.clone(this._sections());
|
|
306
|
-
// @ts-expect-error - TS2542 - Index signature in type 'readonly RendererProps[]' only permits reading.
|
|
307
|
-
sections[i] = _.extend({}, sections[i], newProps);
|
|
308
|
-
this.props.onChange({json: sections});
|
|
309
|
-
};
|
|
310
|
-
|
|
311
|
-
_handleMoveSectionEarlier(i: number) {
|
|
312
|
-
if (i === 0) {
|
|
313
|
-
return;
|
|
314
|
-
}
|
|
315
|
-
const sections = _.clone(this._sections());
|
|
316
|
-
const section = sections[i];
|
|
317
|
-
// @ts-expect-error - TS2551 - Property 'splice' does not exist on type 'readonly RendererProps[]'. Did you mean 'slice'?
|
|
318
|
-
sections.splice(i, 1);
|
|
319
|
-
// @ts-expect-error - TS2551 - Property 'splice' does not exist on type 'readonly RendererProps[]'. Did you mean 'slice'?
|
|
320
|
-
sections.splice(i - 1, 0, section);
|
|
321
|
-
this.props.onChange({
|
|
322
|
-
json: sections,
|
|
323
|
-
});
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
_handleMoveSectionLater(i: number) {
|
|
327
|
-
const sections = _.clone(this._sections());
|
|
328
|
-
if (i + 1 === sections.length) {
|
|
329
|
-
return;
|
|
330
|
-
}
|
|
331
|
-
const section = sections[i];
|
|
332
|
-
// @ts-expect-error - TS2551 - Property 'splice' does not exist on type 'readonly RendererProps[]'. Did you mean 'slice'?
|
|
333
|
-
sections.splice(i, 1);
|
|
334
|
-
// @ts-expect-error - TS2551 - Property 'splice' does not exist on type 'readonly RendererProps[]'. Did you mean 'slice'?
|
|
335
|
-
sections.splice(i + 1, 0, section);
|
|
336
|
-
this.props.onChange({
|
|
337
|
-
json: sections,
|
|
338
|
-
});
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
_handleAddSectionAfter(i: number) {
|
|
342
|
-
// We do a full serialization here because we
|
|
343
|
-
// might be copying widgets:
|
|
344
|
-
const sections = _.clone(this.serialize());
|
|
345
|
-
// Here we do magic to allow you to copy-paste
|
|
346
|
-
// things from the previous section into the new
|
|
347
|
-
// section while preserving widgets.
|
|
348
|
-
// To enable this, we preserve the widgets
|
|
349
|
-
// object for the new section, but wipe out
|
|
350
|
-
// the content.
|
|
351
|
-
const newSection =
|
|
352
|
-
i >= 0
|
|
353
|
-
? {
|
|
354
|
-
widgets: sections[i].widgets,
|
|
355
|
-
}
|
|
356
|
-
: {};
|
|
357
|
-
// @ts-expect-error - TS2339 - Property 'splice' does not exist on type 'JsonType'.
|
|
358
|
-
sections.splice(i + 1, 0, newSection);
|
|
359
|
-
this.props.onChange({
|
|
360
|
-
json: sections,
|
|
361
|
-
});
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
_handleRemoveSection(i: number) {
|
|
365
|
-
const sections = _.clone(this._sections());
|
|
366
|
-
// @ts-expect-error - TS2551 - Property 'splice' does not exist on type 'readonly RendererProps[]'. Did you mean 'slice'?
|
|
367
|
-
sections.splice(i, 1);
|
|
368
|
-
this.props.onChange({
|
|
369
|
-
json: sections,
|
|
370
|
-
});
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
serialize(): JsonType {
|
|
374
|
-
if (this.props.mode === "edit") {
|
|
375
|
-
return this._sections().map((section, i) => {
|
|
376
|
-
// eslint-disable-next-line react/no-string-refs
|
|
377
|
-
// @ts-expect-error - TS2339 - Property 'serialize' does not exist on type 'ReactInstance'.
|
|
378
|
-
return this.refs["editor" + i].serialize();
|
|
379
|
-
});
|
|
380
|
-
}
|
|
381
|
-
if (this.props.mode === "preview" || this.props.mode === "json") {
|
|
382
|
-
return this.props.json;
|
|
383
|
-
}
|
|
384
|
-
throw new PerseusError(
|
|
385
|
-
"Could not serialize; mode " + this.props.mode + " not found",
|
|
386
|
-
Errors.Internal,
|
|
387
|
-
);
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
/**
|
|
391
|
-
* Returns an array, with one element be section.
|
|
392
|
-
* Each element is an array of lint warnings present in that section.
|
|
393
|
-
*
|
|
394
|
-
* This function can currently only be called in edit mode.
|
|
395
|
-
*/
|
|
396
|
-
getSaveWarnings(): ReadonlyArray<RendererProps> {
|
|
397
|
-
if (this.props.mode !== "edit") {
|
|
398
|
-
// TODO(joshuan): We should be able to get save warnings in
|
|
399
|
-
// preview mode.
|
|
400
|
-
throw new PerseusError(
|
|
401
|
-
"Can only get save warnings in edit mode.",
|
|
402
|
-
Errors.NotAllowed,
|
|
403
|
-
);
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
return this._sections().map((section, i) => {
|
|
407
|
-
// eslint-disable-next-line react/no-string-refs
|
|
408
|
-
// @ts-expect-error - TS2339 - Property 'getSaveWarnings' does not exist on type 'ReactInstance'.
|
|
409
|
-
return this.refs["editor" + i].getSaveWarnings();
|
|
410
|
-
});
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
render(): React.ReactNode {
|
|
414
|
-
return (
|
|
415
|
-
<div className="framework-perseus perseus-article-editor">
|
|
416
|
-
{this.props.mode === "edit" && this._renderEditor()}
|
|
417
|
-
|
|
418
|
-
{this.props.mode === "preview" && this._renderPreviewMode()}
|
|
419
|
-
|
|
420
|
-
{this.props.mode === "json" && (
|
|
421
|
-
<div className="json-editor">
|
|
422
|
-
<div className="json-editor-warning">
|
|
423
|
-
<span>
|
|
424
|
-
Warning: Editing in this mode can lead to broken
|
|
425
|
-
articles!
|
|
426
|
-
</span>
|
|
427
|
-
</div>
|
|
428
|
-
<JsonEditor
|
|
429
|
-
multiLine={true}
|
|
430
|
-
onChange={this._handleJsonChange}
|
|
431
|
-
value={this.props.json}
|
|
432
|
-
/>
|
|
433
|
-
</div>
|
|
434
|
-
)}
|
|
435
|
-
</div>
|
|
436
|
-
);
|
|
437
|
-
}
|
|
438
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import {action} from "@storybook/addon-actions";
|
|
2
|
-
import * as React from "react";
|
|
3
|
-
|
|
4
|
-
import BlurInput from "../blur-input";
|
|
5
|
-
|
|
6
|
-
export default {
|
|
7
|
-
title: "Perseus/Editor/Components/Blur Input",
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
export const Default = (): React.ReactElement => {
|
|
11
|
-
const [value, setValue] = React.useState("");
|
|
12
|
-
|
|
13
|
-
return (
|
|
14
|
-
<BlurInput
|
|
15
|
-
value={value}
|
|
16
|
-
onChange={(newValue) => {
|
|
17
|
-
action("onChange")(newValue);
|
|
18
|
-
setValue(newValue);
|
|
19
|
-
}}
|
|
20
|
-
/>
|
|
21
|
-
);
|
|
22
|
-
};
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import {icons} from "@khanacademy/perseus";
|
|
2
|
-
import * as React from "react";
|
|
3
|
-
|
|
4
|
-
import SectionControlButton from "../section-control-button";
|
|
5
|
-
|
|
6
|
-
type StoryArgs = Record<any, any>;
|
|
7
|
-
|
|
8
|
-
type Story = {
|
|
9
|
-
title: string;
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
export default {
|
|
13
|
-
title: "Perseus/Editor/Components/Section Control Button",
|
|
14
|
-
} as Story;
|
|
15
|
-
|
|
16
|
-
export const ButtonForEditingSectionsOfContentWithInArticleEditor = (
|
|
17
|
-
args: StoryArgs,
|
|
18
|
-
): React.ReactElement => {
|
|
19
|
-
return (
|
|
20
|
-
<SectionControlButton
|
|
21
|
-
icon={icons.iconTrash}
|
|
22
|
-
onClick={() => {}}
|
|
23
|
-
title="Remove image widget"
|
|
24
|
-
/>
|
|
25
|
-
);
|
|
26
|
-
};
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import {render, screen} from "@testing-library/react";
|
|
2
|
-
import userEvent from "@testing-library/user-event";
|
|
3
|
-
import * as React from "react";
|
|
4
|
-
|
|
5
|
-
import BlurInput from "../blur-input";
|
|
6
|
-
|
|
7
|
-
import "@testing-library/jest-dom"; // Imports custom matchers
|
|
8
|
-
|
|
9
|
-
describe("BlurInput", () => {
|
|
10
|
-
it("should render", () => {
|
|
11
|
-
render(<BlurInput value="Hello world!" onChange={() => {}} />);
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
it("should call onChange prop on blur", () => {
|
|
15
|
-
// Arrange
|
|
16
|
-
const onChange = jest.fn();
|
|
17
|
-
render(<BlurInput value="Hello world!" onChange={onChange} />);
|
|
18
|
-
|
|
19
|
-
// Act
|
|
20
|
-
userEvent.type(screen.getByRole("textbox"), "Hello Khan Academy!");
|
|
21
|
-
userEvent.tab();
|
|
22
|
-
|
|
23
|
-
// Assert
|
|
24
|
-
expect(onChange).toHaveBeenCalledTimes(1);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it("should call onChange prop when typing into input", () => {
|
|
28
|
-
// Arrange
|
|
29
|
-
const onChange = jest.fn();
|
|
30
|
-
render(<BlurInput value="Hello world!" onChange={onChange} />);
|
|
31
|
-
|
|
32
|
-
// Act
|
|
33
|
-
userEvent.type(screen.getByRole("textbox"), "Hello Khan Academy!");
|
|
34
|
-
// NO TAB
|
|
35
|
-
|
|
36
|
-
// Assert
|
|
37
|
-
expect(onChange).not.toHaveBeenCalledTimes(1);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it("should call onChange prop when pasting into input", () => {
|
|
41
|
-
// Arrange
|
|
42
|
-
const onChange = jest.fn();
|
|
43
|
-
render(<BlurInput value="Hello world!" onChange={onChange} />);
|
|
44
|
-
|
|
45
|
-
// Act
|
|
46
|
-
userEvent.paste(screen.getByRole("textbox"), "Hello Khan Academy!");
|
|
47
|
-
// NO TAB
|
|
48
|
-
|
|
49
|
-
// Assert
|
|
50
|
-
expect(onChange).not.toHaveBeenCalledTimes(1);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it("should focus input through focus function", () => {
|
|
54
|
-
const ref = React.createRef<BlurInput>();
|
|
55
|
-
|
|
56
|
-
// Arrange
|
|
57
|
-
render(
|
|
58
|
-
<BlurInput ref={ref} value="Hello world!" onChange={() => {}} />,
|
|
59
|
-
);
|
|
60
|
-
|
|
61
|
-
// Act
|
|
62
|
-
ref.current?.focus();
|
|
63
|
-
|
|
64
|
-
// Assert
|
|
65
|
-
expect(screen.getByRole("textbox")).toHaveFocus();
|
|
66
|
-
});
|
|
67
|
-
});
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
|
|
3
|
-
import type {CSSProperties} from "aphrodite";
|
|
4
|
-
|
|
5
|
-
type Props = {
|
|
6
|
-
value: string;
|
|
7
|
-
onChange: (arg1: string) => void;
|
|
8
|
-
className?: string;
|
|
9
|
-
style?: CSSProperties;
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
type State = {
|
|
13
|
-
value: string;
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
/* You know when you want to propagate input to a parent...
|
|
17
|
-
* but then that parent does something with the input...
|
|
18
|
-
* then changing the props of the input...
|
|
19
|
-
* on every keystroke...
|
|
20
|
-
* so if some input is invalid or incomplete...
|
|
21
|
-
* the input gets reset or otherwise effed...
|
|
22
|
-
*
|
|
23
|
-
* This is the solution.
|
|
24
|
-
*
|
|
25
|
-
* Enough melodrama. Its an input that only sends changes
|
|
26
|
-
* to its parent on blur.
|
|
27
|
-
*/
|
|
28
|
-
// eslint-disable-next-line react/no-unsafe
|
|
29
|
-
class BlurInput extends React.Component<Props, State> {
|
|
30
|
-
input = React.createRef<HTMLInputElement>();
|
|
31
|
-
|
|
32
|
-
constructor(props: Props) {
|
|
33
|
-
super(props);
|
|
34
|
-
this.state = {value: this.props.value};
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// eslint-disable-next-line react/no-unsafe
|
|
38
|
-
UNSAFE_componentWillReceiveProps(nextProps: Props) {
|
|
39
|
-
this.setState({value: nextProps.value});
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
handleChange: (e: any) => void = (e) => {
|
|
43
|
-
this.setState({value: e.target.value});
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
handleBlur: (e: any) => void = (e) => {
|
|
47
|
-
this.props.onChange(e.target.value);
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
focus() {
|
|
51
|
-
this.input.current?.focus();
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
render(): React.ReactNode {
|
|
55
|
-
return (
|
|
56
|
-
<input
|
|
57
|
-
ref={this.input}
|
|
58
|
-
className={this.props.className}
|
|
59
|
-
style={this.props.style}
|
|
60
|
-
type="text"
|
|
61
|
-
value={this.state.value}
|
|
62
|
-
onChange={this.handleChange}
|
|
63
|
-
onBlur={this.handleBlur}
|
|
64
|
-
/>
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export default BlurInput;
|