@khanacademy/perseus-editor 2.10.0 → 2.12.0

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.
@@ -23,7 +23,6 @@ declare class RadioEditor extends React.Component<any> {
23
23
  customKeypad: PropTypes.Requireable<boolean>;
24
24
  nativeKeypadProxy: PropTypes.Requireable<(...args: any[]) => any>;
25
25
  isMobile: PropTypes.Requireable<boolean>;
26
- useV2Keypad: PropTypes.Requireable<boolean>;
27
26
  setDrawingAreaAvailable: PropTypes.Requireable<(...args: any[]) => any>;
28
27
  useDraftEditor: PropTypes.Requireable<boolean>;
29
28
  hintProgressColor: PropTypes.Requireable<string>;
@@ -33,7 +33,6 @@ declare class SequenceEditor extends React.Component<SequenceEditorProps> {
33
33
  customKeypad: PropTypes.Requireable<boolean>;
34
34
  nativeKeypadProxy: PropTypes.Requireable<(...args: any[]) => any>;
35
35
  isMobile: PropTypes.Requireable<boolean>;
36
- useV2Keypad: PropTypes.Requireable<boolean>;
37
36
  setDrawingAreaAvailable: PropTypes.Requireable<(...args: any[]) => any>;
38
37
  useDraftEditor: PropTypes.Requireable<boolean>;
39
38
  hintProgressColor: PropTypes.Requireable<string>;
@@ -11,7 +11,7 @@ declare class UnitInputEditor extends React.Component<UnitInputEditorProps> {
11
11
  static propTypes: {
12
12
  value: PropTypes.Requireable<string>;
13
13
  acceptingUnits: PropTypes.Requireable<(string | null | undefined)[]>;
14
- accepting: PropTypes.Requireable<"some" | "all">;
14
+ accepting: PropTypes.Requireable<"all" | "some">;
15
15
  sigfigs: PropTypes.Requireable<number>;
16
16
  onChange: PropTypes.Validator<(...args: any[]) => any>;
17
17
  };
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "Perseus editors",
4
4
  "author": "Khan Academy",
5
5
  "license": "MIT",
6
- "version": "2.10.0",
6
+ "version": "2.12.0",
7
7
  "publishConfig": {
8
8
  "access": "public"
9
9
  },
@@ -22,16 +22,17 @@
22
22
  "test": "bash -c 'yarn --silent --cwd \"../..\" test ${@:0} $($([[ ${@: -1} = -* ]] || [[ ${@: -1} = bash ]]) && echo $PWD)'"
23
23
  },
24
24
  "dependencies": {
25
- "@khanacademy/kas": "^0.3.5",
26
- "@khanacademy/kmath": "^0.1.6",
27
- "@khanacademy/perseus": "^13.2.0",
28
- "@khanacademy/perseus-core": "1.3.0"
25
+ "@khanacademy/kas": "^0.3.6",
26
+ "@khanacademy/kmath": "^0.1.7",
27
+ "@khanacademy/perseus": "^15.0.0",
28
+ "@khanacademy/perseus-core": "1.4.0"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@khanacademy/wonder-blocks-button": "^6.2.2",
32
32
  "@khanacademy/wonder-blocks-clickable": "^4.0.11",
33
33
  "@khanacademy/wonder-blocks-color": "^3.0.0",
34
34
  "@khanacademy/wonder-blocks-core": "^6.3.0",
35
+ "@khanacademy/wonder-blocks-icon": "2.2.1",
35
36
  "@khanacademy/wonder-blocks-i18n": "^2.0.2",
36
37
  "@khanacademy/wonder-blocks-spacing": "^4.0.1",
37
38
  "@khanacademy/wonder-blocks-typography": "^2.1.9",
@@ -53,6 +54,7 @@
53
54
  "@khanacademy/wonder-blocks-clickable": "^4.0.11",
54
55
  "@khanacademy/wonder-blocks-color": "^3.0.0",
55
56
  "@khanacademy/wonder-blocks-core": "^6.3.0",
57
+ "@khanacademy/wonder-blocks-icon": "2.2.1",
56
58
  "@khanacademy/wonder-blocks-i18n": "^2.0.2",
57
59
  "@khanacademy/wonder-blocks-spacing": "^4.0.1",
58
60
  "@khanacademy/wonder-blocks-typography": "^2.1.9",
@@ -0,0 +1,26 @@
1
+ import TexErrorView from "../tex-error-view";
2
+
3
+ import type {Meta, StoryObj} from "@storybook/react";
4
+
5
+ const meta: Meta<typeof TexErrorView> = {
6
+ component: TexErrorView,
7
+ title: "Perseus/Editor/TexErrorView",
8
+ };
9
+
10
+ export default meta;
11
+ type Story = StoryObj<typeof TexErrorView>;
12
+
13
+ export const Primary: Story = {
14
+ args: {
15
+ errorList: [
16
+ {
17
+ math: "\\x^2",
18
+ message: "TeX parse error: Undefined control sequence: \\x",
19
+ },
20
+ {
21
+ math: "\\y^2",
22
+ message: "TeX parse error: Undefined control sequence: \\y",
23
+ },
24
+ ],
25
+ },
26
+ };
@@ -32,6 +32,7 @@ class BlurInput extends React.Component<Props, State> {
32
32
  this.state = {value: this.props.value};
33
33
  }
34
34
 
35
+ // eslint-disable-next-line react/no-unsafe
35
36
  UNSAFE_componentWillReceiveProps(nextProps: Props) {
36
37
  this.setState({value: nextProps.value});
37
38
  }
@@ -30,6 +30,7 @@ const SortableArea = createReactClass({
30
30
  componentDidMount: function () {
31
31
  this._setDragEvents();
32
32
  },
33
+ // eslint-disable-next-line react/no-unsafe
33
34
  UNSAFE_componentWillReceiveProps: function (nextProps) {
34
35
  this.setState({components: nextProps.components});
35
36
  },
@@ -58,6 +58,7 @@ class WidgetEditor extends React.Component<
58
58
  };
59
59
  }
60
60
 
61
+ // eslint-disable-next-line react/no-unsafe
61
62
  UNSAFE_componentWillReceiveProps(nextProps: WidgetEditorProps) {
62
63
  // @ts-expect-error - TS2322 - Type 'ReactElement<any, any> | null' is not assignable to type 'PerseusWidget'.
63
64
  this.setState({widgetInfo: _upgradeWidgetInfo(nextProps)});
@@ -4,7 +4,6 @@ import StructuredItemDiff from "../structured-item-diff";
4
4
 
5
5
  import Wrapper from "./perseus-diff-wrapper";
6
6
 
7
- // @ts-expect-error [FEI-5003] - TS2307 - Cannot find module '../../styles/perseus-editor.less' or its corresponding type declarations.
8
7
  import("../../styles/perseus-editor.less");
9
8
 
10
9
  type StoryArgs = Record<any, any>;
package/src/editor.tsx CHANGED
@@ -24,7 +24,7 @@ import _ from "underscore";
24
24
  import DragTarget from "./components/drag-target";
25
25
  import WidgetEditor from "./components/widget-editor";
26
26
  import WidgetSelect from "./components/widget-select";
27
- import KatexErrorView from "./katex-error-view";
27
+ import TexErrorView from "./tex-error-view";
28
28
 
29
29
  import type {ChangeHandler, WidgetInfo} from "@khanacademy/perseus";
30
30
 
@@ -192,6 +192,7 @@ class Editor extends React.Component<Props, State> {
192
192
  }
193
193
 
194
194
  // TODO(arun): This is a deprecated method, use the appropriate replacement
195
+ // eslint-disable-next-line react/no-unsafe
195
196
  UNSAFE_componentWillReceiveProps(nextProps: Props) {
196
197
  if (this.props.content !== nextProps.content) {
197
198
  this.setState({textAreaValue: nextProps.content});
@@ -1100,7 +1101,7 @@ class Editor extends React.Component<Props, State> {
1100
1101
  >
1101
1102
  {textareaWrapper}
1102
1103
  {katexErrorList.length > 0 && (
1103
- <KatexErrorView errorList={katexErrorList} />
1104
+ <TexErrorView errorList={katexErrorList} />
1104
1105
  )}
1105
1106
  {this.props.warnNoPrompt && noPrompt && (
1106
1107
  <div style={warningStyle}>
@@ -0,0 +1,90 @@
1
+ import {View} from "@khanacademy/wonder-blocks-core";
2
+ import {PhosphorIcon} from "@khanacademy/wonder-blocks-icon";
3
+ import caretRight from "@phosphor-icons/core/bold/caret-right-bold.svg";
4
+ import {StyleSheet} from "aphrodite";
5
+ import * as React from "react";
6
+
7
+ type Props = {
8
+ errorList: ReadonlyArray<{math: string; message: string}>;
9
+ };
10
+
11
+ type State = {
12
+ showErrors: boolean;
13
+ };
14
+
15
+ /**
16
+ * Displays a collapsable list of TeX rendering errors.
17
+ */
18
+ class TexErrorView extends React.Component<Props, State> {
19
+ state: State = {
20
+ showErrors: false,
21
+ };
22
+
23
+ handleToggleTexErrors: (arg1: React.MouseEvent) => void = (e) => {
24
+ this.setState({showErrors: !this.state.showErrors});
25
+ };
26
+
27
+ render(): React.ReactNode {
28
+ const {errorList} = this.props;
29
+ const {showErrors} = this.state;
30
+
31
+ const iconStyle = showErrors
32
+ ? styles.iconExpanded
33
+ : styles.iconCollapsed;
34
+
35
+ return (
36
+ <View style={styles.errorContainer}>
37
+ <View style={styles.title} onClick={this.handleToggleTexErrors}>
38
+ <PhosphorIcon icon={caretRight} style={iconStyle} />
39
+ &nbsp; TeX Errors ({errorList.length})
40
+ </View>
41
+ {showErrors && (
42
+ <View style={styles.errorExplanation}>
43
+ If your math doesn't display correctly, these errors
44
+ might help you troubleshoot. Message #content-kitchen
45
+ for help.
46
+ </View>
47
+ )}
48
+ {showErrors &&
49
+ errorList.map((e, index) => (
50
+ <View style={styles.error} key={index}>
51
+ <View style={{color: "red"}}>{e.math}</View>
52
+ <View>{e.message}</View>
53
+ </View>
54
+ ))}
55
+ </View>
56
+ );
57
+ }
58
+ }
59
+
60
+ const styles = StyleSheet.create({
61
+ title: {
62
+ backgroundColor: "#eee",
63
+ fontSize: "1.25em",
64
+ padding: "4px 10px",
65
+ cursor: "pointer",
66
+ display: "flex",
67
+ flexDirection: "row",
68
+ alignItems: "center",
69
+ },
70
+ errorContainer: {
71
+ border: "1px solid #ddd",
72
+ borderTop: "none",
73
+ },
74
+ errorExplanation: {
75
+ padding: "4px 10px",
76
+ backgroundColor: "pink",
77
+ },
78
+ error: {
79
+ padding: "4px 10px",
80
+ },
81
+ iconCollapsed: {
82
+ transition: ".15s",
83
+ },
84
+ iconExpanded: {
85
+ transform: "rotate(90deg)",
86
+ transition: ".15s",
87
+ },
88
+ });
89
+
90
+ export default TexErrorView;
@@ -0,0 +1,56 @@
1
+ import {Dependencies} from "@khanacademy/perseus";
2
+ import {render, screen} from "@testing-library/react";
3
+ import * as React from "react";
4
+
5
+ import "@testing-library/jest-dom";
6
+
7
+ import {testDependencies} from "../../../../../testing/test-dependencies";
8
+ import ExpressionEditor from "../expression-editor";
9
+
10
+ import type {PropsFor} from "@khanacademy/wonder-blocks-core";
11
+
12
+ describe("expression-editor", () => {
13
+ beforeEach(() => {
14
+ jest.spyOn(Dependencies, "getDependencies").mockReturnValue(
15
+ testDependencies,
16
+ );
17
+ });
18
+
19
+ it("should render", async () => {
20
+ render(<ExpressionEditor onChange={() => undefined} />);
21
+
22
+ expect(await screen.findByText(/Add new answer/)).toBeInTheDocument();
23
+ });
24
+
25
+ it("should render answerForms missing a key", async () => {
26
+ const answerForms: PropsFor<typeof ExpressionEditor>["answerForms"] = [
27
+ {
28
+ value: "x\\cdot3=y",
29
+ form: true,
30
+ simplify: true,
31
+ considered: "correct",
32
+ },
33
+ {
34
+ value: "x^2=y",
35
+ form: true,
36
+ simplify: true,
37
+ considered: "wrong",
38
+ },
39
+ {
40
+ value: "x=y\\cdotπ",
41
+ form: true,
42
+ simplify: true,
43
+ considered: "wrong",
44
+ },
45
+ ];
46
+
47
+ render(
48
+ <ExpressionEditor
49
+ onChange={() => undefined}
50
+ answerForms={answerForms}
51
+ />,
52
+ );
53
+
54
+ expect(await screen.findByText(/π/)).toBeInTheDocument();
55
+ });
56
+ });
@@ -18,7 +18,6 @@ import SortableArea from "../components/sortable";
18
18
  import type {PerseusExpressionWidgetOptions} from "@khanacademy/perseus";
19
19
 
20
20
  const {InfoTip, PropCheckBox, TexButtons} = components;
21
- const {getDependencies} = Dependencies;
22
21
 
23
22
  type Props = {
24
23
  widgetId?: any;
@@ -38,11 +37,12 @@ type DefaultProps = {
38
37
  };
39
38
 
40
39
  const parseAnswerKey = ({key}: AnswerForm): number => {
41
- const parsedKey = Number.parseInt(key ?? "");
42
- if (Number.isNaN(parsedKey)) {
43
- throw new Error(`Invalid answer key: ${key}`);
44
- }
45
- return parsedKey;
40
+ // We don't throw here because there is data stored in some
41
+ // exercises/articles where the answer forms don't have a key. If we throw,
42
+ // it blocks content editors from loading the page at all.
43
+ // TODO(Jeremy): find a way to handle these answer forms that are missing
44
+ // keys more gracefully.
45
+ return Number.parseInt(key ?? "");
46
46
  };
47
47
 
48
48
  // Pick a key that isn't currently used by an answer in answerForms
@@ -182,7 +182,7 @@ class ExpressionEditor extends React.Component<Props, State> {
182
182
  },
183
183
  );
184
184
 
185
- const {TeX} = getDependencies(); // OldExpression only
185
+ const {TeX} = Dependencies.getDependencies(); // OldExpression only
186
186
 
187
187
  buttonSetChoices.splice(
188
188
  1,