@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.
- package/CHANGELOG.md +27 -0
- package/dist/es/index.js +49 -47
- package/dist/es/index.js.map +1 -1
- package/dist/index.js +49 -41
- package/dist/index.js.map +1 -1
- package/dist/tex-error-view.d.ts +19 -0
- package/dist/widgets/categorizer-editor.d.ts +0 -1
- package/dist/widgets/example-graphie-widget.d.ts +0 -1
- package/dist/widgets/graded-group-editor.d.ts +0 -1
- package/dist/widgets/graded-group-set-editor.d.ts +0 -1
- package/dist/widgets/group-editor.d.ts +0 -1
- package/dist/widgets/interactive-graph-editor.d.ts +0 -92
- package/dist/widgets/radio/editor.d.ts +0 -1
- package/dist/widgets/sequence-editor.d.ts +0 -1
- package/dist/widgets/unit-editor.d.ts +1 -1
- package/package.json +7 -5
- package/src/__stories__/tex-error-view.stories.tsx +26 -0
- package/src/components/blur-input.tsx +1 -0
- package/src/components/sortable.tsx +1 -0
- package/src/components/widget-editor.tsx +1 -0
- package/src/diffs/__stories__/structured-item-diff.stories.tsx +0 -1
- package/src/editor.tsx +3 -2
- package/src/tex-error-view.tsx +90 -0
- package/src/widgets/__tests__/expression-editor.test.tsx +56 -0
- package/src/widgets/expression-editor.tsx +7 -7
- package/tsconfig-build.tsbuildinfo +1 -1
- package/dist/katex-error-view.d.ts +0 -16
- package/src/katex-error-view.tsx +0 -86
|
@@ -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<"
|
|
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.
|
|
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.
|
|
26
|
-
"@khanacademy/kmath": "^0.1.
|
|
27
|
-
"@khanacademy/perseus": "^
|
|
28
|
-
"@khanacademy/perseus-core": "1.
|
|
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
|
+
};
|
|
@@ -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
|
|
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
|
-
<
|
|
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
|
+
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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,
|