@khanacademy/perseus-editor 2.6.7 → 2.7.1

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.
@@ -1,22 +1,24 @@
1
- import PropTypes from "prop-types";
1
+ import { Changeable } from "@khanacademy/perseus";
2
2
  import * as React from "react";
3
- declare class ExpressionEditor extends React.Component<any, any> {
4
- static propTypes: {
5
- answerForms: PropTypes.Requireable<(PropTypes.InferProps<{
6
- considered: PropTypes.Validator<NonNullable<"correct" | "wrong" | "ungraded">>;
7
- value: PropTypes.Validator<string>;
8
- form: PropTypes.Validator<boolean>;
9
- simplify: PropTypes.Validator<boolean>;
10
- }> | null | undefined)[]>;
11
- times: PropTypes.Requireable<boolean>;
12
- buttonSets: any;
13
- functions: PropTypes.Requireable<(string | null | undefined)[]>;
14
- onChange: PropTypes.Validator<(...args: any[]) => any>;
15
- };
3
+ import type { PerseusExpressionWidgetOptions } from "@khanacademy/perseus";
4
+ type Props = {
5
+ widgetId?: any;
6
+ value?: string;
7
+ } & Omit<PerseusExpressionWidgetOptions, "buttonsVisible"> & Changeable.ChangeableProps;
8
+ type DefaultProps = {
9
+ answerForms: Props["answerForms"];
10
+ times: Props["times"];
11
+ buttonSets: Props["buttonSets"];
12
+ functions: Props["functions"];
13
+ };
14
+ type State = {
15
+ isTex: boolean;
16
+ };
17
+ declare class ExpressionEditor extends React.Component<Props, State> {
16
18
  static widgetName: "expression";
17
- static defaultProps: any;
18
- constructor(props: any);
19
- change: (arg1: any, arg2: any, arg3: any) => any;
19
+ static defaultProps: DefaultProps;
20
+ constructor(props: Props);
21
+ change(...args: any[]): any;
20
22
  render(): React.ReactNode;
21
23
  serialize: () => any;
22
24
  getSaveWarnings: () => any;
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.7",
6
+ "version": "2.7.1",
7
7
  "publishConfig": {
8
8
  "access": "public"
9
9
  },
@@ -24,7 +24,7 @@
24
24
  "dependencies": {
25
25
  "@khanacademy/kas": "^0.3.1",
26
26
  "@khanacademy/kmath": "^0.1.2",
27
- "@khanacademy/perseus": "^11.0.0"
27
+ "@khanacademy/perseus": "^11.2.1"
28
28
  },
29
29
  "devDependencies": {
30
30
  "@khanacademy/wonder-blocks-button": "^4.1.3",
@@ -34,6 +34,7 @@
34
34
  "@khanacademy/wonder-blocks-i18n": "^2.0.2",
35
35
  "@khanacademy/wonder-blocks-spacing": "^4.0.1",
36
36
  "@khanacademy/wonder-blocks-typography": "^2.1.3",
37
+ "@khanacademy/wonder-stuff-core": "^1.5.1",
37
38
  "aphrodite": "^1.2.5",
38
39
  "classnames": "^1.1.4",
39
40
  "create-react-class": "^15.6.3",
@@ -53,6 +54,7 @@
53
54
  "@khanacademy/wonder-blocks-i18n": "^2.0.2",
54
55
  "@khanacademy/wonder-blocks-spacing": "^4.0.1",
55
56
  "@khanacademy/wonder-blocks-typography": "^2.1.3",
57
+ "@khanacademy/wonder-stuff-core": "^1.5.1",
56
58
  "aphrodite": "^1.2.5",
57
59
  "classnames": "^1.1.4",
58
60
  "create-react-class": "^15.6.3",
@@ -290,6 +290,11 @@
290
290
 
291
291
  #workarea {
292
292
  margin: 0; // override ke
293
+
294
+ .perseus-image-caption {
295
+ // Override caption spacing just for content editor
296
+ top: 0px !important;
297
+ }
293
298
  }
294
299
  }
295
300
 
@@ -62,8 +62,7 @@ class WithDebug extends React.Component<Empty, State> {
62
62
  <div className={css(styles.editorWrapper)}>
63
63
  <ExpressionEditor
64
64
  {...this.state}
65
- apiOptions={apiOptions}
66
- onChange={(props) => {
65
+ onChange={(props: PerseusExpressionWidgetOptions) => {
67
66
  this.setState({
68
67
  ...props,
69
68
  });
@@ -1,4 +1,4 @@
1
- /* eslint-disable one-var, react/forbid-prop-types, react/sort-comp */
1
+ /* eslint-disable react/sort-comp */
2
2
  import * as KAS from "@khanacademy/kas";
3
3
  import {
4
4
  components,
@@ -7,24 +7,31 @@ import {
7
7
  Expression,
8
8
  PerseusExpressionAnswerFormConsidered,
9
9
  } from "@khanacademy/perseus";
10
+ import {isTruthy} from "@khanacademy/wonder-stuff-core";
10
11
  // eslint-disable-next-line import/no-extraneous-dependencies
11
12
  import lens from "hubble";
12
- import PropTypes from "prop-types";
13
13
  import * as React from "react";
14
14
  import _ from "underscore";
15
15
 
16
16
  import SortableArea from "../components/sortable";
17
17
 
18
+ import type {PerseusExpressionWidgetOptions} from "@khanacademy/perseus";
19
+
18
20
  const {InfoTip, PropCheckBox, TexButtons} = components;
19
21
  const {getDependencies} = Dependencies;
20
22
 
21
- const answerFormType = PropTypes.shape({
22
- considered: PropTypes.oneOf(PerseusExpressionAnswerFormConsidered)
23
- .isRequired,
24
- value: PropTypes.string.isRequired,
25
- form: PropTypes.bool.isRequired,
26
- simplify: PropTypes.bool.isRequired,
27
- });
23
+ type Props = {
24
+ widgetId?: any;
25
+ value?: string;
26
+ } & Omit<PerseusExpressionWidgetOptions, "buttonsVisible"> &
27
+ Changeable.ChangeableProps;
28
+
29
+ type DefaultProps = {
30
+ answerForms: Props["answerForms"];
31
+ times: Props["times"];
32
+ buttonSets: Props["buttonSets"];
33
+ functions: Props["functions"];
34
+ };
28
35
 
29
36
  // Pick a key that isn't currently used by an answer in answerForms
30
37
  const _makeNewKey = (answerForms: any) => {
@@ -48,25 +55,21 @@ const _makeNewKey = (answerForms: any) => {
48
55
  return usedKeys.length;
49
56
  };
50
57
 
51
- class ExpressionEditor extends React.Component<any, any> {
52
- static propTypes = {
53
- ...Changeable.propTypes,
54
- answerForms: PropTypes.arrayOf(answerFormType),
55
- times: PropTypes.bool,
56
- buttonSets: TexButtons.buttonSetsType,
57
- functions: PropTypes.arrayOf(PropTypes.string),
58
- };
58
+ type State = {
59
+ isTex: boolean;
60
+ };
59
61
 
62
+ class ExpressionEditor extends React.Component<Props, State> {
60
63
  static widgetName = "expression" as const;
61
64
 
62
- static defaultProps: any = {
65
+ static defaultProps: DefaultProps = {
63
66
  answerForms: [],
64
67
  times: false,
65
68
  buttonSets: ["basic"],
66
69
  functions: ["f", "g", "h"],
67
70
  };
68
71
 
69
- constructor(props: any) {
72
+ constructor(props: Props) {
70
73
  super(props);
71
74
  // Is the format of `value` TeX or plain text?
72
75
  // TODO(alex): Remove after backfilling everything to TeX
@@ -79,22 +82,19 @@ class ExpressionEditor extends React.Component<any, any> {
79
82
  if (props.answerForms.length === 0) {
80
83
  isTex = true;
81
84
  } else {
82
- isTex = _(props.answerForms).any((form) => {
85
+ isTex = props.answerForms.some((form) => {
83
86
  const {value} = form;
84
87
  // only TeX has backslashes and curly braces
85
- return (
86
- _.indexOf(value, "\\") !== -1 ||
87
- _.indexOf(value, "{") !== -1
88
- );
88
+ return value.indexOf("\\") !== -1 || value.indexOf("{") !== -1;
89
89
  });
90
90
  }
91
91
 
92
92
  this.state = {isTex};
93
93
  }
94
94
 
95
- change: (arg1: any, arg2: any, arg3: any) => any = (...args) => {
95
+ change(...args) {
96
96
  return Changeable.change.apply(this, args);
97
- };
97
+ }
98
98
 
99
99
  render(): React.ReactNode {
100
100
  const answerOptions = this.props.answerForms
@@ -138,26 +138,28 @@ class ExpressionEditor extends React.Component<any, any> {
138
138
  );
139
139
 
140
140
  // checkboxes to choose which sets of input buttons are shown
141
- const buttonSetChoices = _(TexButtons.buttonSets).map((set, name) => {
142
- // The first one gets special cased to always be checked, disabled,
143
- // and float left.
144
- const isFirst = name === "basic";
145
- const checked = _.contains(this.props.buttonSets, name) || isFirst;
146
- const className = isFirst
147
- ? "button-set-label-float"
148
- : "button-set-label";
149
- return (
150
- <label className={className} key={name}>
151
- <input
152
- type="checkbox"
153
- checked={checked}
154
- disabled={isFirst}
155
- onChange={() => this.handleButtonSet(name)}
156
- />
157
- {name}
158
- </label>
159
- );
160
- });
141
+ const buttonSetChoices = Object.keys(TexButtons.buttonSets).map(
142
+ (name) => {
143
+ // The first one gets special cased to always be checked, disabled,
144
+ // and float left.
145
+ const isFirst = name === "basic";
146
+ const checked = this.props.buttonSets.includes(name) || isFirst;
147
+ const className = isFirst
148
+ ? "button-set-label-float"
149
+ : "button-set-label";
150
+ return (
151
+ <label className={className} key={name}>
152
+ <input
153
+ type="checkbox"
154
+ checked={checked}
155
+ disabled={isFirst}
156
+ onChange={() => this.handleButtonSet(name)}
157
+ />
158
+ {name}
159
+ </label>
160
+ );
161
+ },
162
+ );
161
163
 
162
164
  const {TeX} = getDependencies(); // OldExpression only
163
165
 
@@ -282,7 +284,7 @@ class ExpressionEditor extends React.Component<any, any> {
282
284
  if (this.props.answerForms.length === 0) {
283
285
  issues.push("No answers specified");
284
286
  } else {
285
- const hasCorrect = !!_(this.props.answerForms).find((form) => {
287
+ const hasCorrect = this.props.answerForms.some((form) => {
286
288
  return form.considered === "correct";
287
289
  });
288
290
  if (!hasCorrect) {
@@ -337,14 +339,12 @@ class ExpressionEditor extends React.Component<any, any> {
337
339
  newAnswer: () => void = () => {
338
340
  const answerForms = this.props.answerForms.slice();
339
341
  answerForms.push(this._newEmptyAnswerForm());
340
- // @ts-expect-error - TS2554 - Expected 3 arguments, but got 1.
341
342
  this.change({answerForms});
342
343
  };
343
344
 
344
345
  handleRemoveForm: (arg1: number) => void = (i) => {
345
346
  const answerForms = this.props.answerForms.slice();
346
347
  answerForms.splice(i, 1);
347
- // @ts-expect-error - TS2554 - Expected 3 arguments, but got 1.
348
348
  this.change({answerForms});
349
349
  };
350
350
 
@@ -355,12 +355,11 @@ class ExpressionEditor extends React.Component<any, any> {
355
355
  .merge([i], props)
356
356
  .freeze();
357
357
 
358
- // @ts-expect-error - TS2554 - Expected 3 arguments, but got 1.
359
358
  this.change({answerForms});
360
359
  };
361
360
 
362
361
  handleReorder: (arg1: any) => void = (components) => {
363
- const answerForms = _(components).map((component) => {
362
+ const answerForms = components.map((component) => {
364
363
  const form = _(component.props).pick(
365
364
  "considered",
366
365
  "form",
@@ -372,20 +371,18 @@ class ExpressionEditor extends React.Component<any, any> {
372
371
  return form;
373
372
  });
374
373
 
375
- // @ts-expect-error - TS2554 - Expected 3 arguments, but got 1.
376
374
  this.change({answerForms});
377
375
  };
378
376
 
379
377
  // called when the selected buttonset changes
380
378
  handleButtonSet: (arg1: string) => void = (changingName) => {
381
- const buttonSetNames = _(TexButtons.buttonSets).keys();
379
+ const buttonSetNames = Object.keys(TexButtons.buttonSets);
382
380
 
383
381
  // Filter to preserve order - using .union and .difference would always
384
382
  // move the last added button set to the end.
385
- const buttonSets = _(buttonSetNames).filter((set) => {
383
+ const buttonSets = buttonSetNames.filter((set) => {
386
384
  return (
387
- _(this.props.buttonSets).contains(set) !==
388
- (set === changingName)
385
+ this.props.buttonSets.includes(set) !== (set === changingName)
389
386
  );
390
387
  });
391
388
 
@@ -397,8 +394,9 @@ class ExpressionEditor extends React.Component<any, any> {
397
394
  // "basic+div". Toggle between the two of them.
398
395
  // If someone can think of a more elegant formulation of this (there
399
396
  // must be one!) feel free to change it.
400
- let keep, remove;
401
- if (_(this.props.buttonSets).contains("basic+div")) {
397
+ let keep: string | undefined;
398
+ let remove: string | undefined;
399
+ if (this.props.buttonSets.includes("basic+div")) {
402
400
  keep = "basic";
403
401
  remove = "basic+div";
404
402
  } else {
@@ -406,11 +404,10 @@ class ExpressionEditor extends React.Component<any, any> {
406
404
  remove = "basic";
407
405
  }
408
406
 
409
- const buttonSets = _(this.props.buttonSets)
410
- .reject((set) => set === remove)
407
+ const buttonSets = this.props.buttonSets
408
+ .filter((set) => set !== remove)
411
409
  .concat(keep);
412
410
 
413
- // @ts-expect-error - TS2554 - Expected 3 arguments, but got 2.
414
411
  this.change("buttonSets", buttonSets);
415
412
  };
416
413
 
@@ -426,7 +423,7 @@ class ExpressionEditor extends React.Component<any, any> {
426
423
  e,
427
424
  ) => {
428
425
  const newProps: Record<string, any> = {};
429
- newProps.functions = _.compact(e.target.value.split(/[ ,]+/));
426
+ newProps.functions = e.target.value.split(/[ ,]+/).filter(isTruthy);
430
427
  this.props.onChange(newProps);
431
428
  };
432
429
  }
@@ -438,24 +435,28 @@ const findNextIn = function (arr: ReadonlyArray<string>, val: any) {
438
435
  return arr[ix];
439
436
  };
440
437
 
441
- class AnswerOption extends React.Component<any, any> {
442
- static propTypes = {
443
- ...Changeable.propTypes,
444
- considered: PropTypes.oneOf(PerseusExpressionAnswerFormConsidered)
445
- .isRequired,
446
- expressionProps: PropTypes.object.isRequired,
438
+ type AnswerOptionProps = {
439
+ considered: typeof PerseusExpressionAnswerFormConsidered[number];
440
+ expressionProps: any;
447
441
 
448
- // Must the answer have the same form as this answer.
449
- form: PropTypes.bool.isRequired,
442
+ // Must the answer have the same form as this answer.
443
+ form: boolean;
450
444
 
451
- // Must the answer be simplified.
452
- simplify: PropTypes.bool.isRequired,
445
+ // Must the answer be simplified.
446
+ simplify: boolean;
453
447
 
454
- onChange: PropTypes.func.isRequired,
455
- onDelete: PropTypes.func.isRequired,
456
- };
448
+ onDelete: () => void;
449
+ } & Changeable.ChangeableProps;
450
+
451
+ type AnswerOptionState = {
452
+ deleteFocused: boolean;
453
+ };
457
454
 
458
- state: any = {deleteFocused: false};
455
+ class AnswerOption extends React.Component<
456
+ AnswerOptionProps,
457
+ AnswerOptionState
458
+ > {
459
+ state = {deleteFocused: false};
459
460
 
460
461
  handleDeleteBlur = () => {
461
462
  this.setState({deleteFocused: false});
@@ -466,9 +467,8 @@ class AnswerOption extends React.Component<any, any> {
466
467
  };
467
468
 
468
469
  render(): React.ReactNode {
469
- let removeButton = null;
470
+ let removeButton: React.ReactNode | null = null;
470
471
  if (this.state.deleteFocused) {
471
- // @ts-expect-error - TS2322 - Type 'Element' is not assignable to type 'null'.
472
472
  removeButton = (
473
473
  <button
474
474
  type="button"
@@ -480,7 +480,6 @@ class AnswerOption extends React.Component<any, any> {
480
480
  </button>
481
481
  );
482
482
  } else {
483
- // @ts-expect-error - TS2322 - Type 'Element' is not assignable to type 'null'.
484
483
  removeButton = (
485
484
  <button
486
485
  type="button"