@khanacademy/perseus-editor 0.0.1 → 0.1.2

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 (127) hide show
  1. package/.babelrc.js +1 -1
  2. package/CHANGELOG.md +26 -0
  3. package/dist/es/index.css +1020 -0
  4. package/dist/es/index.js +9 -1
  5. package/dist/es/index.js.map +1 -1
  6. package/dist/index.css +1020 -0
  7. package/dist/index.js +9 -1
  8. package/dist/index.js.map +1 -1
  9. package/dist/strings.js +6 -1
  10. package/package.json +16 -4
  11. package/src/README.md +7 -0
  12. package/src/__stories__/editor.stories.js +33 -0
  13. package/src/__stories__/section-control-button.stories.jsx +27 -0
  14. package/src/__testdata__/input-number_testdata.js +28 -0
  15. package/src/__tests__/editor_test.jsx +123 -0
  16. package/src/__tests__/i18n_test.jsx +585 -0
  17. package/src/__tests__/traversal_test.jsx +378 -0
  18. package/src/all-editors.js +85 -0
  19. package/src/all-editors_test.js +17 -0
  20. package/src/all-widgets.js +10 -0
  21. package/src/article-editor.jsx +437 -0
  22. package/src/components/__stories__/blur-input.stories.jsx +24 -0
  23. package/src/components/blur-input.jsx +62 -0
  24. package/src/components/checkbox.jsx +194 -0
  25. package/src/components/device-framer.jsx +91 -0
  26. package/src/components/drag-target.jsx +88 -0
  27. package/src/components/dropdown-option.jsx +318 -0
  28. package/src/components/form-wrapped-text-field.jsx +248 -0
  29. package/src/components/graph-settings.jsx +631 -0
  30. package/src/components/hover-behavior.jsx +178 -0
  31. package/src/components/json-editor.jsx +121 -0
  32. package/src/components/link.jsx +173 -0
  33. package/src/components/simple-button.jsx +86 -0
  34. package/src/components/sortable.jsx +186 -0
  35. package/src/components/util.js +39 -0
  36. package/src/components/viewport-resizer.jsx +73 -0
  37. package/src/diffs/__stories__/perseus-diff-wrapper.jsx +8 -0
  38. package/src/diffs/__stories__/structured-item-diff.stories.jsx +855 -0
  39. package/src/diffs/__stories__/tags-diff.stories.jsx +38 -0
  40. package/src/diffs/__stories__/text-diff.stories.jsx +37 -0
  41. package/src/diffs/__tests__/split-diff_test.js +34 -0
  42. package/src/diffs/__tests__/string-array-diff_test.js +74 -0
  43. package/src/diffs/__tests__/structured-item-diff_test.js +432 -0
  44. package/src/diffs/__tests__/widget-diff-performer_test.js +44 -0
  45. package/src/diffs/article-diff.jsx +70 -0
  46. package/src/diffs/item-diff.jsx +77 -0
  47. package/src/diffs/renderer-diff.jsx +143 -0
  48. package/src/diffs/split-diff.jsx +30 -0
  49. package/src/diffs/string-array-diff.jsx +75 -0
  50. package/src/diffs/structured-item-diff.jsx +269 -0
  51. package/src/diffs/tags-diff.jsx +50 -0
  52. package/src/diffs/text-diff.jsx +194 -0
  53. package/src/diffs/widget-diff-performer.jsx +74 -0
  54. package/src/diffs/widget-diff.jsx +291 -0
  55. package/src/editor-page.jsx +351 -0
  56. package/src/editor.jsx +1364 -0
  57. package/src/hint-editor.jsx +447 -0
  58. package/src/i18n.jsx +139 -0
  59. package/src/iframe-content-renderer.jsx +201 -0
  60. package/src/index.js +27 -8
  61. package/src/item-editor.jsx +159 -0
  62. package/src/item-extras-editor.jsx +150 -0
  63. package/src/katex-error-view.jsx +87 -0
  64. package/src/multirenderer-editor.jsx +1074 -0
  65. package/src/section-control-button.jsx +53 -0
  66. package/src/stateful-article-editor.jsx +176 -0
  67. package/src/stateful-editor-page.jsx +90 -0
  68. package/src/styles/perseus-editor.less +1339 -0
  69. package/src/testing-widgets.js +12 -0
  70. package/src/util/register-all-widgets-and-editors-for-testing.js +15 -0
  71. package/src/util/register-all-widgets-and-editors-for-testing_test.js +18 -0
  72. package/src/widgets/__stories__/label-image-editor.stories.jsx +97 -0
  73. package/src/widgets/categorizer-editor.jsx +103 -0
  74. package/src/widgets/cs-program-editor.jsx +258 -0
  75. package/src/widgets/definition-editor.jsx +75 -0
  76. package/src/widgets/dropdown-editor.jsx +201 -0
  77. package/src/widgets/example-graphie-widget-editor.jsx +70 -0
  78. package/src/widgets/example-graphie-widget.jsx +175 -0
  79. package/src/widgets/example-widget-editor.jsx +60 -0
  80. package/src/widgets/example-widget.jsx +153 -0
  81. package/src/widgets/explanation-editor.jsx +89 -0
  82. package/src/widgets/expression-editor.jsx +560 -0
  83. package/src/widgets/graded-group-editor.jsx +162 -0
  84. package/src/widgets/graded-group-set-editor.jsx +101 -0
  85. package/src/widgets/grapher-editor.jsx +154 -0
  86. package/src/widgets/group-editor.jsx +83 -0
  87. package/src/widgets/iframe-editor.jsx +200 -0
  88. package/src/widgets/image-editor.jsx +326 -0
  89. package/src/widgets/input-number-editor.jsx +269 -0
  90. package/src/widgets/interaction/constraint-editor.jsx +147 -0
  91. package/src/widgets/interaction-editor.jsx +1389 -0
  92. package/src/widgets/interactive-graph-editor.jsx +322 -0
  93. package/src/widgets/label-image/__stories__/answer-choices.stories.jsx +81 -0
  94. package/src/widgets/label-image/__stories__/behavior.stories.jsx +32 -0
  95. package/src/widgets/label-image/__stories__/marker.stories.jsx +76 -0
  96. package/src/widgets/label-image/__stories__/question-markers.stories.jsx +106 -0
  97. package/src/widgets/label-image/__stories__/select-image.stories.jsx +60 -0
  98. package/src/widgets/label-image/answer-choices.jsx +231 -0
  99. package/src/widgets/label-image/behavior.jsx +92 -0
  100. package/src/widgets/label-image/marker.jsx +292 -0
  101. package/src/widgets/label-image/question-markers.jsx +181 -0
  102. package/src/widgets/label-image/select-image.jsx +86 -0
  103. package/src/widgets/label-image-editor.jsx +257 -0
  104. package/src/widgets/lights-puzzle-editor.jsx +267 -0
  105. package/src/widgets/matcher-editor.jsx +147 -0
  106. package/src/widgets/matrix-editor.jsx +155 -0
  107. package/src/widgets/measurer-editor.jsx +245 -0
  108. package/src/widgets/molecule-editor.jsx +63 -0
  109. package/src/widgets/number-line-editor.jsx +498 -0
  110. package/src/widgets/numeric-input-editor.jsx +554 -0
  111. package/src/widgets/orderer-editor.jsx +166 -0
  112. package/src/widgets/passage-editor.jsx +115 -0
  113. package/src/widgets/passage-ref-editor.jsx +77 -0
  114. package/src/widgets/passage-ref-target-editor.jsx +47 -0
  115. package/src/widgets/plotter-editor.jsx +537 -0
  116. package/src/widgets/radio/editor.jsx +398 -0
  117. package/src/widgets/reaction-diagram-editor.jsx +127 -0
  118. package/src/widgets/sequence-editor.jsx +205 -0
  119. package/src/widgets/simple-markdown-tester-editor.jsx +84 -0
  120. package/src/widgets/simple-markdown-tester.jsx +91 -0
  121. package/src/widgets/simulator-editor.jsx +144 -0
  122. package/src/widgets/sorter-editor.jsx +100 -0
  123. package/src/widgets/table-editor.jsx +164 -0
  124. package/src/widgets/transformer-editor.jsx +1006 -0
  125. package/src/widgets/unit-editor.jsx +293 -0
  126. package/src/widgets/video-editor.jsx +74 -0
  127. package/src/__tests__/index.test.js +0 -15
