@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.
Files changed (175) hide show
  1. package/dist/es/index.js +1 -1
  2. package/dist/index.js +1 -1
  3. package/package.json +9 -6
  4. package/.babelrc.js +0 -8
  5. package/.eslintrc.js +0 -12
  6. package/CHANGELOG.md +0 -1155
  7. package/src/README.md +0 -7
  8. package/src/__stories__/editor-page.stories.tsx +0 -65
  9. package/src/__stories__/editor.stories.tsx +0 -134
  10. package/src/__stories__/item-editor.stories.tsx +0 -41
  11. package/src/__stories__/tex-error-view.stories.tsx +0 -26
  12. package/src/__testdata__/input-number.testdata.ts +0 -27
  13. package/src/__tests__/editor.test.tsx +0 -142
  14. package/src/__tests__/hint-editor.test.tsx +0 -83
  15. package/src/__tests__/i18n.test.ts +0 -501
  16. package/src/__tests__/integration.test.ts +0 -5
  17. package/src/__tests__/item-extras-editor.test.tsx +0 -85
  18. package/src/__tests__/traversal.test.ts +0 -377
  19. package/src/all-editors.test.ts +0 -16
  20. package/src/all-editors.ts +0 -83
  21. package/src/all-widgets.ts +0 -7
  22. package/src/article-editor.tsx +0 -438
  23. package/src/components/__stories__/blur-input.stories.tsx +0 -22
  24. package/src/components/__stories__/section-control-button.stories.tsx +0 -26
  25. package/src/components/__tests__/blur-input.test.tsx +0 -67
  26. package/src/components/blur-input.tsx +0 -69
  27. package/src/components/checkbox.tsx +0 -194
  28. package/src/components/device-framer.tsx +0 -90
  29. package/src/components/drag-target.tsx +0 -87
  30. package/src/components/dropdown-option.tsx +0 -311
  31. package/src/components/form-wrapped-text-field.tsx +0 -245
  32. package/src/components/graph-points-count-selector.tsx +0 -31
  33. package/src/components/graph-settings.tsx +0 -627
  34. package/src/components/graph-type-selector.tsx +0 -31
  35. package/src/components/hover-behavior.ts +0 -177
  36. package/src/components/json-editor.tsx +0 -121
  37. package/src/components/link.ts +0 -173
  38. package/src/components/section-control-button.tsx +0 -53
  39. package/src/components/segment-count-selector.tsx +0 -27
  40. package/src/components/simple-button.tsx +0 -85
  41. package/src/components/sortable.tsx +0 -187
  42. package/src/components/util.ts +0 -38
  43. package/src/components/viewport-resizer.tsx +0 -72
  44. package/src/components/widget-editor.tsx +0 -222
  45. package/src/components/widget-select.tsx +0 -48
  46. package/src/diffs/__stories__/perseus-diff-wrapper.tsx +0 -11
  47. package/src/diffs/__stories__/structured-item-diff.stories.tsx +0 -855
  48. package/src/diffs/__stories__/tags-diff.stories.tsx +0 -36
  49. package/src/diffs/__stories__/text-diff.stories.tsx +0 -35
  50. package/src/diffs/__tests__/split-diff.test.ts +0 -33
  51. package/src/diffs/__tests__/string-array-diff.test.ts +0 -73
  52. package/src/diffs/__tests__/structured-item-diff.test.ts +0 -433
  53. package/src/diffs/__tests__/widget-diff-performer.test.ts +0 -43
  54. package/src/diffs/article-diff.tsx +0 -69
  55. package/src/diffs/item-diff.tsx +0 -76
  56. package/src/diffs/renderer-diff.tsx +0 -137
  57. package/src/diffs/split-diff.ts +0 -31
  58. package/src/diffs/string-array-diff.ts +0 -74
  59. package/src/diffs/structured-item-diff.tsx +0 -275
  60. package/src/diffs/tags-diff.tsx +0 -49
  61. package/src/diffs/text-diff.tsx +0 -193
  62. package/src/diffs/widget-diff-performer.ts +0 -69
  63. package/src/diffs/widget-diff.tsx +0 -290
  64. package/src/editor-page.tsx +0 -325
  65. package/src/editor.tsx +0 -1099
  66. package/src/hint-editor.tsx +0 -469
  67. package/src/i18n.ts +0 -140
  68. package/src/iframe-content-renderer.tsx +0 -188
  69. package/src/index.ts +0 -30
  70. package/src/item-editor.tsx +0 -158
  71. package/src/item-extras-editor.tsx +0 -230
  72. package/src/katex-mhchem/index.js +0 -73
  73. package/src/katex-mhchem/parser.js +0 -2659
  74. package/src/multirenderer-editor.tsx +0 -1061
  75. package/src/stateful-article-editor.tsx +0 -173
  76. package/src/stateful-editor-page.tsx +0 -88
  77. package/src/styles/perseus-editor.less +0 -1321
  78. package/src/testing-widgets.ts +0 -11
  79. package/src/tex-error-view.tsx +0 -90
  80. package/src/util/points.test.ts +0 -24
  81. package/src/util/points.ts +0 -13
  82. package/src/util/register-all-widgets-and-editors-for-testing.test.ts +0 -17
  83. package/src/util/register-all-widgets-and-editors-for-testing.ts +0 -17
  84. package/src/version.ts +0 -10
  85. package/src/widgets/__stories__/categorizer-editor.stories.tsx +0 -24
  86. package/src/widgets/__stories__/definition-editor.stories.tsx +0 -18
  87. package/src/widgets/__stories__/dropdown-editor.stories.tsx +0 -18
  88. package/src/widgets/__stories__/explanation-editor.stories.tsx +0 -18
  89. package/src/widgets/__stories__/expression-editor.stories.tsx +0 -122
  90. package/src/widgets/__stories__/image-editor.stories.tsx +0 -72
  91. package/src/widgets/__stories__/input-number-editor.stories.tsx +0 -18
  92. package/src/widgets/__stories__/interaction-editor.stories.tsx +0 -37
  93. package/src/widgets/__stories__/label-image-editor.stories.tsx +0 -99
  94. package/src/widgets/__stories__/matcher-editor.stories.tsx +0 -18
  95. package/src/widgets/__stories__/number-line-editor.stories.tsx +0 -18
  96. package/src/widgets/__stories__/numeric-input-editor.stories.tsx +0 -18
  97. package/src/widgets/__stories__/radio-editor.stories.tsx +0 -116
  98. package/src/widgets/__stories__/sorter-editor.stories.tsx +0 -18
  99. package/src/widgets/__tests__/categorizer-editor.test.tsx +0 -51
  100. package/src/widgets/__tests__/definition-editor.test.tsx +0 -41
  101. package/src/widgets/__tests__/dropdown-editor.test.tsx +0 -57
  102. package/src/widgets/__tests__/explanation-editor.test.tsx +0 -59
  103. package/src/widgets/__tests__/expression-editor.test.tsx +0 -403
  104. package/src/widgets/__tests__/input-number-editor.test.tsx +0 -128
  105. package/src/widgets/__tests__/matcher-editor.test.tsx +0 -47
  106. package/src/widgets/__tests__/number-line-editor.test.tsx +0 -144
  107. package/src/widgets/__tests__/numeric-input-editor.test.tsx +0 -177
  108. package/src/widgets/__tests__/radio-editor.test.tsx +0 -118
  109. package/src/widgets/__tests__/sorter-editor.test.tsx +0 -55
  110. package/src/widgets/categorizer-editor.tsx +0 -104
  111. package/src/widgets/cs-program-editor.tsx +0 -256
  112. package/src/widgets/definition-editor.tsx +0 -77
  113. package/src/widgets/deprecated-standin-editor.tsx +0 -29
  114. package/src/widgets/dropdown-editor.tsx +0 -211
  115. package/src/widgets/example-graphie-widget-editor.tsx +0 -70
  116. package/src/widgets/example-graphie-widget.tsx +0 -172
  117. package/src/widgets/example-widget-editor.tsx +0 -62
  118. package/src/widgets/example-widget.tsx +0 -154
  119. package/src/widgets/explanation-editor.tsx +0 -92
  120. package/src/widgets/expression-editor.tsx +0 -557
  121. package/src/widgets/graded-group-editor.tsx +0 -161
  122. package/src/widgets/graded-group-set-editor.tsx +0 -104
  123. package/src/widgets/grapher-editor.tsx +0 -155
  124. package/src/widgets/group-editor.tsx +0 -83
  125. package/src/widgets/iframe-editor.tsx +0 -202
  126. package/src/widgets/image-editor.tsx +0 -367
  127. package/src/widgets/input-number-editor.tsx +0 -288
  128. package/src/widgets/interaction-editor/constraint-editor.tsx +0 -134
  129. package/src/widgets/interaction-editor/function-editor.tsx +0 -108
  130. package/src/widgets/interaction-editor/index.ts +0 -3
  131. package/src/widgets/interaction-editor/interaction-editor.tsx +0 -749
  132. package/src/widgets/interaction-editor/label-editor.tsx +0 -87
  133. package/src/widgets/interaction-editor/line-editor.tsx +0 -125
  134. package/src/widgets/interaction-editor/mathquill-input.tsx +0 -81
  135. package/src/widgets/interaction-editor/movable-line-editor.tsx +0 -121
  136. package/src/widgets/interaction-editor/movable-point-editor.tsx +0 -89
  137. package/src/widgets/interaction-editor/parametric-editor.tsx +0 -117
  138. package/src/widgets/interaction-editor/point-editor.tsx +0 -72
  139. package/src/widgets/interaction-editor/rectangle-editor.tsx +0 -96
  140. package/src/widgets/interactive-graph-editor.tsx +0 -571
  141. package/src/widgets/label-image/__stories__/answer-choices.stories.tsx +0 -82
  142. package/src/widgets/label-image/__stories__/behavior.stories.tsx +0 -31
  143. package/src/widgets/label-image/__stories__/marker.stories.tsx +0 -75
  144. package/src/widgets/label-image/__stories__/question-markers.stories.tsx +0 -107
  145. package/src/widgets/label-image/__stories__/select-image.stories.tsx +0 -59
  146. package/src/widgets/label-image/answer-choices.tsx +0 -231
  147. package/src/widgets/label-image/behavior.tsx +0 -122
  148. package/src/widgets/label-image/marker.tsx +0 -290
  149. package/src/widgets/label-image/question-markers.tsx +0 -176
  150. package/src/widgets/label-image/select-image.tsx +0 -81
  151. package/src/widgets/label-image-editor.tsx +0 -252
  152. package/src/widgets/lights-puzzle-editor.tsx +0 -278
  153. package/src/widgets/matcher-editor.tsx +0 -149
  154. package/src/widgets/matrix-editor.tsx +0 -156
  155. package/src/widgets/measurer-editor.tsx +0 -253
  156. package/src/widgets/molecule-editor.tsx +0 -64
  157. package/src/widgets/number-line-editor.tsx +0 -512
  158. package/src/widgets/numeric-input-editor.tsx +0 -536
  159. package/src/widgets/orderer-editor.tsx +0 -172
  160. package/src/widgets/passage-editor.tsx +0 -117
  161. package/src/widgets/passage-ref-editor.tsx +0 -80
  162. package/src/widgets/passage-ref-target-editor.tsx +0 -49
  163. package/src/widgets/plotter-editor.tsx +0 -542
  164. package/src/widgets/radio/editor.tsx +0 -398
  165. package/src/widgets/reaction-diagram-editor.tsx +0 -131
  166. package/src/widgets/sequence-editor.tsx +0 -205
  167. package/src/widgets/simple-markdown-tester-editor.tsx +0 -86
  168. package/src/widgets/simple-markdown-tester.tsx +0 -92
  169. package/src/widgets/simulator-editor.tsx +0 -146
  170. package/src/widgets/sorter-editor.tsx +0 -103
  171. package/src/widgets/table-editor.tsx +0 -164
  172. package/src/widgets/unit-editor.tsx +0 -299
  173. package/src/widgets/video-editor.tsx +0 -73
  174. package/tsconfig-build.json +0 -22
  175. package/tsconfig-build.tsbuildinfo +0 -1
@@ -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;