@khanacademy/perseus 17.8.0 → 18.0.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.
Files changed (56) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/dist/es/index.css +0 -4
  3. package/dist/es/index.js +1422 -1021
  4. package/dist/es/index.js.map +1 -1
  5. package/dist/index.css +0 -4
  6. package/dist/index.d.ts +4 -2
  7. package/dist/index.js +1321 -954
  8. package/dist/index.js.map +1 -1
  9. package/dist/perseus-types.d.ts +4 -3
  10. package/dist/renderer.d.ts +6 -6
  11. package/dist/server-item-renderer.d.ts +2 -4
  12. package/dist/strings.js +1 -1
  13. package/dist/types.d.ts +19 -2
  14. package/dist/util/drawing-transform.d.ts +21 -0
  15. package/dist/util/extract-perseus-data.d.ts +26 -0
  16. package/dist/util/graph-bounds.d.ts +9 -0
  17. package/dist/util/graphie.d.ts +2 -0
  18. package/dist/util/interval.d.ts +1 -0
  19. package/dist/util/parse-perseus-json.d.ts +10 -0
  20. package/dist/util/tex.d.ts +1 -3
  21. package/dist/widgets/deprecated-standin/index.d.ts +4 -4
  22. package/dist/widgets/passage.d.ts +3 -3
  23. package/dist/widgets.d.ts +16 -6
  24. package/package.json +9 -9
  25. package/src/__tests__/extract-perseus-data.test.ts +1027 -0
  26. package/src/index.ts +6 -1
  27. package/src/init.ts +1 -1
  28. package/src/perseus-types.ts +4 -3
  29. package/src/renderer.tsx +12 -9
  30. package/src/server-item-renderer.tsx +2 -4
  31. package/src/styles/widgets/image.less +0 -3
  32. package/src/types.ts +19 -16
  33. package/src/util/drawing-transform.test.ts +67 -0
  34. package/src/util/drawing-transform.ts +102 -0
  35. package/src/util/extract-perseus-data.ts +539 -0
  36. package/src/util/graph-bounds.ts +23 -0
  37. package/src/util/graphie.ts +1167 -1258
  38. package/src/util/interactive.ts +2 -2
  39. package/src/util/interval.ts +4 -0
  40. package/src/util/parse-perseus-json.test.ts +10 -0
  41. package/src/util/parse-perseus-json.ts +13 -0
  42. package/src/util/tex.ts +11 -84
  43. package/src/widgets/__stories__/image.stories.tsx +12 -26
  44. package/src/widgets/__tests__/__snapshots__/grapher.test.ts.snap +72 -288
  45. package/src/widgets/__tests__/__snapshots__/interaction.test.ts.snap +5 -20
  46. package/src/widgets/__tests__/__snapshots__/interactive-graph.test.ts.snap +32 -128
  47. package/src/widgets/__tests__/__snapshots__/number-line.test.ts.snap +22 -88
  48. package/src/widgets/__tests__/__snapshots__/plotter.test.tsx.snap +12 -48
  49. package/src/widgets/deprecated-standin/__stories__/deprecated-standin.stories.tsx +8 -12
  50. package/src/widgets/deprecated-standin/__tests__/__snapshots__/deprecated-standin.test.ts.snap +6 -7
  51. package/src/widgets/deprecated-standin/index.tsx +8 -7
  52. package/src/widgets/interactive-graph.tsx +3 -2
  53. package/src/widgets/label-image/answer-pill.tsx +6 -1
  54. package/src/widgets/passage.tsx +5 -7
  55. package/src/widgets.ts +34 -10
  56. package/tsconfig-build.tsbuildinfo +1 -1
@@ -31,7 +31,7 @@ import {Errors} from "../logging/log";
31
31
  import {PerseusError} from "../perseus-error";
32
32
 
33
33
  import KhanColors from "./colors";
34
- import GraphUtils from "./graphie";
34
+ import GraphUtils, {polar} from "./graphie";
35
35
  import KhanMath from "./math";
36
36
 
37
37
  import type {Coord} from "../interactive2/types";
@@ -4024,7 +4024,7 @@ _.extend(MovableAngle.prototype, {
4024
4024
  Math.round((angle - snapOffset) / snap) * snap +
4025
4025
  snapOffset;
4026
4026
  const distance = GraphUtils.getDistance(newPoint, vertex);
4027
- return addPoints(vertex, graphie.polar(distance, angle));
4027
+ return addPoints(vertex, polar(distance, angle));
4028
4028
  }
4029
4029
  return true;
4030
4030
  };
