@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,53 @@
1
+ // @flow
2
+
3
+ import {components} from "@khanacademy/perseus";
4
+ import Clickable from "@khanacademy/wonder-blocks-clickable";
5
+ import Color from "@khanacademy/wonder-blocks-color";
6
+ import {StyleSheet} from "aphrodite";
7
+ import * as React from "react";
8
+
9
+ const {InlineIcon} = components;
10
+
11
+ type InlineIconProps = React.ElementConfig<typeof InlineIcon>;
12
+
13
+ type SectionControlButtonProps = {|
14
+ icon: InlineIconProps,
15
+ onClick: () => mixed,
16
+ title: string,
17
+ |};
18
+
19
+ export default class SectionControlButton extends React.Component<SectionControlButtonProps> {
20
+ render(): React$Element<typeof Clickable> {
21
+ const {icon, onClick, title} = this.props;
22
+
23
+ return (
24
+ <Clickable
25
+ className={
26
+ "section-control-button " +
27
+ "simple-button " +
28
+ "simple-button--small "
29
+ }
30
+ onClick={(e) => {
31
+ e.preventDefault();
32
+ onClick();
33
+ }}
34
+ role="button"
35
+ hideDefaultFocusRing={true}
36
+ aria-label={title}
37
+ style={styles.button}
38
+ >
39
+ {({hovered, focused, pressed}) => <InlineIcon {...icon} />}
40
+ </Clickable>
41
+ );
42
+ }
43
+ }
44
+
45
+ const styles = StyleSheet.create({
46
+ button: {
47
+ backgroundColor: Color.gold,
48
+ margin: 2,
49
+ paddingRight: 10,
50
+ paddingLeft: 10,
51
+ paddingBottom: 2,
52
+ },
53
+ });
@@ -0,0 +1,176 @@
1
+ /* eslint-disable react/no-unsafe */
2
+ // @flow
3
+
4
+ /**
5
+ * Renders an ArticleEditor as a non-controlled component.
6
+ *
7
+ * Normally the parent of ArticleEditor must pass it an onChange callback and
8
+ * then respond to any changes by modifying the ArticleEditor props to reflect
9
+ * those changes. With StatefulArticleEditor changes are stored in state so you
10
+ * can query them with serialize.
11
+ */
12
+
13
+ import createReactClass from "create-react-class";
14
+ import * as React from "react";
15
+ import _ from "underscore";
16
+
17
+ import ArticleEditor from "./article-editor.jsx";
18
+
19
+ // $FlowFixMe[signature-verification-failure]
20
+ const StatefulArticleEditor = createReactClass({
21
+ displayName: "StatefulArticleEditor",
22
+
23
+ getInitialState: function () {
24
+ return _({}).extend(this.props, {
25
+ mode: "edit",
26
+ onChange: this.handleChange,
27
+ ref: "editor",
28
+ screen: "phone",
29
+ });
30
+ },
31
+
32
+ componentDidMount: function () {
33
+ // TODO(scottgrant): This is a hack to remove the deprecated call to
34
+ // this.isMounted() but is still considered an anti-pattern.
35
+ this._isMounted = true;
36
+ },
37
+
38
+ // getInitialState isn't called if the react component is re-rendered
39
+ // in-place on the dom, in which case this is called instead, so we
40
+ // need to update the state here.
41
+ // (This component is currently re-rendered by the "Add image" button.)
42
+ UNSAFE_componentWillReceiveProps: function (nextProps) {
43
+ // be careful not to overwrite our onChange and ref
44
+ this.setState(_(nextProps).omit("onChange", "ref"));
45
+ },
46
+
47
+ componentWillUnmount: function () {
48
+ this._isMounted = false;
49
+ },
50
+
51
+ getSaveWarnings: function () {
52
+ // eslint-disable-next-line react/no-string-refs
53
+ return this.refs.editor.getSaveWarnings();
54
+ },
55
+
56
+ serialize: function () {
57
+ // eslint-disable-next-line react/no-string-refs
58
+ return this.refs.editor.serialize();
59
+ },
60
+
61
+ handleChange: function (newState, cb) {
62
+ if (this._isMounted) {
63
+ this.setState(newState, cb);
64
+ }
65
+ },
66
+
67
+ scorePreview: function () {
68
+ // eslint-disable-next-line react/no-string-refs
69
+ return this.refs.editor.scorePreview();
70
+ },
71
+
72
+ render: function () {
73
+ const {mode, screen} = this.state;
74
+ return (
75
+ <div>
76
+ <div style={styles.controlBar}>
77
+ <span style={styles.controls}>
78
+ Mode:{" "}
79
+ <span
80
+ onClick={() => this.setState({mode: "edit"})}
81
+ style={
82
+ mode === "edit"
83
+ ? styles.controlSelected
84
+ : styles.control
85
+ }
86
+ >
87
+ EDIT
88
+ </span>
89
+ {" | "}
90
+ <span
91
+ onClick={() => this.setState({mode: "preview"})}
92
+ style={
93
+ mode === "preview"
94
+ ? styles.controlSelected
95
+ : styles.control
96
+ }
97
+ >
98
+ PREVIEW
99
+ </span>
100
+ {" | "}
101
+ <span
102
+ onClick={() => this.setState({mode: "json"})}
103
+ style={
104
+ mode === "json"
105
+ ? styles.controlSelected
106
+ : styles.control
107
+ }
108
+ >
109
+ JSON
110
+ </span>
111
+ </span>
112
+ <span style={styles.controls}>
113
+ Screen:{" "}
114
+ <span
115
+ onClick={() => this.setState({screen: "phone"})}
116
+ style={
117
+ screen === "phone"
118
+ ? styles.controlSelected
119
+ : styles.control
120
+ }
121
+ >
122
+ PHONE
123
+ </span>
124
+ {" | "}
125
+ <span
126
+ onClick={() => this.setState({screen: "tablet"})}
127
+ style={
128
+ screen === "tablet"
129
+ ? styles.controlSelected
130
+ : styles.control
131
+ }
132
+ >
133
+ TABLET
134
+ </span>
135
+ {" | "}
136
+ <span
137
+ onClick={() => this.setState({screen: "desktop"})}
138
+ style={
139
+ screen === "desktop"
140
+ ? styles.controlSelected
141
+ : styles.control
142
+ }
143
+ >
144
+ DESKTOP
145
+ </span>
146
+ </span>
147
+ </div>
148
+ <div style={styles.editor}>
149
+ <ArticleEditor {...this.state} />
150
+ </div>
151
+ </div>
152
+ );
153
+ },
154
+ });
155
+
156
+ const styles = {
157
+ controlBar: {
158
+ marginBottom: 25,
159
+ marginTop: 5,
160
+ },
161
+ controls: {
162
+ marginRight: 10,
163
+ },
164
+ controlSelected: {
165
+ cursor: "default",
166
+ fontWeight: 700,
167
+ },
168
+ control: {
169
+ cursor: "pointer",
170
+ },
171
+ editor: {
172
+ marginTop: -20,
173
+ },
174
+ };
175
+
176
+ export default StatefulArticleEditor;
@@ -0,0 +1,90 @@
1
+ /* eslint-disable react/no-unsafe, react/sort-comp */
2
+ // @flow
3
+ import createReactClass from "create-react-class";
4
+ import PropTypes from "prop-types";
5
+ import * as React from "react";
6
+ import _ from "underscore";
7
+
8
+ import EditorPage from "./editor-page.jsx";
9
+
10
+ /* Renders an EditorPage (or an ArticleEditor) as a non-controlled component.
11
+ *
12
+ * Normally the parent of EditorPage must pass it an onChange callback and then
13
+ * respond to any changes by modifying the EditorPage props to reflect those
14
+ * changes. With StatefulEditorPage changes are stored in state so you can
15
+ * query them with serialize.
16
+ */
17
+ // $FlowFixMe[signature-verification-failure]
18
+ const StatefulEditorPage = createReactClass({
19
+ displayName: "StatefulEditorPage",
20
+
21
+ propTypes: {
22
+ componentClass: PropTypes.func,
23
+ },
24
+
25
+ getDefaultProps: function () {
26
+ return {
27
+ componentClass: EditorPage,
28
+ };
29
+ },
30
+
31
+ render: function () {
32
+ const Component = this.props.componentClass;
33
+ return <Component {...this.state} />;
34
+ },
35
+
36
+ getInitialState: function () {
37
+ return _({}).extend(_.omit(this.props, "componentClass"), {
38
+ onChange: this.handleChange,
39
+ ref: "editor",
40
+ });
41
+ },
42
+
43
+ componentDidMount: function () {
44
+ this._isMounted = true;
45
+ },
46
+
47
+ componentWillUnmount: function () {
48
+ this._isMounted = false;
49
+ },
50
+
51
+ // getInitialState isn't called if the react component is re-rendered
52
+ // in-place on the dom, in which case this is called instead, so we
53
+ // need to update the state here.
54
+ // (This component is currently re-rendered by the "Add image" button.)
55
+ UNSAFE_componentWillReceiveProps: function (nextProps) {
56
+ this.setState(
57
+ _(nextProps).pick(
58
+ "apiOptions",
59
+ "imageUploader",
60
+ "developerMode",
61
+ "problemNum",
62
+ "previewDevice",
63
+ "frameSource",
64
+ ),
65
+ );
66
+ },
67
+
68
+ getSaveWarnings: function () {
69
+ // eslint-disable-next-line react/no-string-refs
70
+ return this.refs.editor.getSaveWarnings();
71
+ },
72
+
73
+ serialize: function () {
74
+ // eslint-disable-next-line react/no-string-refs
75
+ return this.refs.editor.serialize();
76
+ },
77
+
78
+ handleChange: function (newState, cb) {
79
+ if (this._isMounted) {
80
+ this.setState(newState, cb);
81
+ }
82
+ },
83
+
84
+ scorePreview: function () {
85
+ // eslint-disable-next-line react/no-string-refs
86
+ return this.refs.editor.scorePreview();
87
+ },
88
+ });
89
+
90
+ export default StatefulEditorPage;