@@ -0,0 +1,12 @@
1
+ // @flow
2
+ import ExampleGraphieWidget from "./widgets/example-graphie-widget.jsx";
3
+ import ExampleWidget from "./widgets/example-widget.jsx";
4
+ import SimpleMarkdownTester from "./widgets/simple-markdown-tester.jsx";
5
+
6
+ import type {WidgetExports} from "@khanacademy/perseus";
7
+
8
+ export default ([
9
+ ExampleGraphieWidget,
10
+ ExampleWidget,
11
+ SimpleMarkdownTester,
12
+ ]: $ReadOnlyArray<WidgetExports<>>);
@@ -0,0 +1,15 @@
1
+ // @flow
2
+ /**
3
+ * Some tests require some or all of the widgets and editors to be registered
4
+ * in order for them to work. Requiring this file will register all of the
5
+ * widgets and editors.
6
+ */
7
+ import {Widgets} from "@khanacademy/perseus";
8
+
9
+ import allEditors from "../all-editors.js";
10
+ import allWidgets from "../all-widgets.js";
11
+
12
+ export const registerAllWidgetsAndEditorsForTesting = () => {
13
+ Widgets.registerWidgets(allWidgets);
14
+ Widgets.registerEditors(allEditors);
15
+ };
@@ -0,0 +1,18 @@
1
+ // @flow
2
+ import {Widgets} from "@khanacademy/perseus";
3
+
4
+ import {registerAllWidgetsAndEditorsForTesting} from "./register-all-widgets-and-editors-for-testing.js";
5
+
6
+ describe("Registering all widgets and editors", () => {
7
+ it("should have an editor for every widget", () => {
8
+ registerAllWidgetsAndEditorsForTesting();
9
+
10
+ const allWidgetsTypes = Widgets.getAllWidgetTypes();
11
+
12
+ expect(allWidgetsTypes).not.toContain("undefined");
13
+
14
+ for (const widgetType of allWidgetsTypes) {
15
+ expect(Widgets.getEditor(widgetType)).toBeTruthy();
16
+ }
17
+ });
18
+ });
@@ -0,0 +1,97 @@
1
+ // @flow
2
+ import {StyleSheet, css} from "aphrodite";
3
+ import * as React from "react";
4
+
5
+ import LabelImageEditor from "../label-image-editor.jsx";
6
+
7
+ import type {MarkerType} from "@khanacademy/perseus";
8
+
9
+ type StoryArgs = {||};
10
+
11
+ type Story = {|
12
+ title: string,
13
+ |};
14
+
15
+ export default ({
16
+ title: "Perseus/Editor/Widgets/Label Image Editor",
17
+ }: Story);
18
+
19
+ const styles = StyleSheet.create({
20
+ wrapper: {
21
+ // The maximum width of a widget in the editor.
22
+ width: 338,
23
+ },
24
+ });
25
+
26
+ type State = {|
27
+ imageAlt: string,
28
+ choices: $ReadOnlyArray<string>,
29
+ imageUrl: string,
30
+ imageWidth: number,
31
+ imageHeight: number,
32
+ markers: $ReadOnlyArray<MarkerType>,
33
+ |};
34
+
35
+ class WithState extends React.Component<Empty, State> {
36
+ _widget: LabelImageEditor;
37
+
38
+ state = {
39
+ imageAlt: "Map of Europe",
40
+ choices: [
41
+ "Lamborghini",
42
+ "BMW",
43
+ "Volkswagen",
44
+ "Fiat",
45
+ "Porsche",
46
+ "Ferrari",
47
+ ],
48
+ imageUrl:
49
+ "https://ka-perseus-images.s3.amazonaws.com/2ee5fc32e35c5178373b39fd304b325b2994c913.png",
50
+ imageWidth: 1280,
51
+ imageHeight: 1024,
52
+ markers: [
53
+ {
54
+ answers: ["BMW", "Volkswagen", "Porsche"],
55
+ label: "Germany",
56
+ x: 37.3,
57
+ y: 53.6,
58
+ },
59
+ {
60
+ answers: [],
61
+ label: "",
62
+ x: 21,
63
+ y: 46,
64
+ },
65
+ {
66
+ answers: ["Lamborghini", "Fiat", "Ferrari"],
67
+ label: "Italy",
68
+ x: 41.4,
69
+ y: 78.8,
70
+ },
71
+ ],
72
+ };
73
+
74
+ render(): React.Node {
75
+ return (
76
+ <div className={css(styles.wrapper)}>
77
+ <LabelImageEditor
78
+ {...this.state}
79
+ onChange={(props) =>
80
+ this.setState({
81
+ ...this._widget.serialize(),
82
+ ...props,
83
+ })
84
+ }
85
+ // TODO(mdr): We found a new Flow error when upgrading:
86
+ // "widget (null) This type is incompatible with this._widget (LabelImageEditor)"
87
+ // $FlowFixMe[incompatible-type](0.52.0->0.53.0)
88
+ ref={(widget) => (this._widget = widget)}
89
+ />
90
+ </div>
91
+ );
92
+ }
93
+ }
94
+
95
+ export const Default = (args: StoryArgs): React.Node => {
96
+ return <WithState />;
97
+ };
@@ -0,0 +1,103 @@
1
+ // @flow
2
+ import {
3
+ components,
4
+ ApiOptions,
5
+ Categorizer as CategorizerWidget,
6
+ Changeable,
7
+ EditorJsonify,
8
+ } from "@khanacademy/perseus";
9
+ import PropTypes from "prop-types";
10
+ import * as React from "react";
11
+ import _ from "underscore";
12
+
13
+ const {PropCheckBox, TextListEditor} = components;
14
+ const Categorizer = CategorizerWidget.widget;
15
+
16
+ type Props = $FlowFixMe;
17
+
18
+ class CategorizerEditor extends React.Component<Props> {
19
+ static propTypes = {
20
+ ...Changeable.propTypes,
21
+ apiOptions: ApiOptions.propTypes,
22
+ items: PropTypes.arrayOf(PropTypes.string),
23
+ categories: PropTypes.arrayOf(PropTypes.string),
24
+ values: PropTypes.arrayOf(PropTypes.number),
25
+ randomizeItems: PropTypes.bool,
26
+ };
27
+
28
+ static widgetName: string = "categorizer";
29
+
30
+ static defaultProps: Props = {
31
+ items: [],
32
+ categories: [],
33
+ values: [],
34
+ randomizeItems: false,
35
+ };
36
+
37
+ change: ($FlowFixMe, $FlowFixMe, $FlowFixMe) => $FlowFixMe = (...args) => {
38
+ return Changeable.change.apply(this, args);
39
+ };
40
+
41
+ serialize: () => $FlowFixMe = () => {
42
+ return EditorJsonify.serialize.call(this);
43
+ };
44
+
45
+ render(): React.Node {
46
+ return (
47
+ <div>
48
+ <div className="perseus-widget-row">
49
+ <PropCheckBox
50
+ label="Randomize item order"
51
+ labelAlignment="right"
52
+ randomizeItems={this.props.randomizeItems}
53
+ onChange={this.props.onChange}
54
+ />
55
+ </div>
56
+ Categories:
57
+ <TextListEditor
58
+ options={this.props.categories}
59
+ onChange={(cat) => {
60
+ this.change("categories", cat);
61
+ }}
62
+ layout="horizontal"
63
+ />
64
+ Items:
65
+ <TextListEditor
66
+ options={this.props.items}
67
+ onChange={(items) => {
68
+ this.change({
69
+ items: items,
70
+ // TODO(eater): This truncates props.values so there
71
+ // are never more correct answers than items,
72
+ // ensuring the widget is possible to answer
73
+ // correctly. It doesn't necessarly keep each
74
+ // answer with its corresponding item if an item
75
+ // is deleted from the middle. Inconvenient, but
76
+ // it's at least possible for content creators to
77
+ // catch and fix.
78
+ values: _.first(this.props.values, items.length),
79
+ });
80
+ }}
81
+ layout="vertical"
82
+ />
83
+ {/* There are a bunch of props that renderer.jsx passes to each widget
84
+ via widget-container.jsx that we aren't passing to Categorizer here.
85
+ See perseus-all-package/types.js#WidgetProps for details. */}
86
+ {/* $FlowFixMe[prop-missing] */}
87
+ <Categorizer
88
+ apiOptions={this.props.apiOptions}
89
+ items={this.props.items}
90
+ categories={this.props.categories}
91
+ values={this.props.values}
92
+ onChange={(newProps) => {
93
+ this.props.onChange(newProps);
94
+ }}
95
+ // eslint-disable-next-line react/jsx-no-bind
96
+ trackInteraction={function () {}}
97
+ />
98
+ </div>
99
+ );
100
+ }
101
+ }
102
+
103
+ export default CategorizerEditor;
@@ -0,0 +1,258 @@
1
+ /* eslint-disable no-useless-escape */
2
+ // @flow
3
+ /**
4
+ * This editor is for embedding Khan Academy CS programs.
5
+ */
6
+
7
+ import {
8
+ components,
9
+ Changeable,
10
+ Dependencies,
11
+ EditorJsonify,
12
+ Errors,
13
+ Log,
14
+ } from "@khanacademy/perseus";
15
+ import $ from "jquery";
16
+ import PropTypes from "prop-types";
17
+ import * as React from "react";
18
+ import _ from "underscore";
19
+
20
+ import BlurInput from "../components/blur-input.jsx";
21
+
22
+ const {InfoTip, PropCheckBox} = components;
23
+
24
+ const DEFAULT_WIDTH = 400;
25
+ const DEFAULT_HEIGHT = 400;
26
+
27
+ /**
28
+ * This is used for editing a name/value pair.
29
+ */
30
+ class PairEditor extends React.Component<$FlowFixMe> {
31
+ static propTypes = {
32
+ ...Changeable.propTypes,
33
+ name: PropTypes.string,
34
+ value: PropTypes.string,
35
+ };
36
+
37
+ static defaultProps = {
38
+ name: "",
39
+ value: "",
40
+ };
41
+
42
+ change = (...args) => {
43
+ return Changeable.change.apply(this, args);
44
+ };
45
+
46
+ serialize = () => {
47
+ return EditorJsonify.serialize.call(this);
48
+ };
49
+
50
+ render(): React.Node {
51
+ return (
52
+ <fieldset className="pair-editor">
53
+ <label>
54
+ Name:{" "}
55
+ <BlurInput
56
+ value={this.props.name}
57
+ // $FlowFixMe[incompatible-type] single-param call returns a callback
58
+ onChange={this.change("name")}
59
+ />
60
+ </label>
61
+ <label>
62
+ {" "}
63
+ Value:{" "}
64
+ <BlurInput
65
+ value={this.props.value}
66
+ // $FlowFixMe[incompatible-type] single-param call returns a callback
67
+ onChange={this.change("value")}
68
+ />
69
+ </label>
70
+ </fieldset>
71
+ );
72
+ }
73
+ }
74
+
75
+ /**
76
+ * This is used for editing a set of name/value pairs.
77
+ */
78
+ class PairsEditor extends React.Component<$FlowFixMe> {
79
+ static propTypes = {
80
+ ...Changeable.propTypes,
81
+ pairs: PropTypes.arrayOf(
82
+ PropTypes.shape({
83
+ name: PropTypes.string,
84
+ value: PropTypes.string,
85
+ }),
86
+ ).isRequired,
87
+ };
88
+
89
+ change = (...args) => {
90
+ return Changeable.change.apply(this, args);
91
+ };
92
+
93
+ handlePairChange = (pairIndex, pair) => {
94
+ // If they're both non empty, add a new one
95
+ const pairs = this.props.pairs.slice();
96
+ pairs[pairIndex] = pair;
97
+
98
+ const lastPair = pairs[pairs.length - 1];
99
+ if (lastPair.name && lastPair.value) {
100
+ pairs.push({name: "", value: ""});
101
+ }
102
+ this.change("pairs", pairs);
103
+ };
104
+
105
+ serialize = () => {
106
+ return EditorJsonify.serialize.call(this);
107
+ };
108
+
109
+ render(): React.Node {
110
+ const editors = _.map(this.props.pairs, (pair, i) => {
111
+ return (
112
+ <PairEditor
113
+ key={i}
114
+ name={pair.name}
115
+ value={pair.value}
116
+ // eslint-disable-next-line react/jsx-no-bind
117
+ onChange={this.handlePairChange.bind(this, i)}
118
+ />
119
+ );
120
+ });
121
+ return <div>{editors}</div>;
122
+ }
123
+ }
124
+
125
+ const KA_PROGRAM_URL = /khanacademy\.org\/computer-programming\/[^\/]+\/(\d+)/;
126
+
127
+ /**
128
+ * Given a program URL from the site, extract its program ID.
129
+ * If the input does not match the known URL patterns, it is assumed to be
130
+ * a program ID.
131
+ */
132
+ function isolateProgramID(programUrl) {
133
+ const match = KA_PROGRAM_URL.exec(programUrl);
134
+ if (match) {
135
+ programUrl = match[1];
136
+ }
137
+
138
+ return programUrl;
139
+ }
140
+
141
+ /**
142
+ * This is the main editor for this widget, to specify all the options.
143
+ */
144
+ class CSProgramEditor extends React.Component<$FlowFixMe> {
145
+ static propTypes = {
146
+ ...Changeable.propTypes,
147
+ };
148
+
149
+ static widgetName: string = "cs-program";
150
+
151
+ static defaultProps: $FlowFixMe = {
152
+ programID: "",
153
+ programType: null,
154
+ settings: [{name: "", value: ""}],
155
+ showEditor: false,
156
+ showButtons: false,
157
+ width: DEFAULT_WIDTH,
158
+ height: DEFAULT_HEIGHT,
159
+ };
160
+
161
+ change: (...args: $ReadOnlyArray<mixed>) => $FlowFixMe = (...args) => {
162
+ // $FlowFixMe[incompatible-call]
163
+ return Changeable.change.apply(this, args);
164
+ };
165
+
166
+ _handleSettingsChange: ($FlowFixMe) => void = (settings) => {
167
+ this.change({settings: settings.pairs});
168
+ };
169
+
170
+ _handleProgramIDChange: (string) => void = (programID) => {
171
+ programID = isolateProgramID(programID);
172
+ const {isDevServer, InitialRequestUrl} = Dependencies.getDependencies();
173
+
174
+ const host = isDevServer
175
+ ? InitialRequestUrl.origin
176
+ : "https://www.khanacademy.org";
177
+ const baseUrl = `${host}/api/internal/scratchpads/${programID}`;
178
+
179
+ $.getJSON(baseUrl)
180
+ .done((programInfo) => {
181
+ const programType = programInfo.userAuthoredContentType;
182
+ this.change({
183
+ width: programInfo.width,
184
+ height: programInfo.height,
185
+ programID: programID,
186
+ programType: programType,
187
+ });
188
+ })
189
+ .fail((jqxhr, textStatus, error) => {
190
+ Log.error(
191
+ "Error retrieving scratchpad info for program ID ",
192
+ Errors.TransientService,
193
+ {
194
+ cause: error,
195
+ loggedMetadata: {
196
+ textStatus,
197
+ programID,
198
+ },
199
+ },
200
+ );
201
+ this.change({
202
+ width: DEFAULT_WIDTH,
203
+ height: DEFAULT_HEIGHT,
204
+ programID: programID,
205
+ programType: null,
206
+ });
207
+ });
208
+ };
209
+
210
+ serialize: () => $FlowFixMe = () => {
211
+ return EditorJsonify.serialize.call(this);
212
+ };
213
+
214
+ render(): React.Node {
215
+ return (
216
+ <div>
217
+ <label>
218
+ Url or Program ID:{" "}
219
+ <BlurInput
220
+ value={this.props.programID}
221
+ onChange={this._handleProgramIDChange}
222
+ />
223
+ </label>
224
+ <br />
225
+ <PropCheckBox
226
+ label="Show Editor"
227
+ showEditor={this.props.showEditor}
228
+ onChange={this.props.onChange}
229
+ />
230
+ <InfoTip>
231
+ If you show the editor, you should use the "full-width"
232
+ alignment to make room for the width of the editor.
233
+ </InfoTip>
234
+ <br />
235
+ <PropCheckBox
236
+ label="Show Buttons"
237
+ showButtons={this.props.showButtons}
238
+ onChange={this.props.onChange}
239
+ />
240
+ <br />
241
+ <label>
242
+ Settings:
243
+ <PairsEditor
244
+ name="settings"
245
+ pairs={this.props.settings}
246
+ onChange={this._handleSettingsChange}
247
+ />
248
+ <InfoTip>
249
+ Settings that you add here are available to the program
250
+ as an object returned by <code>Program.settings()</code>
251
+ </InfoTip>
252
+ </label>
253
+ </div>
254
+ );
255
+ }
256
+ }
257
+
258
+ export default CSProgramEditor;
@@ -0,0 +1,75 @@
1
+ // @flow
2
+ import {components, Changeable, EditorJsonify} from "@khanacademy/perseus";
3
+ import PropTypes from "prop-types";
4
+ import * as React from "react";
5
+ import _ from "underscore";
6
+
7
+ import Editor from "../editor.jsx";
8
+
9
+ const {TextInput} = components;
10
+
11
+ type Props = $FlowFixMe;
12
+
13
+ class DefinitionEditor extends React.Component<Props> {
14
+ static propTypes = {
15
+ ...Changeable.propTypes,
16
+ togglePrompt: PropTypes.string,
17
+ definition: PropTypes.string,
18
+ apiOptions: PropTypes.any,
19
+ };
20
+
21
+ static widgetName: string = "definition";
22
+
23
+ static defaultProps: Props = {
24
+ togglePrompt: "",
25
+ definition: "",
26
+ };
27
+
28
+ change: ($FlowFixMe, $FlowFixMe, $FlowFixMe) => $FlowFixMe = (...args) => {
29
+ return Changeable.change.apply(this, args);
30
+ };
31
+
32
+ serialize: () => $FlowFixMe = () => {
33
+ return EditorJsonify.serialize.call(this);
34
+ };
35
+
36
+ render(): React.Node {
37
+ return (
38
+ <div className="perseus-widget-definition-editor">
39
+ <a
40
+ href="https://docs.google.com/document/d/1udaPef4imOfTMhmLDlWq4SM0mxL0r3YHFZE-5J1uGfo"
41
+ target="_blank"
42
+ >
43
+ Definition style guide
44
+ </a>
45
+ <div className="perseus-widget-row">
46
+ <label>
47
+ Word to be defined:{" "}
48
+ <TextInput
49
+ value={this.props.togglePrompt}
50
+ onChange={this.change("togglePrompt")}
51
+ placeholder="define me"
52
+ />
53
+ </label>
54
+ </div>
55
+ <div className="perseus-widget-row">
56
+ <Editor
57
+ apiOptions={this.props.apiOptions}
58
+ content={this.props.definition}
59
+ widgetEnabled={false}
60
+ placeholder="definition goes here"
61
+ onChange={(props) => {
62
+ const newProps = {};
63
+ if (_.has(props, "content")) {
64
+ newProps.definition = props.content;
65
+ }
66
+ this.change(newProps);
67
+ }}
68
+ />
69
+ </div>
70
+ </div>
71
+ );
72
+ }
73
+ }
74
+
75
+ export default DefinitionEditor;