@@ -0,0 +1,4 @@
1
+ // type Interval represents a closed interval.
2
+ // (see: https://en.wikipedia.org/wiki/Interval_(mathematics))
3
+
4
+ export type Interval = [number, number];
@@ -0,0 +1,10 @@
1
+ import parsePerseusJSON from "./parse-perseus-json";
2
+
3
+ describe("parsePerseusJSON", () => {
4
+ it("should parse JSON", () => {
5
+ const result = parsePerseusJSON(
6
+ `{ "question": { "content": "idk why I'm testing this" }}`,
7
+ );
8
+ expect(result.question.content).toBe("idk why I'm testing this");
9
+ });
10
+ });
@@ -0,0 +1,13 @@
1
+ import type {PerseusItem} from "../perseus-types";
2
+
3
+ /**
4
+ * Helper to parse Perseus JSON
5
+ * Why not just use JSON.parse? We want:
6
+ * - To make sure types are correct
7
+ * - To give us a central place to validate/transform output if needed
8
+ * @param {string} input - the stringified Perseus JSON
9
+ * @returns {PerseusItem} the parsed PerseusItem object
10
+ */
11
+ export default function parsePerseusJSON(input: string): PerseusItem {
12
+ return JSON.parse(input);
13
+ }
package/src/util/tex.ts CHANGED
@@ -6,8 +6,6 @@ import {getDependencies} from "../dependencies";
6
6
  import KhanMath from "./math";
7
7
  import reactRender from "./react-render";
8
8
 
