@khanacademy/perseus 11.5.1 → 11.6.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.
@@ -47,6 +47,9 @@ type SerializedState = {
47
47
  [id: string]: any;
48
48
  };
49
49
  };
50
+ /**
51
+ * @depreciated use ServerItemRenderer
52
+ */
50
53
  declare class ItemRenderer extends React.Component<Props, State> {
51
54
  questionRenderer: Renderer;
52
55
  hintsRenderer: React.ElementRef<typeof HintsRenderer> | null | undefined;
@@ -0,0 +1,8 @@
1
+ export declare const apiVersion: {
2
+ major: number;
3
+ minor: number;
4
+ };
5
+ export declare const itemDataVersion: {
6
+ major: number;
7
+ minor: number;
8
+ };
package/dist/version.d.ts CHANGED
@@ -1,11 +1 @@
1
- declare const _default: {
2
- apiVersion: {
3
- major: number;
4
- minor: number;
5
- };
6
- itemDataVersion: {
7
- major: number;
8
- minor: number;
9
- };
10
- };
11
- export default _default;
1
+ export declare const libVersion = "__lib_version__";
@@ -36,7 +36,7 @@ export declare class NumericInput extends React.Component<Props, State> {
36
36
  static defaultProps: DefaultProps;
37
37
  static getUserInputFromProps(props: Props): UserInput;
38
38
  static getOneCorrectAnswerFromRubric(rubric: Rubric): string | null | undefined;
39
- static validate(useInput: UserInput, rubric: Rubric): PerseusScore;
39
+ static validate(userInput: UserInput, rubric: Rubric): PerseusScore;
40
40
  state: State;
41
41
  examples: () => ReadonlyArray<string>;
42
42
  shouldShowExamples: () => boolean;
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "Core Perseus API (includes renderers and widgets)",
4
4
  "author": "Khan Academy",
5
5
  "license": "MIT",
6
- "version": "11.5.1",
6
+ "version": "11.6.1",
7
7
  "publishConfig": {
8
8
  "access": "public"
9
9
  },
@@ -22,13 +22,13 @@
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.1",
26
- "@khanacademy/kmath": "^0.1.2",
27
- "@khanacademy/math-input": "^13.1.0",
28
- "@khanacademy/perseus-core": "1.1.0",
29
- "@khanacademy/perseus-linter": "^0.3.4",
30
- "@khanacademy/pure-markdown": "^0.2.6",
31
- "@khanacademy/simple-markdown": "^0.10.0"
25
+ "@khanacademy/kas": "^0.3.2",
26
+ "@khanacademy/kmath": "^0.1.3",
27
+ "@khanacademy/math-input": "^14.0.1",
28
+ "@khanacademy/perseus-core": "1.1.1",
29
+ "@khanacademy/perseus-linter": "^0.3.5",
30
+ "@khanacademy/pure-markdown": "^0.2.7",
31
+ "@khanacademy/simple-markdown": "^0.10.1"
32
32
  },
33
33
  "devDependencies": {
34
34
  "@khanacademy/wonder-blocks-button": "^4.1.3",
@@ -58,7 +58,7 @@
58
58
  "katex": "^0.11.1",
59
59
  "lodash.debounce": "^4.0.8",
60
60
  "mathquill": "git+https://git@github.com/Khan/mathquill.git#32d9f351aaa68537170b3120a52e99b8def3a2c3",
61
- "perseus-build-settings": "^0.2.0",
61
+ "perseus-build-settings": "^0.2.1",
62
62
  "prop-types": "^15.6.1",
63
63
  "react": "16.14.0",
64
64
  "react-dom": "16.14.0",
@@ -189,7 +189,7 @@ exports[`server item renderer should snapshot: initial render 1`] = `
189
189
  class=""
190
190
  >
191
191
  <div
192
- class="perseus-hint-renderer"
192
+ class="hint perseus-hint-renderer"
193
193
  tabindex="-1"
194
194
  >
195
195
  <span
@@ -219,7 +219,7 @@ exports[`server item renderer should snapshot: initial render 1`] = `
219
219
  </div>
220
220
  </div>
221
221
  <div
222
- class="perseus-hint-renderer"
222
+ class="hint perseus-hint-renderer"
223
223
  tabindex="-1"
224
224
  >
225
225
  <span
@@ -249,7 +249,7 @@ exports[`server item renderer should snapshot: initial render 1`] = `
249
249
  </div>
250
250
  </div>
251
251
  <div
252
- class="perseus-hint-renderer last-hint last-rendered"
252
+ class="hint perseus-hint-renderer last-hint last-rendered"
253
253
  tabindex="-1"
254
254
  >
255
255
  <span
@@ -1,5 +1,6 @@
1
+ import {StatefulKeypadContextProvider} from "@khanacademy/math-input";
1
2
  import {RenderStateRoot} from "@khanacademy/wonder-blocks-core";
2
- import {fireEvent, render, screen} from "@testing-library/react";
3
+ import {fireEvent, render, screen, waitFor} from "@testing-library/react";
3
4
  import userEvent from "@testing-library/user-event";
4
5
  import * as React from "react";
5
6
  import "@testing-library/jest-dom"; // Imports custom matchers
@@ -71,23 +72,25 @@ export const renderQuestion = (
71
72
  let renderer: ItemRenderer | null = null;
72
73
  const {container} = render(
73
74
  <RenderStateRoot>
74
- <ItemRenderer
75
- ref={(node) => (renderer = node)}
76
- apiOptions={apiOptions}
77
- item={question}
78
- problemNum={0}
79
- reviewMode={false}
80
- savedState=""
81
- controlPeripherals={false}
82
- dependencies={testDependenciesV2}
83
- {...optionalProps}
84
- />
85
- {/* The ItemRenderer _requires_ two divs: a work area and hints
75
+ <StatefulKeypadContextProvider>
76
+ <ItemRenderer
77
+ ref={(node) => (renderer = node)}
78
+ apiOptions={apiOptions}
79
+ item={question}
80
+ problemNum={0}
81
+ reviewMode={false}
82
+ savedState=""
83
+ controlPeripherals={false}
84
+ dependencies={testDependenciesV2}
85
+ {...optionalProps}
86
+ />
87
+ {/* The ItemRenderer _requires_ two divs: a work area and hints
86
88
  area. Without both of these, it fails to render anything! */}
87
- <div id="workarea" />
88
- <div id="hintsarea" />
89
+ <div id="workarea" />
90
+ <div id="hintsarea" />
89
91
 
90
- <Peripherals />
92
+ <Peripherals />
93
+ </StatefulKeypadContextProvider>
91
94
  </RenderStateRoot>,
92
95
  );
93
96
  if (!renderer) {
@@ -309,18 +312,21 @@ describe("item renderer", () => {
309
312
  );
310
313
  });
311
314
 
312
- it("should activate the keypad when widget with input is focused", () => {
315
+ it("should activate the keypad when widget with input is focused", async () => {
313
316
  // Arrange
314
317
  const {renderer} = renderQuestion(itemWithInput, {
315
318
  isMobile: true,
316
319
  customKeypad: true,
320
+ useV2Keypad: true,
317
321
  });
318
322
 
319
323
  // Act
320
324
  renderer.focus();
321
325
 
322
326
  // Assert
323
- expect(screen.getByLabelText("7")).toBeVisible();
327
+ await waitFor(() => {
328
+ expect(screen.getByLabelText("7")).toBeVisible();
329
+ });
324
330
  });
325
331
 
326
332
  it("should provide current and previous focus paths on focus change to, and away from, a single widget", () => {
@@ -1,6 +1,6 @@
1
1
  import * as React from "react";
2
2
 
3
- import {ItemRendererWithDebugUI} from "../../../../../testing/item-renderer-with-debug-ui";
3
+ import {ServerItemRendererWithDebugUI} from "../../../../../testing/server-item-renderer-with-debug-ui";
4
4
  import {itemWithPieChart} from "../../__testdata__/graphie.testdata";
5
5
  import Graphie from "../graphie";
6
6
 
@@ -29,5 +29,5 @@ export const SquareBoxSizeAndOtherwiseEmpty = (
29
29
  };
30
30
 
31
31
  export const PieChartGraphieLabels = (args: StoryArgs): React.ReactElement => {
32
- return <ItemRendererWithDebugUI item={itemWithPieChart} />;
32
+ return <ServerItemRendererWithDebugUI item={itemWithPieChart} />;
33
33
  };
@@ -62,6 +62,7 @@ class HintRenderer extends React.Component<Props> {
62
62
  const {isMobile} = apiOptions;
63
63
 
64
64
  const classNames = classnames(
65
+ "hint",
65
66
  !isMobile && "perseus-hint-renderer",
66
67
  isMobile && css(styles.newHint),
67
68
  isMobile && lastRendered && css(styles.lastRenderedNewHint),
package/src/index.ts CHANGED
@@ -1,16 +1,13 @@
1
- /* eslint-disable @khanacademy/ts-no-error-suppressions */
2
1
  /**
3
2
  * Main entry point
4
3
  */
5
4
 
6
- import version from "./version";
7
-
8
5
  export {default as init} from "./init";
9
6
 
10
7
  export {ApiOptions, ClassNames} from "./perseus-api";
11
8
 
12
- export const apiVersion = version.apiVersion;
13
- export const itemDataVersion = version.itemDataVersion;
9
+ export {libVersion} from "./version";
10
+ export {apiVersion, itemDataVersion} from "./perseus-version";
14
11
  export {default as itemVersion} from "./item-version";
15
12
 
16
13
  /**
@@ -76,6 +76,9 @@ type SerializedState = {
76
76
  };
77
77
  };
78
78
 
79
+ /**
80
+ * @depreciated use ServerItemRenderer
81
+ */
79
82
  class ItemRenderer extends React.Component<Props, State> {
80
83
  // @ts-expect-error - TS2564 - Property 'questionRenderer' has no initializer and is not definitely assigned in the constructor.
81
84
  questionRenderer: Renderer;
@@ -1,10 +1,10 @@
1
1
  import allWidgets from "./all-widgets";
2
- import version from "./version";
2
+ import {itemDataVersion} from "./perseus-version";
3
3
  import * as Widgets from "./widgets";
4
4
 
5
5
  Widgets.registerWidgets(allWidgets);
6
6
 
7
7
  const ItemVersion = Widgets.getVersionVector();
8
- ItemVersion["::renderer::"] = version.itemDataVersion;
8
+ ItemVersion["::renderer::"] = itemDataVersion;
9
9
 
10
10
  export default ItemVersion;
@@ -1128,7 +1128,7 @@ exports[`multi-item renderer should snapshot: initial render 1`] = `
1128
1128
  class=""
1129
1129
  >
1130
1130
  <div
1131
- class="perseus-hint-renderer last-rendered"
1131
+ class="hint perseus-hint-renderer last-rendered"
1132
1132
  tabindex="-1"
1133
1133
  >
1134
1134
  <span
@@ -762,7 +762,8 @@ export type PerseusNumericInputAnswer = {
762
762
  // The forms available for this answer. Options: "integer, ""decimal", "proper", "improper", "mixed", or "pi"
763
763
  // NOTE: perseus_data.go says this is required even though it isn't necessary.
764
764
  answerForms?: ReadonlyArray<MathFormat>;
765
- // Whether the answerForms should be strictly matched
765
+ // Whether we should check the answer strictly against the the configured answerForms (strict = true)
766
+ // or include the set of default answerForms (strict = false).
766
767
  strict: boolean;
767
768
  // A range of error +/- the value
768
769
  // NOTE: perseus_data.go says this is non-nullable even though we handle null values.
@@ -0,0 +1,9 @@
1
+ export const apiVersion = {
2
+ major: 12,
3
+ minor: 0,
4
+ };
5
+
6
+ export const itemDataVersion = {
7
+ major: 0,
8
+ minor: 1,
9
+ };
@@ -10,6 +10,12 @@ var {
10
10
  font-style: normal;
11
11
  }
12
12
 
13
+ .hint.last-hint {
14
+ .paragraph {
15
+ font-weight: bold;
16
+ }
17
+ }
18
+
13
19
  .hint_blue {
14
20
  color: #6495ed;
15
21
  }
@@ -32,10 +38,6 @@ var {
32
38
  color: #9d38bd;
33
39
  }
34
40
 
35
- .last-hint {
36
- font-weight: bold;
37
- }
38
-
39
41
  div.subhint {
40
42
  border: 1px solid #aaaaaa;
41
43
  background: #f9f9f9;
@@ -144,7 +144,6 @@
144
144
  }
145
145
 
146
146
  .paragraph ul:not(.perseus-widget-radio) {
147
- .legacy-typography;
148
147
  padding-left: 35px;
149
148
  list-style-type: disc;
150
149
  }
package/src/version.ts CHANGED
@@ -1,11 +1,10 @@
1
- export default {
2
- apiVersion: {
3
- major: 12,
4
- minor: 0,
5
- },
1
+ // This file is processed by a Rollup plugin (replace) to inject the production
2
+ // version number during the release build.
3
+ // In dev, you'll never see the version number.
6
4
 
7
- itemDataVersion: {
8
- major: 0,
9
- minor: 1,
10
- },
11
- };
5
+ import {addLibraryVersionToPerseusDebug} from "@khanacademy/perseus-core";
6
+
7
+ const libName = "@khanacademy/perseus";
8
+ export const libVersion = "__lib_version__";
9
+
10
+ addLibraryVersionToPerseusDebug(libName, libVersion);
@@ -1,7 +1,7 @@
1
1
  import {KeypadContext, KeypadType} from "@khanacademy/math-input";
2
2
  import * as React from "react";
3
3
 
4
- import {ItemRendererWithDebugUI} from "../../../../../testing/item-renderer-with-debug-ui";
4
+ import {ServerItemRendererWithDebugUI} from "../../../../../testing/server-item-renderer-with-debug-ui";
5
5
  import {
6
6
  expressionItem2,
7
7
  expressionItem3,
@@ -25,20 +25,27 @@ type Story = {
25
25
  type WrappedKeypadContextProps = {
26
26
  item: PerseusItem;
27
27
  customKeypad: boolean;
28
+ isMobile?: boolean;
28
29
  };
29
30
 
30
- const WrappedKeypadContext = (props: WrappedKeypadContextProps) => {
31
+ const WrappedKeypadContext = ({
32
+ item,
33
+ customKeypad,
34
+ isMobile = false,
35
+ }: WrappedKeypadContextProps) => {
31
36
  return (
32
37
  <TestKeypadContextWrapper>
33
38
  <KeypadContext.Consumer>
34
- {({keypadElement, setRenderer, scrollableElement}) => {
39
+ {({keypadElement}) => {
35
40
  return (
36
- <ItemRendererWithDebugUI
37
- item={props.item}
41
+ <ServerItemRendererWithDebugUI
42
+ item={item}
43
+ keypadElement={keypadElement}
38
44
  // Hardcoding the V2 Keypad to true as the Storybook Args
39
45
  // were not working.
40
46
  apiOptions={{
41
- customKeypad: props.customKeypad,
47
+ isMobile: isMobile,
48
+ customKeypad: customKeypad,
42
49
  useV2Keypad: true,
43
50
  }}
44
51
  />
@@ -98,13 +105,17 @@ export const Mobile = (args: StoryArgs): React.ReactElement => {
98
105
  return (
99
106
  <div>
100
107
  <p>
101
- For some reason you need to be{" "}
108
+ MathInput uses touch events;{" "}
102
109
  <a href="https://developer.chrome.com/docs/devtools/device-mode/">
103
- emulating mobile
110
+ emulate mobile
104
111
  </a>{" "}
105
- to see the custom keypad.
112
+ to use the custom keypad.
106
113
  </p>
107
- <WrappedKeypadContext item={expressionItem3} customKeypad={true} />
114
+ <WrappedKeypadContext
115
+ item={expressionItem3}
116
+ customKeypad
117
+ isMobile
118
+ />
108
119
  </div>
109
120
  );
110
121
  };
@@ -1,4 +1,8 @@
1
- import {KeypadContext, MobileKeypad} from "@khanacademy/math-input";
1
+ import {
2
+ KeypadContext,
3
+ StatefulKeypadContextProvider,
4
+ MobileKeypad,
5
+ } from "@khanacademy/math-input";
2
6
  import {View} from "@khanacademy/wonder-blocks-core";
3
7
  import {action} from "@storybook/addon-actions";
4
8
  import {StyleSheet} from "aphrodite";
@@ -29,29 +33,11 @@ type Props = {
29
33
  };
30
34
 
31
35
  const TestKeypadContextWrapper = (props: Props): React.ReactElement => {
32
- const [keypadElement, setKeypadElement] = React.useState(null);
33
- const [renderer, setRenderer] = React.useState(null);
34
- const [scrollableElement, setScrollableElement] = React.useState(
35
- document.body,
36
- );
37
-
38
36
  return (
39
- <KeypadContext.Provider
40
- value={{
41
- // @ts-expect-error - TS2322 - Type 'Dispatch<SetStateAction<HTMLElement>>' is not assignable to type '(scrollableElement?: HTMLElement | null | undefined) => void'.
42
- setKeypadElement,
43
- keypadElement,
44
- // @ts-expect-error - TS2322 - Type 'Dispatch<SetStateAction<HTMLElement>>' is not assignable to type '(scrollableElement?: HTMLElement | null | undefined) => void'.
45
- setRenderer,
46
- renderer,
47
- // @ts-expect-error - TS2322 - Type 'Dispatch<SetStateAction<HTMLElement>>' is not assignable to type '(scrollableElement?: HTMLElement | null | undefined) => void'.
48
- setScrollableElement,
49
- scrollableElement,
50
- }}
51
- >
37
+ <StatefulKeypadContextProvider>
52
38
  {props.children}
53
39
  <Footer />
54
- </KeypadContext.Provider>
40
+ </StatefulKeypadContextProvider>
55
41
  );
56
42
  };
57
43
 
@@ -1,5 +1,9 @@
1
1
  import "@testing-library/jest-dom";
2
- import {KeypadContext, MobileKeypad} from "@khanacademy/math-input";
2
+ import {
3
+ KeypadContext,
4
+ StatefulKeypadContextProvider,
5
+ MobileKeypad,
6
+ } from "@khanacademy/math-input";
3
7
  import {RenderStateRoot} from "@khanacademy/wonder-blocks-core";
4
8
  import {
5
9
  fireEvent,
@@ -22,8 +26,6 @@ import {registerWidget} from "../../widgets";
22
26
  import {expressionItem2} from "../__testdata__/expression.testdata";
23
27
  import ExpressionExport from "../expression";
24
28
 
25
- import type {KeypadAPI} from "@khanacademy/math-input";
26
-
27
29
  const MQ = MathQuill.getInterface(2);
28
30
 
29
31
  function RendererWithContext({item}) {
@@ -67,27 +69,12 @@ function KeypadWithContext() {
67
69
  }
68
70
 
69
71
  function ConnectedRenderer({item = expressionItem2}) {
70
- const [keypadElement, setKeypadElement] =
71
- React.useState<KeypadAPI | null>();
72
- const [renderer, setRenderer] = React.useState<any>(null);
73
- const [scrollableElement, setScrollableElement] =
74
- React.useState<HTMLElement | null>();
75
-
76
72
  return (
77
73
  <RenderStateRoot>
78
- <KeypadContext.Provider
79
- value={{
80
- setKeypadElement,
81
- keypadElement,
82
- setRenderer,
83
- renderer,
84
- setScrollableElement,
85
- scrollableElement,
86
- }}
87
- >
74
+ <StatefulKeypadContextProvider>
88
75
  <RendererWithContext item={item} />
89
76
  <KeypadWithContext />
90
- </KeypadContext.Provider>
77
+ </StatefulKeypadContextProvider>
91
78
  </RenderStateRoot>
92
79
  );
93
80
  }
@@ -250,6 +250,41 @@ describe("static function validate", () => {
250
250
  expect(score.message?.includes("pi")).toBeFalsy();
251
251
  });
252
252
 
253
+ it("still validates against pi if provided in answerForms", () => {
254
+ const rubric: Rubric = {
255
+ answers: [
256
+ {
257
+ maxError: null,
258
+ message: "",
259
+ simplify: "required",
260
+ status: "correct",
261
+ strict: false,
262
+ value: 311.01767270538954,
263
+ answerForms: ["pi"],
264
+ },
265
+ ],
266
+ labelText: "",
267
+ size: "normal",
268
+ static: false,
269
+ coefficient: false,
270
+ };
271
+
272
+ const userInput = {
273
+ currentValue: "99 pi",
274
+ } as const;
275
+
276
+ const score = NumericInput.validate(userInput, rubric);
277
+
278
+ expect(score).toMatchInlineSnapshot(`
279
+ {
280
+ "earned": 1,
281
+ "message": null,
282
+ "total": 1,
283
+ "type": "points",
284
+ }
285
+ `);
286
+ });
287
+
253
288
  it("with a strict answer", () => {
254
289
  const rubric: Rubric = {
255
290
  answers: [
@@ -15,6 +15,7 @@ import type {
15
15
  PerseusNumericInputAnswer,
16
16
  PerseusNumericInputWidgetOptions,
17
17
  PerseusNumericInputAnswerForm,
18
+ MathFormat,
18
19
  } from "../perseus-types";
19
20
  import type {
20
21
  FocusPath,
@@ -25,7 +26,11 @@ import type {
25
26
 
26
27
  const ParseTex = TexWrangler.parseTex;
27
28
 
28
- const answerFormButtons = [
29
+ const answerFormButtons: ReadonlyArray<{
30
+ title: string;
31
+ value: MathFormat;
32
+ content: string;
33
+ }> = [
29
34
  {title: "Integers", value: "integer", content: "6"},
30
35
  {title: "Decimals", value: "decimal", content: "0.75"},
31
36
  {title: "Proper fractions", value: "proper", content: "\u2157"},
@@ -149,8 +154,8 @@ export class NumericInput extends React.Component<Props, State> {
149
154
  return answerStrings[0];
150
155
  }
151
156
 
152
- static validate(useInput: UserInput, rubric: Rubric): PerseusScore {
153
- const allAnswerForms = answerFormButtons
157
+ static validate(userInput: UserInput, rubric: Rubric): PerseusScore {
158
+ const defaultAnswerForms = answerFormButtons
154
159
  .map((e) => e["value"])
155
160
  // Don't default to validating the answer as a pi answer
156
161
  // if answerForm isn't set on the answer
@@ -159,6 +164,18 @@ export class NumericInput extends React.Component<Props, State> {
159
164
 
160
165
  const createValidator = (answer: PerseusNumericInputAnswer) => {
161
166
  const stringAnswer = `${answer.value}`;
167
+
168
+ // Always validate against the provided answer forms (pi, decimal, etc.)
169
+ const validatorForms = [...(answer.answerForms ?? [])];
170
+
171
+ // When an answer is set to strict, we validate using ONLY
172
+ // the provided answerForms. If strict is false, or if there
173
+ // were no provided answer forms, we will include all
174
+ // of the default answer forms in our validator.
175
+ if (!answer.strict || validatorForms.length === 0) {
176
+ validatorForms.push(...defaultAnswerForms);
177
+ }
178
+
162
179
  return KhanAnswerTypes.number.createValidatorFunctional(
163
180
  stringAnswer,
164
181
  {
@@ -169,19 +186,14 @@ export class NumericInput extends React.Component<Props, State> {
169
186
  : "optional",
170
187
  inexact: true, // TODO(merlob) backfill / delete
171
188
  maxError: answer.maxError,
172
- forms:
173
- answer.strict &&
174
- answer.answerForms &&
175
- answer.answerForms.length !== 0
176
- ? answer.answerForms
177
- : allAnswerForms,
189
+ forms: validatorForms,
178
190
  },
179
191
  );
180
192
  };
181
193
 
182
194
  // We may have received TeX; try to parse it before grading.
183
195
  // If `currentValue` is not TeX, this should be a no-op.
184
- const currentValue = ParseTex(useInput.currentValue);
196
+ const currentValue = ParseTex(userInput.currentValue);
185
197
  const correctAnswers = rubric.answers.filter(
186
198
  (answer) => answer.status === "correct",
187
199
  );
@@ -18,6 +18,7 @@
18
18
  {"path": "../kmath/tsconfig-build.json"},
19
19
  {"path": "../math-input/tsconfig-build.json"},
20
20
  {"path": "../perseus-core/tsconfig-build.json"},
21
+ {"path": "../perseus-core/tsconfig-build.json"},
21
22
  {"path": "../perseus-linter/tsconfig-build.json"},
22
23
  {"path": "../pure-markdown/tsconfig-build.json"},
23
24
  {"path": "../simple-markdown/tsconfig-build.json"},