@khanacademy/perseus 6.4.3 → 6.5.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/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": "6.4.3",
6
+ "version": "6.5.0",
7
7
  "publishConfig": {
8
8
  "access": "public"
9
9
  },
@@ -23,8 +23,8 @@
23
23
  "dependencies": {
24
24
  "@khanacademy/kas": "^0.3.1",
25
25
  "@khanacademy/kmath": "^0.1.1",
26
- "@khanacademy/math-input": "^6.0.1",
27
- "@khanacademy/perseus-core": "0.0.1",
26
+ "@khanacademy/math-input": "^6.0.3",
27
+ "@khanacademy/perseus-core": "0.0.2",
28
28
  "@khanacademy/perseus-linter": "^0.3.1",
29
29
  "@khanacademy/pure-markdown": "^0.2.3",
30
30
  "@khanacademy/simple-markdown": "^0.9.3"
@@ -4,6 +4,7 @@ import {ItemRendererWithDebugUI} from "../../../../testing/item-renderer-with-de
4
4
  import {
5
5
  itemWithInput,
6
6
  labelImageItem,
7
+ codeblockItem,
7
8
  } from "../__testdata__/item-renderer.testdata";
8
9
 
9
10
  type StoryArgs = Record<any, any>;
@@ -23,3 +24,7 @@ export const InputNumberItem = (args: StoryArgs): React.ReactElement => {
23
24
  export const LabelImageItem = (args: StoryArgs): React.ReactElement => {
24
25
  return <ItemRendererWithDebugUI item={labelImageItem} />;
25
26
  };
27
+
28
+ export const CodeblockItem = (args: StoryArgs): React.ReactElement => {
29
+ return <ItemRendererWithDebugUI item={codeblockItem} />;
30
+ };
@@ -5,6 +5,145 @@ import type {
5
5
  PerseusRenderer,
6
6
  } from "../perseus-types";
7
7
 
8
+ export const codeblockItem: PerseusItem = {
9
+ question: {
10
+ content:
11
+ 'Charlotte is writing code to turn sentences into Pig Latin.\n\nThis is what she has so far:\n\n```\nsentence ← ""\nword1 ← "Hello"\nfirstLetter1 ← SUBSTRING(word1, 1, 1)\notherLetters1 ← SUBSTRING(word1, 2, LENGTH(word1) - 1)\npigLatin1 ← CONCAT(otherLetters1, firstLetter1, "ay")\nsentence ← CONCAT(sentence, pigLatin1, " ")\nword2 ← "Mister"\nfirstLetter2 ← SUBSTRING(word2, 1, 1)\notherLetters2 ← SUBSTRING(word2, 2, LENGTH(word2) - 1)\npigLatin2 ← CONCAT(otherLetters2, firstLetter2, "ay")\nsentence ← CONCAT(sentence, pigLatin2, " ")\nword3 ← "Rogers"\nfirstLetter3 ← SUBSTRING(word3, 1, 1)\notherLetters3 ← SUBSTRING(word3, 2, LENGTH(word3) - 1)\npigLatin3 ← CONCAT(otherLetters3, firstLetter3, "ay")\nsentence ← CONCAT(sentence, pigLatin3, " ")\nDISPLAY(sentence)\n```\n\nThe code relies on two string procedures:\n\nName | Description\n- | - | -\n`CONCATENATE (string1, string2, ...)` | Returns a single string that combines the provided strings together in order.\n`SUBSTRING (string, startPos, numChars)` | Returns a substring of `string` starting at `startPos` of length `numChars`. The first character in the string is at position 1.\n\nA friend points out that she can reduce the complexity of her code by using the abstractions of lists and loops. \n\nCharlotte decides to "refactor" the code, to rewrite it so that it produces the same output but is structured better.\n\n**Which of these is the best refactor of the code?**\n\n[[☃ radio 1]]\n\n🤔[[☃ explanation 1]]',
12
+ images: {},
13
+ widgets: {
14
+ "radio 1": {
15
+ type: "radio",
16
+ alignment: "default",
17
+ static: false,
18
+ graded: true,
19
+ options: {
20
+ choices: [
21
+ {
22
+ content:
23
+ '```\nwords ← ["Hello", "Mister", "Rogers"]\nsentence ← ""\nFOR EACH word IN words\n{\n firstLetter ← SUBSTRING(word, 1, 1)\n otherLetters ← SUBSTRING(word, 2, LENGTH(word) - 1)\n pigLatin ← CONCAT(otherLetters, firstLetter, "ay")\n sentence ← CONCAT(sentence, pigLatin, " ")\n}\nDISPLAY(sentence)\n```',
24
+ correct: true,
25
+ },
26
+ {
27
+ content:
28
+ '```\nwords ← ["Hello", "Mister", "Rogers"]\nsentence ← ""\nFOR EACH word IN words\n{\n firstLetter ← SUBSTRING(words[1], 1, 1)\n otherLetters ← SUBSTRING(words[1], 2, LENGTH(words[1]) - 1)\n pigLatin ← CONCAT(otherLetters, firstLetter, "ay")\n sentence ← CONCAT(sentence, pigLatin, " ")\n}\nDISPLAY(sentence)\n```',
29
+ correct: false,
30
+ },
31
+ {
32
+ isNoneOfTheAbove: false,
33
+ content:
34
+ '```\nwords ← ["Hello", "Mister", "Rogers"]\nFOR EACH word IN words\n{\n sentence ← ""\n firstLetter ← SUBSTRING(word, 1, 1)\n otherLetters ← SUBSTRING(word, 2, LENGTH(word) - 1)\n pigLatin ← CONCAT(otherLetters, firstLetter, "ay")\n sentence ← CONCAT(sentence, pigLatin, " ")\n}\nDISPLAY(sentence)\n```',
35
+ correct: false,
36
+ },
37
+ {
38
+ isNoneOfTheAbove: false,
39
+ content:
40
+ '```\nwords ← ["Hello", "Mister", "Rogers"]\nsentence ← ""\nFOR EACH word IN words\n{\n firstLetter ← SUBSTRING(word, 1, 1)\n otherLetters ← SUBSTRING(word, 2, LENGTH(word) - 1)\n pigLatin ← CONCAT(otherLetters, firstLetter, "ay")\n sentence ← CONCAT(sentence, pigLatin, " ")\n DISPLAY(sentence)\n}\n```',
41
+ correct: false,
42
+ },
43
+ {
44
+ isNoneOfTheAbove: false,
45
+ content:
46
+ '```\nwords ← ["Hello", "Mister", "Rogers"]\nFOR EACH word IN words\n{\n sentence ← ""\n firstLetter ← SUBSTRING(word, 1, 1)\n otherLetters ← SUBSTRING(word, 2, LENGTH(word) - 1)\n pigLatin ← CONCAT(otherLetters, firstLetter, "ay")\n sentence ← CONCAT(sentence, pigLatin, " ")\n DISPLAY(sentence)\n}\n```',
47
+ },
48
+ ],
49
+ randomize: true,
50
+ multipleSelect: false,
51
+ countChoices: false,
52
+ displayCount: null,
53
+ hasNoneOfTheAbove: false,
54
+ deselectEnabled: false,
55
+ },
56
+ version: {
57
+ major: 1,
58
+ minor: 0,
59
+ },
60
+ },
61
+ "explanation 1": {
62
+ type: "explanation",
63
+ alignment: "default",
64
+ static: false,
65
+ graded: true,
66
+ options: {
67
+ static: false,
68
+ showPrompt: "What language is the code in?",
69
+ hidePrompt: "Hide explanation",
70
+ explanation:
71
+ "This code is written in **pseudocode**. You can't actually run pseudocode, but we use it for planning programs and for thinking about code independently of specific programming language. \n\nAP CSP exam questions use pseudocode, so it's helpful to become familiar with it.\n\nHere is a reference for the pseudocode used here:\n\n****\n\n`a ← expression`\n\nEvaluates `expression` and assigns the result to the variable `a`.\n\n****\n\n`list ← [value1, value2, value3]`\n\nCreates a list with 3 values. The first value is located at index 1, the second at index 2, the third at index 3.\n\n****\n\n```\nFOR EACH value IN list\n{\n <instructions>\n}\n```\n\nAssigns `value` to the value of each element in `list` sequentially, starting from the first element and ending with the last element. Executes the code in `instructions` once for each `value`.\n",
72
+ widgets: {},
73
+ },
74
+ version: {
75
+ major: 0,
76
+ minor: 0,
77
+ },
78
+ },
79
+ },
80
+ },
81
+ answerArea: {
82
+ calculator: false,
83
+ chi2Table: false,
84
+ periodicTable: false,
85
+ tTable: false,
86
+ zTable: false,
87
+ },
88
+ itemDataVersion: {
89
+ major: 0,
90
+ minor: 1,
91
+ },
92
+ hints: [
93
+ {
94
+ replace: false,
95
+ content:
96
+ 'The first step in refactoring this code is to recognize the related variables that can be combined into a list.\n\nIn this case, that\'s `word1`, `word2`, and `word3` from the original code. We can tell they\'re related from the naming, but more importantly, from the way the same code is repeated for each of them.\n\nWe\'ll store the values of those 3 variables in a single list:\n\n```words ← ["Hello", "Mister", "Rogers"]```',
97
+ images: {},
98
+ widgets: {},
99
+ },
100
+ {
101
+ replace: false,
102
+ content:
103
+ "The next step in refactoring this code is to identify the code that is only executed once versus the code that is executed for each element in that list.",
104
+ images: {},
105
+ widgets: {},
106
+ },
107
+ {
108
+ replace: false,
109
+ content:
110
+ 'We can see that there are 3 blocks of code that deal with each of the original variables.\n\nCode for `word1`:\n\n```\nword1 ← "Hello"\nfirstLetter1 ← SUBSTRING(word1, 1, 1)\notherLetters1 ← SUBSTRING(word1, 2, LENGTH(word1) - 1)\npigLatin1 ← CONCAT(otherLetters1, firstLetter1, "ay")\nsentence ← CONCAT(sentence, pigLatin1, " ")\n```\n\n\nCode for `word2`:\n\n```\nword2 ← "Mister"\nfirstLetter2 ← SUBSTRING(word2, 1, 1)\notherLetters2 ← SUBSTRING(word2, 2, LENGTH(word2) - 1)\npigLatin2 ← CONCAT(otherLetters2, firstLetter2, "ay")\nsentence ← CONCAT(sentence, pigLatin2, " ")\n```\n\nCode for `word3`:\n\n```\nword3 ← "Rogers"\nfirstLetter3 ← SUBSTRING(word3, 1, 1)\notherLetters3 ← SUBSTRING(word3, 2, LENGTH(word3) - 1)\npigLatin3 ← CONCAT(otherLetters3, firstLetter3, "ay")\nsentence ← CONCAT(sentence, pigLatin3, " ")\n```\n\nEach of these blocks of code do the same operations:\n\n1. store the word in a variable\n2. store a substring of the word, the first letter in it\n3. store another substring of the word, the rest of the letters\n4. store a new string that concatenates the two substrings and "ay"\n5. concatenate that string with the existing `sentence`',
111
+ images: {},
112
+ widgets: {},
113
+ },
114
+ {
115
+ replace: false,
116
+ content:
117
+ 'Now that we\'ve put the variables into a list, we can use a loop to execute those same operations on each element:\n\n```\nFOR EACH word in words\n{\n firstLetter ← SUBSTRING(word, 1, 1)\n otherLetters ← SUBSTRING(word, 2, LENGTH(word) - 1)\n pigLatin ← CONCAT(otherLetters, firstLetter, "ay")\n sentence ← CONCAT(sentence, pigLatin, " ")\n}\n```\n\nYou\'ll notice there are only four lines of code inside the loop, versus the five lines in the grouped blocks from before. That\'s because the `FOR EACH` header takes care of storing the current list element in `word`. The value of `word` will first be "Hello", then "Mister", then "Rogers", according to the order of the values in the list.\n',
118
+ images: {},
119
+ widgets: {},
120
+ },
121
+ {
122
+ replace: false,
123
+ content:
124
+ 'There are two lines of code that we haven\'t refactored yet.\n\nOne is the very first line of the original code:\n\n`sentence ← ""`\n\nIn the new code, this needs to happen _before_ the `FOR EACH` loop, since the value of `sentence` is manipulated inside the loop.',
125
+ images: {},
126
+ widgets: {},
127
+ },
128
+ {
129
+ replace: false,
130
+ content:
131
+ "The remaining line is the very last line of the original code:\n\n```DISPLAY(sentence)```\n\nIn the new code, this needs to happen _after_ the `FOR EACH` loop, since it should display the value after all the concatenations happen.",
132
+ images: {},
133
+ widgets: {},
134
+ },
135
+ {
136
+ replace: false,
137
+ content:
138
+ 'Thus, this is the best refactor of the original code to use lists and loops:\n\n```\nwords ← ["Hello", "Mister", "Rogers"]\nsentence ← ""\nFOR EACH word IN words\n{\n firstLetter ← SUBSTRING(word, 1, 1)\n otherLetters ← SUBSTRING(word, 2, LENGTH(word) - 1)\n pigLatin ← CONCAT(otherLetters, firstLetter, "ay")\n sentence ← CONCAT(sentence, pigLatin, " ")\n}\nDISPLAY(sentence)\n```',
139
+ images: {},
140
+ widgets: {},
141
+ },
142
+ ],
143
+ _multi: null,
144
+ answer: null,
145
+ };
146
+
8
147
  export const itemWithInput: PerseusItem = {
9
148
  question: {
10
149
  content:
@@ -4,14 +4,15 @@ import * as React from "react";
4
4
  import ReactDOM from "react-dom";
5
5
  import _ from "underscore";
6
6
 
7
- import {Coord} from "../interactive2/types";
8
- import {PerseusImageBackground} from "../perseus-types";
9
7
  import {interactiveSizes} from "../styles/constants";
10
8
  import Util from "../util";
11
9
  import GraphUtils from "../util/graph-utils";
12
10
 
13
11
  import SvgImage from "./svg-image";
14
12
 
13
+ import type {Coord} from "../interactive2/types";
14
+ import type {PerseusImageBackground} from "../perseus-types";
15
+
15
16
  const defaultBackgroundImage = {
16
17
  url: null,
17
18
  };
@@ -6,7 +6,6 @@ import * as React from "react";
6
6
  import _ from "underscore";
7
7
 
8
8
  import {getDependencies} from "../dependencies";
9
- import {Coord} from "../interactive2/types";
10
9
  import {Errors, Log} from "../logging/log";
11
10
  import {PerseusError} from "../perseus-error";
12
11
  import Util from "../util";
@@ -16,6 +15,7 @@ import FixedToResponsive from "./fixed-to-responsive";
16
15
  import Graphie from "./graphie";
17
16
  import ImageLoader from "./image-loader";
18
17
 
18
+ import type {Coord} from "../interactive2/types";
19
19
  import type {Size} from "../perseus-types";
20
20
  import type {Alignment, Dimensions} from "../types";
21
21
  import type {ImageProps} from "./image-loader";
@@ -105,10 +105,10 @@ exports[`multi-item renderer should snapshot: initial render 1`] = `
105
105
  class="perseus-widget-container widget-nohighlight widget-block"
106
106
  >
107
107
  <div
108
- class="responsiveContainer_vq37gq"
108
+ class="responsiveContainer_12z1v8o"
109
109
  >
110
110
  <fieldset
111
- class="perseus-widget-radio-fieldset responsiveFieldset_c0l5kf"
111
+ class="perseus-widget-radio-fieldset responsiveFieldset_le06t6"
112
112
  >
113
113
  <legend
114
114
  class="perseus-sr-only"
@@ -117,7 +117,7 @@ exports[`multi-item renderer should snapshot: initial render 1`] = `
117
117
  </legend>
118
118
  <div
119
119
  aria-hidden="true"
120
- class="instructions instructions_125m8j1"
120
+ class="instructions instructions_f9jkqj"
121
121
  >
122
122
  Choose 1 answer:
123
123
  </div>
@@ -133,7 +133,7 @@ exports[`multi-item renderer should snapshot: initial render 1`] = `
133
133
  style="flex-direction: column; color: rgb(33, 36, 44);"
134
134
  >
135
135
  <div
136
- style="display: flex; flex-direction: row; opacity: 1;"
136
+ style="display: flex; flex-direction: row; opacity: 1; overflow: auto;"
137
137
  >
138
138
  <div
139
139
  class="perseus-sr-only"
@@ -300,7 +300,7 @@ exports[`multi-item renderer should snapshot: initial render 1`] = `
300
300
  style="flex-direction: column; color: rgb(33, 36, 44);"
301
301
  >
302
302
  <div
303
- style="display: flex; flex-direction: row; opacity: 1;"
303
+ style="display: flex; flex-direction: row; opacity: 1; overflow: auto;"
304
304
  >
305
305
  <div
306
306
  class="perseus-sr-only"
@@ -467,7 +467,7 @@ exports[`multi-item renderer should snapshot: initial render 1`] = `
467
467
  style="flex-direction: column; color: rgb(33, 36, 44);"
468
468
  >
469
469
  <div
470
- style="display: flex; flex-direction: row; opacity: 1;"
470
+ style="display: flex; flex-direction: row; opacity: 1; overflow: auto;"
471
471
  >
472
472
  <div
473
473
  class="perseus-sr-only"
@@ -634,7 +634,7 @@ exports[`multi-item renderer should snapshot: initial render 1`] = `
634
634
  style="flex-direction: column; color: rgb(33, 36, 44);"
635
635
  >
636
636
  <div
637
- style="display: flex; flex-direction: row; opacity: 1;"
637
+ style="display: flex; flex-direction: row; opacity: 1; overflow: auto;"
638
638
  >
639
639
  <div
640
640
  class="perseus-sr-only"
@@ -801,7 +801,7 @@ exports[`multi-item renderer should snapshot: initial render 1`] = `
801
801
  style="flex-direction: column; color: rgb(33, 36, 44);"
802
802
  >
803
803
  <div
804
- style="display: flex; flex-direction: row; opacity: 1;"
804
+ style="display: flex; flex-direction: row; opacity: 1; overflow: auto;"
805
805
  >
806
806
  <div
807
807
  class="perseus-sr-only"
@@ -129,7 +129,9 @@
129
129
  margin-top: 32px;
130
130
  }
131
131
 
132
- h4, h5, h6 {
132
+ h4,
133
+ h5,
134
+ h6 {
133
135
  font-family: inherit;
134
136
  font-size: 22px;
135
137
  font-weight: 700;
@@ -175,7 +177,8 @@
175
177
 
176
178
  // Some images use an image widget, but some are just inlined in the
177
179
  // paragraph. Inlined images need legacy typography, too.
178
- .unresponsive-svg-image, .svg-image {
180
+ .unresponsive-svg-image,
181
+ .svg-image {
179
182
  .legacy-typography;
180
183
  }
181
184
  .perseus-block-math {
@@ -201,7 +204,12 @@
201
204
  // If the article starts with a heading, remove its unnecessary top margin.
202
205
  > .clearfix:first-child {
203
206
  > .perseus-renderer:first-child > .paragraph:first-child {
204
- h1, h2, h3, h4, h5, h6 {
207
+ h1,
208
+ h2,
209
+ h3,
210
+ h4,
211
+ h5,
212
+ h6 {
205
213
  &:first-child {
206
214
  margin-top: 0;
207
215
  }
@@ -235,10 +243,10 @@
235
243
  color: @gray17;
236
244
  font-size: 18px;
237
245
  padding: 16px;
238
- white-space: pre-wrap;
246
+ white-space: pre;
247
+ overflow: auto;
239
248
  margin: 0 -16px 32px -16px;
240
249
  }
241
-
242
250
  }
243
251
 
244
252
  // HACK(emily): This is a copy of a bunch of the mixins from the block of
@@ -343,7 +351,8 @@
343
351
  // 06/08/2016, there are 188 articles and exercises that use H4 (and
344
352
  // just 8 that use H5 or H6). As such, we provide style support for H4,
345
353
  // since it's still quite common--but we just replicate the H3 style.
346
- h3, h4 {
354
+ h3,
355
+ h4 {
347
356
  .header(@fontSize, @lineHeight, @gray41, @paddingTopMultiplier);
348
357
  }
349
358
  }
@@ -421,7 +430,6 @@
421
430
 
422
431
  .math(@blockMathTextSize; @blockMathLineHeight;
423
432
  @inlineMathTextSize; @inlineMathLineHeight) {
424
-
425
433
  .katex {
426
434
  font-size: @inlineMathTextSize;
427
435
  line-height: @inlineMathLineHeight;
@@ -446,7 +454,6 @@
446
454
 
447
455
  .code(@codeTextSize; @codeTextLineHeight;
448
456
  @codeBlockHorizontalMarginMultiplier) {
449
-
450
457
  code {
451
458
  font-family: Courier, monospace;
452
459
  }
@@ -458,9 +465,8 @@
458
465
  font-size: @codeTextSize;
459
466
  line-height: @codeTextLineHeight;
460
467
  padding: @baseUnit;
461
- white-space: pre-wrap;
462
- // NOTE(pamela): This rule previously included negative margins.
463
- // However, negative margins overlap with question answers (A/B/C)
468
+ white-space: pre;
469
+ overflow: auto;
464
470
  }
465
471
  }
466
472
 
@@ -529,7 +535,8 @@
529
535
  // Support for nested lists
530
536
  .perseus-renderer > .paragraph ol,
531
537
  .perseus-renderer > .paragraph ul:not(.perseus-widget-radio) {
532
- ol, ul:not(.perseus-widget-radio) {
538
+ ol,
539
+ ul:not(.perseus-widget-radio) {
533
540
  padding-top: 1.5 * @baseUnit;
534
541
  }
535
542
  }
@@ -537,7 +544,7 @@
537
544
  // Disable the Safari tap highlight for block math, which is now tappable
538
545
  // via the tap-to-zoom functionality.
539
546
  .perseus-block-math {
540
- -webkit-tap-highlight-color: rgba(0,0,0,0);
547
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
541
548
  -webkit-touch-callout: none;
542
549
  }
543
550
 
@@ -652,7 +659,8 @@
652
659
 
653
660
  // Some images use an image widget, but some are just inlined in the
654
661
  // paragraph. Inlined images need legacy typography, too.
655
- .unresponsive-svg-image, .svg-image {
662
+ .unresponsive-svg-image,
663
+ .svg-image {
656
664
  .legacy-typography;
657
665
  }
658
666
 
@@ -673,8 +681,6 @@
673
681
  }
674
682
  }
675
683
 
676
-
677
-
678
684
  /* Derived from the MIT-licensed zoom.js:
679
685
  https://github.com/fat/zoom.js/blob/fd4f3e43153da7596da0bade198e99f98b47791e/
680
686
  */
@@ -704,7 +710,7 @@ img.zoom-img {
704
710
  }
705
711
 
706
712
  .zoom-transition {
707
- transition: transform @zoomAnimationDurationMs ease;
713
+ transition: transform @zoomAnimationDurationMs ease;
708
714
  }
709
715
 
710
716
  .zoom-overlay {
@@ -739,7 +745,7 @@ img.zoom-img {
739
745
  bottom: 0;
740
746
  background: white;
741
747
  opacity: 0;
742
- transition: opacity @zoomAnimationDurationMs;
748
+ transition: opacity @zoomAnimationDurationMs;
743
749
  }
744
750
 
745
751
  .zoom-overlay-open > .zoom-backdrop {