9
- declare const MathJax: any;
10
-
11
9
  function findChildOrAdd(elem: any, className: string) {
12
10
  const $child = $(elem).find("." + className);
13
11
  if ($child.length === 0) {
@@ -16,27 +14,6 @@ function findChildOrAdd(elem: any, className: string) {
16
14
  return $child;
17
15
  }
18
16
 
19
- function doCallback(
20
- elem: HTMLElement,
21
- callback: (() => unknown) | (() => void),
22
- ) {
23
- let tries = 0;
24
- (function check() {
25
- const height = elem.scrollHeight;
26
- // Heuristic to guess if the font has kicked in
27
- // so we have box metrics (magic number ick,
28
- // but this seems to work mostly-consistently)
29
- if (height > 18 || tries >= 10) {
30
- callback();
31
- } else {
32
- tries++;
33
- // TODO(jeff, CP-3128): Use Wonder Blocks Timing API
34
- // eslint-disable-next-line no-restricted-syntax
35
- setTimeout(check, 100);
36
- }
37
- })();
38
- }
39
-
40
17
  export default {
41
18
  // Process a node and add math inside of it. This attempts to use KaTeX to
42
19
  // format the math, and if that fails it falls back to MathJax.
@@ -55,7 +32,7 @@ export default {
55
32
  // processed
56
33
  processMath: async function (
57
34
  elem: HTMLElement,
58
- text: string,
35
+ text: string | number,
59
36
  force?: boolean,
60
37
  callback?: () => unknown,
61
38
  ) {
@@ -63,29 +40,14 @@ export default {
63
40
 
64
41
  // Only process if it hasn't been done before, or it is forced
65
42
  if ($elem.attr("data-math-formula") == null || force) {
66
- const $katexHolder = findChildOrAdd($elem, "katex-holder");
67
- const $mathjaxHolder = findChildOrAdd($elem, "mathjax-holder");
68
-
69
- // Search for MathJax-y script tags inside of the node. These are
70
- // used by MathJax to denote the formula to be typeset. Before, we
71
- // would update the formula by updating the contents of the script
72
- // tag, which shouldn't happen any more, but we manage them just in
73
- // case.
74
- const script: HTMLElement | undefined = $mathjaxHolder.find(
75
- "script[type='math/tex']",
76
- )[0];
77
-
78
- // If text wasn't provided, we look in two places
79
- if (text == null) {
80
- if ($elem.attr("data-math-formula")) {
81
- // The old typeset formula
82
- // @ts-expect-error - TS2322 - Type 'string | undefined' is not assignable to type 'string'.
83
- text = $elem.attr("data-math-formula");
84
- } else if (script) {
85
- // The contents of the <script> tag
86
- // @ts-expect-error - TS2339 - Property 'text' does not exist on type 'HTMLElement'.
87
- text = script.text || script.textContent;
88
- }
43
+ const $texHolder = findChildOrAdd($elem, "tex-holder");
44
+
45
+ // If text wasn't provided, we use the cached text.
46
+ // TODO(benchristel): I'm not sure if text can ever be null. It's
47
+ // possible we don't need this check.
48
+ if (text == null && $elem.attr("data-math-formula")) {
49
+ // @ts-expect-error - TS2322 - Type 'string | undefined' is not assignable to type 'string'.
50
+ text = $elem.attr("data-math-formula");
89
51
  }
90
52
 
91
53
  text = text != null ? text + "" : "";
@@ -102,45 +64,10 @@ export default {
102
64
  reactRender(
103
65
  React.createElement(TeX, {
104
66
  children: text,
105
- onRender: () => {
106
- if (callback) {
107
- doCallback(elem, callback);
108
- }
109
- },
67
+ onRender: callback,
110
68
  }),
111
- $katexHolder[0],
69
+ $texHolder[0],
112
70
  );
113
71
  }
114
72
  },
115
-
116
- // Function to restore a node to a non-math-processed state
117
- cleanupMath: function (elem: HTMLElement): HTMLElement {
118
- const $elem = $(elem);
119
-
120
- // Only mess with it if it's been processed before
121
- if ($elem.attr("data-math-formula")) {
122
- // Remove MathJax remnants
123
- if (typeof MathJax !== "undefined") {
124
- const jax = MathJax.Hub.getJaxFor($elem.find("script")[0]);
125
- if (jax) {
126
- const e = jax.SourceElement();
127
- if (e.previousSibling && e.previousSibling.className) {
128
- jax.Remove();
129
- }
130
- }
131
- }
132
-
133
- // @ts-expect-error - TS2769 - No overload matches this call.
134
- $elem.text($elem.attr("data-math-formula"));
135
- $elem.attr("data-math-formula", null);
136
- $elem.attr("data-math-type", null);
137
- }
138
-
139
- return elem;
140
- },
141
-
142
- // Function to retrieve the formula of a typeset math node
143
- retrieveMathFormula: function (elem: HTMLElement): any {
144
- return $(elem).attr("data-math-formula");
145
- },
146
73
  };
@@ -28,18 +28,11 @@ export const Question1 = (args: StoryArgs): React.ReactElement => {
28
28
  ...question.widgets,
29
29
  "image 1": {
30
30
  ...question.widgets["image 1"],
31
- options:
32
- // `options` is nullable so we have handle it explicitly
33
- // to avoid spreading `null` and generating
34
- // hard-to-understand type errors about missing keys
35
- // in object literal.
36
- imageOptions != null
37
- ? {
38
- ...imageOptions,
39
- title: args.title,
40
- caption: args.caption,
41
- }
42
- : null,
31
+ options: {
32
+ ...imageOptions,
33
+ title: args.title,
34
+ caption: args.caption,
35
+ },
43
36
  },
44
37
  },
45
38
  } as const;
@@ -64,20 +57,13 @@ export const Question2 = (args: StoryArgs): React.ReactElement => {
64
57
  ...question.widgets,
65
58
  "image 1": {
66
59
  ...question.widgets["image 1"],
67
- options:
68
- // `options` is nullable so we have handle it explicitly
69
- // to avoid spreading `null` and generating
70
- // hard-to-understand type errors about missing keys
71
- // in object literal.
72
- imageOptions != null
73
- ? {
74
- ...imageOptions,
75
- alignment: "full-width",
76
- title: args.title,
77
- caption:
78
- "There is neither happiness nor unhappiness in this world; there is only the comparison of one state with another. Only a man who has felt ultimate despair is capable of feeling ultimate bliss. It is necessary to have wished for death in order to know how good it is to live.....the sum of all human wisdom will be contained in these two words: Wait and Hope",
79
- }
80
- : null,
60
+ options: {
61
+ ...imageOptions,
62
+ alignment: "full-width",
63
+ title: args.title,
64
+ caption:
65
+ "There is neither happiness nor unhappiness in this world; there is only the comparison of one state with another. Only a man who has felt ultimate despair is capable of feeling ultimate bliss. It is necessary to have wished for death in order to know how good it is to live.....the sum of all human wisdom will be contained in these two words: Wait and Hope",
66
+ },
81
67
  },
82
68
  },
83
69
  } as const;