@khanacademy/perseus 0.0.1 → 0.2.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 (507) hide show
  1. package/.babelrc.js +1 -1
  2. package/CHANGELOG.md +29 -0
  3. package/dist/es/index.css +4361 -0
  4. package/dist/es/index.js +7 -1
  5. package/dist/es/index.js.map +1 -1
  6. package/dist/index.css +4361 -0
  7. package/dist/index.js +7 -1
  8. package/dist/index.js.map +1 -1
  9. package/dist/strings.js +148 -46
  10. package/package.json +50 -4
  11. package/src/README.md +35 -0
  12. package/src/__stories__/item-renderer.stories.jsx +27 -0
  13. package/src/__stories__/server-item-renderer.stories.jsx +27 -0
  14. package/src/__testdata__/item-renderer_testdata.js +161 -0
  15. package/src/__testdata__/renderer_testdata.js +123 -0
  16. package/src/__tests__/__snapshots__/item-renderer_test.jsx.snap +233 -0
  17. package/src/__tests__/__snapshots__/perseus-markdown_test.jsx.snap +516 -0
  18. package/src/__tests__/__snapshots__/pure-markdown_test.js.snap +113 -0
  19. package/src/__tests__/__snapshots__/renderer_test.jsx.snap +576 -0
  20. package/src/__tests__/__snapshots__/server-item-renderer_test.jsx.snap +306 -0
  21. package/src/__tests__/a11y_test.jsx +304 -0
  22. package/src/__tests__/item-renderer_test.jsx +892 -0
  23. package/src/__tests__/mock-asset-loading-widget.jsx +57 -0
  24. package/src/__tests__/mock-widget.jsx +35 -0
  25. package/src/__tests__/perseus-markdown_test.jsx +747 -0
  26. package/src/__tests__/pure-markdown_test.js +552 -0
  27. package/src/__tests__/renderability_test.jsx +481 -0
  28. package/src/__tests__/renderer-api_test.jsx +333 -0
  29. package/src/__tests__/renderer_test.jsx +1792 -0
  30. package/src/__tests__/server-item-renderer_test.jsx +516 -0
  31. package/src/__tests__/test-items/image-item.js +25 -0
  32. package/src/__tests__/test-items/input-number-1-item.js +27 -0
  33. package/src/__tests__/test-items/input-number-2-item.js +39 -0
  34. package/src/__tests__/test-items/table-item.js +30 -0
  35. package/src/__tests__/tex-wrangler_test.js +83 -0
  36. package/src/__tests__/util_test.js +41 -0
  37. package/src/a11y.js +46 -0
  38. package/src/all-widgets.js +9 -0
  39. package/src/article-renderer.jsx +215 -0
  40. package/src/asset-context.js +30 -0
  41. package/src/basic-widgets.js +13 -0
  42. package/src/components/__stories__/button-group.stories.jsx +61 -0
  43. package/src/components/__stories__/fixed-to-responsive.stories.jsx +132 -0
  44. package/src/components/__stories__/graph.stories.jsx +27 -0
  45. package/src/components/__stories__/graphie.stories.jsx +28 -0
  46. package/src/components/__stories__/hud.stories.jsx +36 -0
  47. package/src/components/__stories__/icon.stories.jsx +56 -0
  48. package/src/components/__stories__/image-loader.stories.jsx +60 -0
  49. package/src/components/__stories__/info-tip.stories.jsx +33 -0
  50. package/src/components/__stories__/inline-icon.stories.jsx +39 -0
  51. package/src/components/__stories__/input-with-examples.stories.jsx +51 -0
  52. package/src/components/__stories__/lint.stories.jsx +40 -0
  53. package/src/components/__stories__/math-input.stories.jsx +29 -0
  54. package/src/components/__stories__/math-output.stories.jsx +24 -0
  55. package/src/components/__stories__/multi-button-group.stories.jsx +69 -0
  56. package/src/components/__stories__/number-input.stories.jsx +42 -0
  57. package/src/components/__stories__/possible-answers.stories.jsx +22 -0
  58. package/src/components/__stories__/prop-check-box.stories.jsx +62 -0
  59. package/src/components/__stories__/range-input.stories.jsx +28 -0
  60. package/src/components/__stories__/simple-keypad-input.stories.jsx +28 -0
  61. package/src/components/__stories__/sortable.stories.jsx +73 -0
  62. package/src/components/__stories__/stub-tag-editor.stories.jsx +44 -0
  63. package/src/components/__stories__/svg-image.stories.jsx +60 -0
  64. package/src/components/__stories__/tex.stories.jsx +18 -0
  65. package/src/components/__stories__/text-buttons.stories.jsx +51 -0
  66. package/src/components/__stories__/text-input.stories.jsx +34 -0
  67. package/src/components/__stories__/text-list-editor.stories.jsx +33 -0
  68. package/src/components/__stories__/zoomable-tex.stories.jsx +39 -0
  69. package/src/components/__stories__/zoomable.stories.jsx +40 -0
  70. package/src/components/__testdata__/sorter_testdata.js +20 -0
  71. package/src/components/__tests__/__snapshots__/sortable_test.jsx.snap +79 -0
  72. package/src/components/__tests__/__snapshots__/sorter_test.jsx.snap +297 -0
  73. package/src/components/__tests__/number-input_test.jsx +112 -0
  74. package/src/components/__tests__/sortable_test.jsx +234 -0
  75. package/src/components/__tests__/sorter_test.jsx +111 -0
  76. package/src/components/__tests__/zoomable_test.jsx +388 -0
  77. package/src/components/button-group.jsx +131 -0
  78. package/src/components/constants.js +15 -0
  79. package/src/components/fixed-to-responsive.jsx +164 -0
  80. package/src/components/graph.jsx +415 -0
  81. package/src/components/graphie-classes.jsx +148 -0
  82. package/src/components/graphie-movables.jsx +342 -0
  83. package/src/components/graphie.jsx +353 -0
  84. package/src/components/highlighting/highlightable-content.jsx +245 -0
  85. package/src/components/highlighting/highlights.js +208 -0
  86. package/src/components/highlighting/ranges.js +299 -0
  87. package/src/components/highlighting/types.js +73 -0
  88. package/src/components/highlighting/ui/highlight-renderer.jsx +179 -0
  89. package/src/components/highlighting/ui/highlight-set-renderer.jsx +190 -0
  90. package/src/components/highlighting/ui/highlight-tooltip.jsx +153 -0
  91. package/src/components/highlighting/ui/highlighting-ui.jsx +163 -0
  92. package/src/components/highlighting/ui/selection-tracker.jsx +195 -0
  93. package/src/components/highlighting/ui/types.js +21 -0
  94. package/src/components/highlighting/ui/util.js +166 -0
  95. package/src/components/highlighting/word-indexer.jsx +155 -0
  96. package/src/components/hud.jsx +179 -0
  97. package/src/components/icon.jsx +149 -0
  98. package/src/components/image-loader.jsx +175 -0
  99. package/src/components/info-tip/info-tip.jsx +146 -0
  100. package/src/components/info-tip.jsx +38 -0
  101. package/src/components/inline-icon.jsx +81 -0
  102. package/src/components/input-with-examples.jsx +231 -0
  103. package/src/components/lint.jsx +454 -0
  104. package/src/components/math-input.jsx +282 -0
  105. package/src/components/math-output.jsx +128 -0
  106. package/src/components/multi-button-group.jsx +137 -0
  107. package/src/components/new-tooltip/README.md +110 -0
  108. package/src/components/new-tooltip/__stories__/flexible-position.stories.jsx +437 -0
  109. package/src/components/new-tooltip/__stories__/new-tooltip.stories.jsx +665 -0
  110. package/src/components/new-tooltip/__stories__/tooltip-arrow.stories.jsx +70 -0
  111. package/src/components/new-tooltip/__stories__/tooltip-bubble.stories.jsx +98 -0
  112. package/src/components/new-tooltip/__tests__/style-constants_test.js +8 -0
  113. package/src/components/new-tooltip/__tests__/util_test.js +47 -0
  114. package/src/components/new-tooltip/flexible-position.jsx +182 -0
  115. package/src/components/new-tooltip/new-tooltip.jsx +399 -0
  116. package/src/components/new-tooltip/style-constants.js +13 -0
  117. package/src/components/new-tooltip/tooltip-arrow.jsx +184 -0
  118. package/src/components/new-tooltip/tooltip-bubble.jsx +223 -0
  119. package/src/components/new-tooltip/tooltip-portal.jsx +101 -0
  120. package/src/components/new-tooltip/tooltip-positioner.jsx +529 -0
  121. package/src/components/new-tooltip/types.js +183 -0
  122. package/src/components/new-tooltip/util.js +27 -0
  123. package/src/components/number-input.jsx +253 -0
  124. package/src/components/possible-answers.jsx +36 -0
  125. package/src/components/prop-check-box.jsx +80 -0
  126. package/src/components/range-input.jsx +61 -0
  127. package/src/components/simple-keypad-input.jsx +91 -0
  128. package/src/components/sortable.jsx +873 -0
  129. package/src/components/stub-tag-editor.jsx +54 -0
  130. package/src/components/svg-image.jsx +762 -0
  131. package/src/components/tex-buttons.jsx +322 -0
  132. package/src/components/tex.jsx +70 -0
  133. package/src/components/text-input.jsx +82 -0
  134. package/src/components/text-list-editor.jsx +157 -0
  135. package/src/components/tooltip.jsx +395 -0
  136. package/src/components/visibility-observer/__stories__/visibility-observer.stories.jsx +140 -0
  137. package/src/components/visibility-observer/get-scroll-parent.js +45 -0
  138. package/src/components/visibility-observer/visibility-observer.js +109 -0
  139. package/src/components/visibility-observer/visibility-observer.jsx +123 -0
  140. package/src/components/zoomable-tex.jsx +70 -0
  141. package/src/components/zoomable.jsx +304 -0
  142. package/src/components.js +17 -0
  143. package/src/dependencies.js +22 -0
  144. package/src/error-boundary.jsx +48 -0
  145. package/src/extra-widgets.js +75 -0
  146. package/src/gorgon/README +40 -0
  147. package/src/gorgon/__tests__/matcher_test.js +497 -0
  148. package/src/gorgon/__tests__/rule_test.js +101 -0
  149. package/src/gorgon/__tests__/rules_test.js +487 -0
  150. package/src/gorgon/__tests__/selector-parser_test.js +52 -0
  151. package/src/gorgon/__tests__/tree-transformer_test.js +446 -0
  152. package/src/gorgon/gorgon.js +290 -0
  153. package/src/gorgon/proptypes.js +29 -0
  154. package/src/gorgon/rule.js +413 -0
  155. package/src/gorgon/rules/absolute-url.js +24 -0
  156. package/src/gorgon/rules/all-rules.js +72 -0
  157. package/src/gorgon/rules/blockquoted-math.js +10 -0
  158. package/src/gorgon/rules/blockquoted-widget.js +10 -0
  159. package/src/gorgon/rules/double-spacing-after-terminal.js +12 -0
  160. package/src/gorgon/rules/extra-content-spacing.js +12 -0
  161. package/src/gorgon/rules/heading-level-1.js +14 -0
  162. package/src/gorgon/rules/heading-level-skip.js +20 -0
  163. package/src/gorgon/rules/heading-sentence-case.js +11 -0
  164. package/src/gorgon/rules/heading-title-case.js +63 -0
  165. package/src/gorgon/rules/image-alt-text.js +21 -0
  166. package/src/gorgon/rules/image-in-table.js +10 -0
  167. package/src/gorgon/rules/image-spaces-around-urls.js +35 -0
  168. package/src/gorgon/rules/image-widget.js +50 -0
  169. package/src/gorgon/rules/link-click-here.js +11 -0
  170. package/src/gorgon/rules/lint-utils.js +48 -0
  171. package/src/gorgon/rules/long-paragraph.js +14 -0
  172. package/src/gorgon/rules/math-adjacent.js +10 -0
  173. package/src/gorgon/rules/math-align-extra-break.js +11 -0
  174. package/src/gorgon/rules/math-align-linebreaks.js +43 -0
  175. package/src/gorgon/rules/math-empty.js +10 -0
  176. package/src/gorgon/rules/math-font-size.js +12 -0
  177. package/src/gorgon/rules/math-frac.js +10 -0
  178. package/src/gorgon/rules/math-nested.js +11 -0
  179. package/src/gorgon/rules/math-starts-with-space.js +12 -0
  180. package/src/gorgon/rules/math-text-empty.js +10 -0
  181. package/src/gorgon/rules/math-without-dollars.js +14 -0
  182. package/src/gorgon/rules/nested-lists.js +11 -0
  183. package/src/gorgon/rules/profanity.js +10 -0
  184. package/src/gorgon/rules/table-missing-cells.js +20 -0
  185. package/src/gorgon/rules/unbalanced-code-delimiters.js +14 -0
  186. package/src/gorgon/rules/unescaped-dollar.js +10 -0
  187. package/src/gorgon/rules/widget-in-table.js +10 -0
  188. package/src/gorgon/selector.js +506 -0
  189. package/src/gorgon/tree-transformer.js +588 -0
  190. package/src/hint-renderer.jsx +159 -0
  191. package/src/hints-renderer.jsx +359 -0
  192. package/src/icon-paths.js +120 -0
  193. package/src/index.js +149 -2
  194. package/src/init.js +49 -0
  195. package/src/interaction-tracker.js +60 -0
  196. package/src/interactive2/__tests__/movable-mock.js +48 -0
  197. package/src/interactive2/__tests__/movable-point_test.js +146 -0
  198. package/src/interactive2/interactive-util.js +152 -0
  199. package/src/interactive2/movable-helper-methods.js +160 -0
  200. package/src/interactive2/movable-line-options.js +392 -0
  201. package/src/interactive2/movable-line.js +322 -0
  202. package/src/interactive2/movable-point-options.js +190 -0
  203. package/src/interactive2/movable-point.jsx +603 -0
  204. package/src/interactive2/movable-polygon-options.js +351 -0
  205. package/src/interactive2/movable-polygon.js +368 -0
  206. package/src/interactive2/movable.js +273 -0
  207. package/src/interactive2/objective_.js +79 -0
  208. package/src/interactive2/types.js +3 -0
  209. package/src/interactive2/wrapped-defaults.js +86 -0
  210. package/src/interactive2/wrapped-ellipse.js +87 -0
  211. package/src/interactive2/wrapped-line.js +109 -0
  212. package/src/interactive2/wrapped-path.js +47 -0
  213. package/src/interactive2.js +91 -0
  214. package/src/item-renderer.jsx +518 -0
  215. package/src/item-version.js +12 -0
  216. package/src/jipt-paragraphs.js +44 -0
  217. package/src/jquery.mobile.vmouse.js +545 -0
  218. package/src/keypad-context.js +33 -0
  219. package/src/loading-context.js +20 -0
  220. package/src/logging/log.js +107 -0
  221. package/src/logging/log_test.js +41 -0
  222. package/src/mixins/changeable.jsx +115 -0
  223. package/src/mixins/editor-jsonify.jsx +13 -0
  224. package/src/mixins/provide-keypad.jsx +129 -0
  225. package/src/mixins/widget-jsonify-deprecated.jsx +23 -0
  226. package/src/mixins/widget-prop-blacklist.js +36 -0
  227. package/src/multi-items/__stories__/multi-renderer.stories.js +88 -0
  228. package/src/multi-items/__testdata__/multi-renderer_testdata.js +174 -0
  229. package/src/multi-items/__tests__/__snapshots__/multi-renderer_test.js.snap +994 -0
  230. package/src/multi-items/__tests__/items_test.js +245 -0
  231. package/src/multi-items/__tests__/multi-renderer_test.js +903 -0
  232. package/src/multi-items/__tests__/prop-type-builders_test.js +134 -0
  233. package/src/multi-items/__tests__/shapes_test.js +44 -0
  234. package/src/multi-items/__tests__/trees_test.js +316 -0
  235. package/src/multi-items/item-types.js +55 -0
  236. package/src/multi-items/items.js +176 -0
  237. package/src/multi-items/multi-renderer.jsx +543 -0
  238. package/src/multi-items/prop-type-builders.js +105 -0
  239. package/src/multi-items/shape-types.js +47 -0
  240. package/src/multi-items/shapes.js +53 -0
  241. package/src/multi-items/tree-types.js +42 -0
  242. package/src/multi-items/trees.js +345 -0
  243. package/src/multi-items.js +76 -0
  244. package/src/not-gorgon.js +129 -0
  245. package/src/perseus-api.jsx +270 -0
  246. package/src/perseus-env.js +26 -0
  247. package/src/perseus-error.js +19 -0
  248. package/src/perseus-markdown.jsx +433 -0
  249. package/src/perseus-types.js +1597 -0
  250. package/src/pure-markdown.js +347 -0
  251. package/src/question-paragraph.jsx +30 -0
  252. package/src/renderability.jsx +108 -0
  253. package/src/renderer.jsx +1932 -0
  254. package/src/server-item-renderer.jsx +467 -0
  255. package/src/sigfigs.jsx +492 -0
  256. package/src/styles/articles.less +748 -0
  257. package/src/styles/constants.js +119 -0
  258. package/src/styles/global-constants.js +350 -0
  259. package/src/styles/global-styles.js +307 -0
  260. package/src/styles/khan-exercise.css +1304 -0
  261. package/src/styles/media-queries.js +48 -0
  262. package/src/styles/perseus-renderer.less +1007 -0
  263. package/src/styles/shared.js +132 -0
  264. package/src/styles/util.less +47 -0
  265. package/src/styles/variables.boostrap.less +301 -0
  266. package/src/styles/variables.less +68 -0
  267. package/src/styles/variables.shared.less +356 -0
  268. package/src/styles/widgets/categorizer.less +39 -0
  269. package/src/styles/widgets/definition.less +86 -0
  270. package/src/styles/widgets/dropdown.less +3 -0
  271. package/src/styles/widgets/expression.less +147 -0
  272. package/src/styles/widgets/graded-group.less +36 -0
  273. package/src/styles/widgets/grapher.less +11 -0
  274. package/src/styles/widgets/group.less +19 -0
  275. package/src/styles/widgets/image.less +29 -0
  276. package/src/styles/widgets/interactive-graph.less +61 -0
  277. package/src/styles/widgets/label-image.less +41 -0
  278. package/src/styles/widgets/matcher.less +8 -0
  279. package/src/styles/widgets/matrix.less +120 -0
  280. package/src/styles/widgets/measurer.less +20 -0
  281. package/src/styles/widgets/orderer.less +97 -0
  282. package/src/styles/widgets/passage.less +151 -0
  283. package/src/styles/widgets/plotter.less +33 -0
  284. package/src/styles/widgets/radio.less +52 -0
  285. package/src/styles/widgets/reaction-diagram.less +35 -0
  286. package/src/styles/widgets/sequence.less +5 -0
  287. package/src/styles/widgets/sortable.less +127 -0
  288. package/src/styles/widgets/table.less +56 -0
  289. package/src/styles/widgets/transformer.less +28 -0
  290. package/src/styles/widgets/unit.less +14 -0
  291. package/src/tex-wrangler.js +176 -0
  292. package/src/traversal.jsx +141 -0
  293. package/src/types.js +538 -0
  294. package/src/util/a11y.js +24 -0
  295. package/src/util/answer-types.js +862 -0
  296. package/src/util/answer-types_test.js +115 -0
  297. package/src/util/colors.js +94 -0
  298. package/src/util/fix-passage-refs.jsx +122 -0
  299. package/src/util/graph-utils.js +16 -0
  300. package/src/util/graphie.js +1586 -0
  301. package/src/util/interactive.js +4078 -0
  302. package/src/util/katex-preprocess.js +16 -0
  303. package/src/util/math.js +296 -0
  304. package/src/util/mobile-native-utils.js +14 -0
  305. package/src/util/mobile-native-utils_test.js +24 -0
  306. package/src/util/register-all-widgets-for-testing.js +12 -0
  307. package/src/util/scroll-utils.js +162 -0
  308. package/src/util/sizing-utils.js +46 -0
  309. package/src/util/tex.js +203 -0
  310. package/src/util/url-utils.js +36 -0
  311. package/src/util.js +990 -0
  312. package/src/version.js +11 -0
  313. package/src/widget-container.jsx +174 -0
  314. package/src/widgets/__stories__/categorizer.stories.jsx +15 -0
  315. package/src/widgets/__stories__/cs-program.stories.jsx +15 -0
  316. package/src/widgets/__stories__/definition.stories.jsx +38 -0
  317. package/src/widgets/__stories__/dropdown.stories.jsx +15 -0
  318. package/src/widgets/__stories__/explanation.stories.jsx +19 -0
  319. package/src/widgets/__stories__/expression.stories.jsx +26 -0
  320. package/src/widgets/__stories__/graded-group-set.stories.jsx +32 -0
  321. package/src/widgets/__stories__/graded-group.stories.jsx +32 -0
  322. package/src/widgets/__stories__/grapher.stories.jsx +47 -0
  323. package/src/widgets/__stories__/group.stories.jsx +15 -0
  324. package/src/widgets/__stories__/iframe.stories.jsx +15 -0
  325. package/src/widgets/__stories__/image.stories.jsx +62 -0
  326. package/src/widgets/__stories__/input-number.stories.jsx +90 -0
  327. package/src/widgets/__stories__/interaction.stories.jsx +15 -0
  328. package/src/widgets/__stories__/interactive-graph.stories.jsx +61 -0
  329. package/src/widgets/__stories__/matcher.stories.jsx +15 -0
  330. package/src/widgets/__stories__/matrix.stories.jsx +15 -0
  331. package/src/widgets/__stories__/number-line.stories.jsx +15 -0
  332. package/src/widgets/__stories__/numeric-input.stories.jsx +19 -0
  333. package/src/widgets/__stories__/orderer.stories.jsx +15 -0
  334. package/src/widgets/__stories__/passage-ref.stories.jsx +19 -0
  335. package/src/widgets/__stories__/passage.stories.jsx +19 -0
  336. package/src/widgets/__stories__/radio.stories.jsx +82 -0
  337. package/src/widgets/__stories__/sorter.stories.jsx +15 -0
  338. package/src/widgets/__stories__/transformer.stories.jsx +15 -0
  339. package/src/widgets/__stories__/unit.stories.jsx +45 -0
  340. package/src/widgets/__stories__/video-transcript-link.stories.jsx +24 -0
  341. package/src/widgets/__stories__/video.stories.jsx +15 -0
  342. package/src/widgets/__testdata__/categorizer_testdata.js +50 -0
  343. package/src/widgets/__testdata__/cs-program_testdata.js +28 -0
  344. package/src/widgets/__testdata__/dropdown_testdata.js +34 -0
  345. package/src/widgets/__testdata__/explanation_testdata.js +56 -0
  346. package/src/widgets/__testdata__/expression_testdata.js +95 -0
  347. package/src/widgets/__testdata__/graded-group-set_testdata.js +358 -0
  348. package/src/widgets/__testdata__/graded-group_testdata.js +79 -0
  349. package/src/widgets/__testdata__/grapher_testdata.js +376 -0
  350. package/src/widgets/__testdata__/group_testdata.js +156 -0
  351. package/src/widgets/__testdata__/iframe_testdata.js +36 -0
  352. package/src/widgets/__testdata__/image_testdata.js +38 -0
  353. package/src/widgets/__testdata__/input-number_testdata.js +73 -0
  354. package/src/widgets/__testdata__/interaction_testdata.js +264 -0
  355. package/src/widgets/__testdata__/interactive-graph_testdata.js +646 -0
  356. package/src/widgets/__testdata__/matcher_testdata.js +34 -0
  357. package/src/widgets/__testdata__/matrix_testdata.js +29 -0
  358. package/src/widgets/__testdata__/number-line_testdata.js +31 -0
  359. package/src/widgets/__testdata__/numeric-input_testdata.js +266 -0
  360. package/src/widgets/__testdata__/orderer_testdata.js +58 -0
  361. package/src/widgets/__testdata__/passage-ref_testdata.js +41 -0
  362. package/src/widgets/__testdata__/passage_testdata.js +71 -0
  363. package/src/widgets/__testdata__/radio_testdata.js +210 -0
  364. package/src/widgets/__testdata__/sorter_testdata.js +20 -0
  365. package/src/widgets/__testdata__/transformer_testdata.js +109 -0
  366. package/src/widgets/__testdata__/video_testdata.js +22 -0
  367. package/src/widgets/__tests__/__snapshots__/categorizer_test.jsx.snap +928 -0
  368. package/src/widgets/__tests__/__snapshots__/cs-program_test.jsx.snap +75 -0
  369. package/src/widgets/__tests__/__snapshots__/definition_test.js.snap +99 -0
  370. package/src/widgets/__tests__/__snapshots__/dropdown_test.jsx.snap +143 -0
  371. package/src/widgets/__tests__/__snapshots__/explanation_test.js.snap +473 -0
  372. package/src/widgets/__tests__/__snapshots__/expression_test.jsx.snap +38 -0
  373. package/src/widgets/__tests__/__snapshots__/graded-group-set-jipt_test.jsx.snap +754 -0
  374. package/src/widgets/__tests__/__snapshots__/graded-group-set_test.jsx.snap +310 -0
  375. package/src/widgets/__tests__/__snapshots__/graded-group_test.jsx.snap +863 -0
  376. package/src/widgets/__tests__/__snapshots__/grapher_test.jsx.snap +2880 -0
  377. package/src/widgets/__tests__/__snapshots__/group_test.jsx.snap +1154 -0
  378. package/src/widgets/__tests__/__snapshots__/iframe_test.jsx.snap +69 -0
  379. package/src/widgets/__tests__/__snapshots__/image_test.jsx.snap +388 -0
  380. package/src/widgets/__tests__/__snapshots__/input-number_test.jsx.snap +978 -0
  381. package/src/widgets/__tests__/__snapshots__/interaction_test.jsx.snap +311 -0
  382. package/src/widgets/__tests__/__snapshots__/interactive-graph_test.jsx.snap +2915 -0
  383. package/src/widgets/__tests__/__snapshots__/matcher_test.jsx.snap +1039 -0
  384. package/src/widgets/__tests__/__snapshots__/matrix_test.jsx.snap +450 -0
  385. package/src/widgets/__tests__/__snapshots__/number-line_test.jsx.snap +888 -0
  386. package/src/widgets/__tests__/__snapshots__/numeric-input_test.jsx.snap +930 -0
  387. package/src/widgets/__tests__/__snapshots__/orderer_test.jsx.snap +275 -0
  388. package/src/widgets/__tests__/__snapshots__/passage-ref_test.jsx.snap +446 -0
  389. package/src/widgets/__tests__/__snapshots__/passage_test.jsx.snap +1988 -0
  390. package/src/widgets/__tests__/__snapshots__/radio_test.jsx.snap +7107 -0
  391. package/src/widgets/__tests__/__snapshots__/video_test.jsx.snap +163 -0
  392. package/src/widgets/__tests__/categorizer_test.jsx +205 -0
  393. package/src/widgets/__tests__/cs-program_test.jsx +45 -0
  394. package/src/widgets/__tests__/definition_test.js +152 -0
  395. package/src/widgets/__tests__/dropdown_test.jsx +106 -0
  396. package/src/widgets/__tests__/explanation_test.js +199 -0
  397. package/src/widgets/__tests__/expression_test.jsx +439 -0
  398. package/src/widgets/__tests__/graded-group-set-jipt_test.jsx +33 -0
  399. package/src/widgets/__tests__/graded-group-set_test.jsx +270 -0
  400. package/src/widgets/__tests__/graded-group_test.jsx +233 -0
  401. package/src/widgets/__tests__/grapher.cypress.jsx +486 -0
  402. package/src/widgets/__tests__/grapher_test.jsx +35 -0
  403. package/src/widgets/__tests__/group_test.jsx +512 -0
  404. package/src/widgets/__tests__/iframe_test.jsx +47 -0
  405. package/src/widgets/__tests__/image_test.jsx +65 -0
  406. package/src/widgets/__tests__/input-number_test.jsx +360 -0
  407. package/src/widgets/__tests__/interaction_test.jsx +33 -0
  408. package/src/widgets/__tests__/interactive-graph.cypress.jsx +697 -0
  409. package/src/widgets/__tests__/interactive-graph_test.jsx +105 -0
  410. package/src/widgets/__tests__/matcher_test.jsx +157 -0
  411. package/src/widgets/__tests__/matrix_test.jsx +84 -0
  412. package/src/widgets/__tests__/number-line_test.jsx +81 -0
  413. package/src/widgets/__tests__/numeric-input_test.jsx +574 -0
  414. package/src/widgets/__tests__/orderer_test.jsx +75 -0
  415. package/src/widgets/__tests__/passage-ref_test.jsx +166 -0
  416. package/src/widgets/__tests__/passage_test.jsx +133 -0
  417. package/src/widgets/__tests__/radio/base-radio_test.jsx +82 -0
  418. package/src/widgets/__tests__/radio_test.jsx +651 -0
  419. package/src/widgets/__tests__/renderQuestion.jsx +66 -0
  420. package/src/widgets/__tests__/unit_test.jsx +144 -0
  421. package/src/widgets/__tests__/video_test.jsx +55 -0
  422. package/src/widgets/categorizer.jsx +338 -0
  423. package/src/widgets/cs-program.jsx +227 -0
  424. package/src/widgets/definition.jsx +141 -0
  425. package/src/widgets/dropdown.jsx +143 -0
  426. package/src/widgets/explanation.jsx +315 -0
  427. package/src/widgets/expression.jsx +652 -0
  428. package/src/widgets/graded-group-answer-bar.jsx +152 -0
  429. package/src/widgets/graded-group-set.jsx +285 -0
  430. package/src/widgets/graded-group.jsx +448 -0
  431. package/src/widgets/grapher/util.jsx +806 -0
  432. package/src/widgets/grapher.jsx +663 -0
  433. package/src/widgets/group.jsx +213 -0
  434. package/src/widgets/iframe.jsx +198 -0
  435. package/src/widgets/image.jsx +310 -0
  436. package/src/widgets/input-number.jsx +411 -0
  437. package/src/widgets/interaction/arrow-picker.jsx +39 -0
  438. package/src/widgets/interaction/color-picker.jsx +69 -0
  439. package/src/widgets/interaction/dash-picker.jsx +46 -0
  440. package/src/widgets/interaction/element-container.jsx +97 -0
  441. package/src/widgets/interaction.jsx +742 -0
  442. package/src/widgets/interactive-graph.jsx +2820 -0
  443. package/src/widgets/label-image/__stories__/answer-choices.stories.jsx +93 -0
  444. package/src/widgets/label-image/__stories__/marker.stories.jsx +172 -0
  445. package/src/widgets/label-image/answer-choices.jsx +311 -0
  446. package/src/widgets/label-image/marker.jsx +365 -0
  447. package/src/widgets/label-image/types.js +33 -0
  448. package/src/widgets/label-image.jsx +841 -0
  449. package/src/widgets/label-image_test.js +840 -0
  450. package/src/widgets/lights-puzzle.jsx +356 -0
  451. package/src/widgets/matcher.jsx +271 -0
  452. package/src/widgets/matrix.jsx +616 -0
  453. package/src/widgets/measurer.jsx +215 -0
  454. package/src/widgets/molecule/__tests__/layout_test.jsx +159 -0
  455. package/src/widgets/molecule/__tests__/parser_test.jsx +142 -0
  456. package/src/widgets/molecule/molecule-drawing.jsx +175 -0
  457. package/src/widgets/molecule/molecule-layout.jsx +367 -0
  458. package/src/widgets/molecule/smiles-parser.jsx +300 -0
  459. package/src/widgets/molecule.jsx +172 -0
  460. package/src/widgets/number-line.jsx +858 -0
  461. package/src/widgets/numeric-input.jsx +622 -0
  462. package/src/widgets/orderer.jsx +753 -0
  463. package/src/widgets/passage/__tests__/passage-markdown_test.jsx +382 -0
  464. package/src/widgets/passage/get-line-height-for-node.js +10 -0
  465. package/src/widgets/passage/passage-markdown.jsx +416 -0
  466. package/src/widgets/passage-ref-target.jsx +70 -0
  467. package/src/widgets/passage-ref.jsx +198 -0
  468. package/src/widgets/passage.jsx +601 -0
  469. package/src/widgets/plotter.jsx +1198 -0
  470. package/src/widgets/radio/__stories__/base-radio.stories.jsx +40 -0
  471. package/src/widgets/radio/__stories__/choice-icon.stories.jsx +157 -0
  472. package/src/widgets/radio/__stories__/choice-none-above.stories.jsx +26 -0
  473. package/src/widgets/radio/__stories__/cross-out-button.stories.jsx +64 -0
  474. package/src/widgets/radio/__stories__/cross-out-menu.stories.jsx +59 -0
  475. package/src/widgets/radio/__stories__/cross-out-tooltip.stories.jsx +59 -0
  476. package/src/widgets/radio/__stories__/focus-ring.stories.jsx +38 -0
  477. package/src/widgets/radio/__stories__/option-status.stories.jsx +118 -0
  478. package/src/widgets/radio/__stories__/toggleable-radio-button.stories.jsx +28 -0
  479. package/src/widgets/radio/base-radio.jsx +642 -0
  480. package/src/widgets/radio/choice-icon.jsx +468 -0
  481. package/src/widgets/radio/choice-none-above.jsx +54 -0
  482. package/src/widgets/radio/choice.jsx +692 -0
  483. package/src/widgets/radio/choice.jsx.fixture.js +64 -0
  484. package/src/widgets/radio/cross-out-button.jsx +136 -0
  485. package/src/widgets/radio/cross-out-menu-wrapper.jsx +492 -0
  486. package/src/widgets/radio/cross-out-tooltip.jsx +129 -0
  487. package/src/widgets/radio/focus-ring.jsx +51 -0
  488. package/src/widgets/radio/option-status.jsx +89 -0
  489. package/src/widgets/radio/toggleable-radio-button.jsx +133 -0
  490. package/src/widgets/radio/util.js +32 -0
  491. package/src/widgets/radio/util_test.js +19 -0
  492. package/src/widgets/radio/widget.jsx +470 -0
  493. package/src/widgets/radio.jsx +156 -0
  494. package/src/widgets/reaction-diagram.jsx +137 -0
  495. package/src/widgets/sequence.jsx +140 -0
  496. package/src/widgets/simulator.jsx +808 -0
  497. package/src/widgets/sorter.jsx +146 -0
  498. package/src/widgets/table.jsx +394 -0
  499. package/src/widgets/transformer.jsx +2876 -0
  500. package/src/widgets/unit.jsx +372 -0
  501. package/src/widgets/video-transcript-link.jsx +84 -0
  502. package/src/widgets/video-transcript-link_test.js +211 -0
  503. package/src/widgets/video.jsx +126 -0
  504. package/src/widgets.js +431 -0
  505. package/src/zoom.js +539 -0
  506. package/README.md +0 -3
  507. package/src/__tests__/index.test.js +0 -14
@@ -0,0 +1,601 @@
1
+ // @flow
2
+
3
+ import * as i18n from "@khanacademy/wonder-blocks-i18n";
4
+ import {StyleSheet, css} from "aphrodite";
5
+ import $ from "jquery";
6
+ import * as React from "react";
7
+ import ReactDOM from "react-dom";
8
+ import _ from "underscore";
9
+
10
+ import HighlightableContent from "../components/highlighting/highlightable-content.jsx";
11
+ import {getDependencies} from "../dependencies.js";
12
+ import {linterContextDefault} from "../gorgon/proptypes.js";
13
+ import Renderer from "../renderer.jsx";
14
+
15
+ import {getLineHeightForNode} from "./passage/get-line-height-for-node.js";
16
+ import PassageMarkdown from "./passage/passage-markdown.jsx";
17
+
18
+ import type {SerializedHighlightSet} from "../components/highlighting/types.js";
19
+ import type {ChangeableProps} from "../mixins/changeable.jsx";
20
+ import type {PerseusPassageWidgetOptions} from "../perseus-types.js";
21
+ import type {PerseusScore, WidgetExports, WidgetProps} from "../types.js";
22
+
23
+ type JQueryCollection = $FlowFixMe;
24
+
25
+ // A fake paragraph to measure the line height of the passage. In CSS we always
26
+ // set the line height to 22 pixels, but when using the browser zoom feature,
27
+ // the line height often ends up being a fractional number of pixels close to
28
+ // 22 pixels.
29
+ class LineHeightMeasurer extends React.Component<{...}> {
30
+ _cachedLineHeight: number;
31
+ $body: JQueryCollection;
32
+ $end: JQueryCollection;
33
+
34
+ measureLineHeight(): number {
35
+ if (typeof this._cachedLineHeight !== "number") {
36
+ this.forceMeasureLineHeight();
37
+ }
38
+
39
+ return this._cachedLineHeight;
40
+ }
41
+
42
+ forceMeasureLineHeight() {
43
+ // Add some text which magically fills an entire line.
44
+ this.$body.text(" \u0080");
45
+
46
+ // Now, the line height is the difference between the top of the
47
+ // second line and the top of the first line.
48
+ this._cachedLineHeight = getLineHeightForNode(this.$body, this.$end);
49
+
50
+ // Clear out the first line so it doesn't overlap the passage.
51
+ this.$body.text("");
52
+ }
53
+
54
+ render(): React.Node {
55
+ return (
56
+ <div className={css(styles.measurer)}>
57
+ <div>
58
+ <div
59
+ ref={(e) => (this.$body = $(e))}
60
+ className="paragraph"
61
+ />
62
+ <div ref={(e) => (this.$end = $(e))} />
63
+ </div>
64
+ </div>
65
+ );
66
+ }
67
+ }
68
+
69
+ const styles = StyleSheet.create({
70
+ measurer: {
71
+ position: "absolute",
72
+ width: "100%",
73
+ top: 0,
74
+ left: 0,
75
+ },
76
+ });
77
+
78
+ type UserInput = null;
79
+
80
+ type Rubric = PerseusPassageWidgetOptions;
81
+
82
+ // The result of the `transform` function (end of this file)
83
+ type RenderProps = {|
84
+ passageTitle: PerseusPassageWidgetOptions["passageTitle"],
85
+ passageText: PerseusPassageWidgetOptions["passageText"],
86
+ footnotes: PerseusPassageWidgetOptions["footnotes"],
87
+ showLineNumbers: PerseusPassageWidgetOptions["showLineNumbers"],
88
+ |};
89
+
90
+ type PassageProps = {|
91
+ ...ChangeableProps,
92
+ ...WidgetProps<RenderProps, Rubric>,
93
+ highlights: SerializedHighlightSet,
94
+ |};
95
+
96
+ type DefaultPassageProps = {|
97
+ passageTitle: PassageProps["passageTitle"],
98
+ passageText: PassageProps["passageText"],
99
+ footnotes: PassageProps["footnotes"],
100
+ showLineNumbers: PassageProps["showLineNumbers"],
101
+ highlights: PassageProps["highlights"],
102
+ linterContext: PassageProps["linterContext"],
103
+ |};
104
+
105
+ type PassageState = {|
106
+ nLines: ?number,
107
+ startLineNumbersAfter: number,
108
+ stylesAreApplied: boolean,
109
+ |};
110
+
111
+ // State kept track of by the PassageMarkdown parser.
112
+ type PassageParseState = {
113
+ firstQuestionRef: ?any,
114
+ firstSentenceRef: ?any,
115
+ ...
116
+ };
117
+
118
+ // Information about a passage reference, used in inter-widgets.
119
+ type Reference = {
120
+ startLine: number,
121
+ endLine: number,
122
+ content: ?string,
123
+ ...
124
+ };
125
+
126
+ class Passage extends React.Component<PassageProps, PassageState> {
127
+ _onResize: () => {...};
128
+ _lineHeightMeasurer: LineHeightMeasurer;
129
+ _stylesAppiedTimer: TimeoutID;
130
+
131
+ static defaultProps: DefaultPassageProps = {
132
+ passageTitle: "",
133
+ passageText: "",
134
+ footnotes: "",
135
+ showLineNumbers: true,
136
+ highlights: {},
137
+ linterContext: linterContextDefault,
138
+ };
139
+
140
+ state: PassageState = {
141
+ nLines: null,
142
+ startLineNumbersAfter: 0,
143
+ stylesAreApplied: false,
144
+ };
145
+
146
+ componentDidMount() {
147
+ this._updateState();
148
+
149
+ this._onResize = _.throttle(() => {
150
+ // If we're rendering JIPT text, we won't have line numbers or a
151
+ // line height measurer, so skip handling this resize.
152
+ if (this.shouldRenderJipt()) {
153
+ return;
154
+ }
155
+
156
+ // Remeasure the line height on resize, because the only line
157
+ // height changes we expect are subpixel changes when the user
158
+ // zooms in/out, and the only way to listen for zoom events is to
159
+ // listen for resize events.
160
+ this._lineHeightMeasurer.forceMeasureLineHeight();
161
+ this._updateState();
162
+ }, 500);
163
+ window.addEventListener("resize", this._onResize);
164
+
165
+ // Wait for Aphrodite styles (which are guaranteed to apply after one
166
+ // tick), then set state.
167
+ //
168
+ // This flag is used to set the `enabled` prop of the
169
+ // `HighlightableContent` component. That way, we only show highlights
170
+ // once styles are ready, and they're measured with the correct
171
+ // position.
172
+ //
173
+ // HACK(mdr): It's not really the Passage's Aphrodite styles that are
174
+ // causing bad measures, but more so the Khan Academy Test Prep
175
+ // app's Aphrodite styles. We would ideally instead offer the
176
+ // embedding application an API to signal that the app's layout has
177
+ // changed in a way that affects the Renderer... but, for now, just
178
+ // hardcode this hack into here.
179
+ // TODO(somewhatabstract): Use WB timing
180
+ // eslint-disable-next-line no-restricted-syntax
181
+ this._stylesAppiedTimer = window.setTimeout(() => {
182
+ this.setState({stylesAreApplied: true});
183
+ }, 0);
184
+ }
185
+
186
+ shouldComponentUpdate(
187
+ nextProps: PassageProps,
188
+ nextState: PassageState,
189
+ ): any | boolean {
190
+ return (
191
+ !_.isEqual(this.props, nextProps) ||
192
+ !_.isEqual(this.state, nextState)
193
+ );
194
+ }
195
+
196
+ componentDidUpdate() {
197
+ this._updateState();
198
+ }
199
+
200
+ componentWillUnmount() {
201
+ window.removeEventListener("resize", this._onResize);
202
+ // eslint-disable-next-line no-restricted-syntax
203
+ clearTimeout(this._stylesAppiedTimer);
204
+ }
205
+
206
+ _handleSerializedHighlightsUpdate: (
207
+ serializedHighlights: SerializedHighlightSet,
208
+ ) => void = (serializedHighlights: SerializedHighlightSet) => {
209
+ this.props.onChange({highlights: serializedHighlights});
210
+ };
211
+
212
+ /**
213
+ * Line numbering
214
+ *
215
+ * These functions support passage line numbering, which is non-trivial
216
+ * because we manually measure lines to support resizing as well as
217
+ * continuing line numbers from previous passages.
218
+ */
219
+
220
+ _updateState() {
221
+ // If we're rendering JIPT text, we're not rendering line numbers so we
222
+ // don't need to update this state.
223
+ if (this.shouldRenderJipt()) {
224
+ return;
225
+ }
226
+
227
+ this.setState({
228
+ nLines: this._measureLines(),
229
+ startLineNumbersAfter: this._getInitialLineNumber(),
230
+ });
231
+ }
232
+
233
+ _measureLines(): number {
234
+ // eslint-disable-next-line react/no-string-refs
235
+ const $renderer = $(ReactDOM.findDOMNode(this.refs.content));
236
+ const contentsHeight = $renderer.height();
237
+ const lineHeight = this._getLineHeight();
238
+ const nLines = Math.round(contentsHeight / lineHeight);
239
+ return nLines;
240
+ }
241
+
242
+ _getInitialLineNumber(): number {
243
+ let isPassageBeforeThisPassage = true;
244
+ const passagesBeforeUs = this.props.findWidgets((id, widgetInfo) => {
245
+ if (widgetInfo.type !== "passage") {
246
+ return false;
247
+ }
248
+ if (id === this.props.widgetId) {
249
+ isPassageBeforeThisPassage = false;
250
+ }
251
+ return isPassageBeforeThisPassage;
252
+ });
253
+
254
+ return passagesBeforeUs
255
+ .map((passageWidget) => {
256
+ return passageWidget.getLineCount();
257
+ })
258
+ .reduce((a, b) => a + b, 0);
259
+ }
260
+
261
+ _getLineHeight(): number {
262
+ return this._lineHeightMeasurer.measureLineHeight();
263
+ }
264
+
265
+ getLineCount(): number {
266
+ if (this.state.nLines != null) {
267
+ return this.state.nLines;
268
+ }
269
+ return this._measureLines();
270
+ }
271
+
272
+ /**
273
+ * Passage refs
274
+ *
275
+ * These are functions to support the passage refs inter-widgets feature
276
+ * where other widgets can fetch the line numbers of a reference inside of
277
+ * a passage.
278
+ */
279
+
280
+ _getStartRefLineNumber(referenceNumber: number): ?number {
281
+ const refRef = PassageMarkdown.START_REF_PREFIX + referenceNumber;
282
+ // eslint-disable-next-line react/no-string-refs
283
+ const ref = this.refs[refRef];
284
+ if (!ref) {
285
+ return null;
286
+ }
287
+
288
+ const $ref = $(ReactDOM.findDOMNode(ref));
289
+ // We really care about the first text after the ref, not the
290
+ // ref element itself:
291
+ let $refText = $ref.next();
292
+ if ($refText.length === 0) {
293
+ // But if there are no elements after the ref, just
294
+ // use the ref itself.
295
+ $refText = $ref;
296
+ }
297
+ const vPos = $refText.offset().top;
298
+
299
+ return (
300
+ this.state.startLineNumbersAfter +
301
+ 1 +
302
+ this._convertPosToLineNumber(vPos)
303
+ );
304
+ }
305
+
306
+ _getEndRefLineNumber(referenceNumber: number): ?number {
307
+ const refRef = PassageMarkdown.END_REF_PREFIX + referenceNumber;
308
+ // eslint-disable-next-line react/no-string-refs
309
+ const ref = this.refs[refRef];
310
+ if (!ref) {
311
+ return null;
312
+ }
313
+
314
+ const $ref = $(ReactDOM.findDOMNode(ref));
315
+ // We really care about the last text before the ref, not the
316
+ // ref element itself:
317
+ let $refText = $ref.prev();
318
+ if ($refText.length === 0) {
319
+ // But if there are no elements before the ref, just
320
+ // use the ref itself.
321
+ $refText = $ref;
322
+ }
323
+ const height = $refText.height();
324
+ const vPos = $refText.offset().top;
325
+
326
+ let line = this._convertPosToLineNumber(vPos + height);
327
+ if (height === 0) {
328
+ // If the element before the end ref span was the start
329
+ // ref span, it might have 0 height. This is obviously not
330
+ // the intended use case, but we should handle it gracefully.
331
+ // If this is the case, then the "bottom" of our element is
332
+ // actually the top of the line we're on, so we need to add
333
+ // one to the line number.
334
+ line += 1;
335
+ }
336
+
337
+ return this.state.startLineNumbersAfter + line;
338
+ }
339
+
340
+ _convertPosToLineNumber(absoluteVPos: number): number {
341
+ // eslint-disable-next-line react/no-string-refs
342
+ const $content = $(ReactDOM.findDOMNode(this.refs.content));
343
+ const relativeVPos = absoluteVPos - $content.offset().top;
344
+ const lineHeight = this._getLineHeight();
345
+
346
+ const line = Math.round(relativeVPos / lineHeight);
347
+ return line;
348
+ }
349
+
350
+ _getRefContent(referenceNumber: number): ?string {
351
+ const refRef = PassageMarkdown.START_REF_PREFIX + referenceNumber;
352
+ // eslint-disable-next-line react/no-string-refs
353
+ const ref = this.refs[refRef];
354
+ if (!ref) {
355
+ return null;
356
+ }
357
+ return ref.getRefContent();
358
+ }
359
+
360
+ getReference(referenceNumber: number): ?Reference {
361
+ const refStartLine = this._getStartRefLineNumber(referenceNumber);
362
+ const refEndLine = this._getEndRefLineNumber(referenceNumber);
363
+ if (refStartLine == null || refEndLine == null) {
364
+ return null;
365
+ }
366
+ const refContent = this._getRefContent(referenceNumber);
367
+
368
+ return {
369
+ startLine: refStartLine,
370
+ endLine: refEndLine,
371
+ content: refContent,
372
+ };
373
+ }
374
+
375
+ /**
376
+ * Misc functions
377
+ *
378
+ * These are misc widget functions used for the widget API
379
+ */
380
+
381
+ getUserInput(): UserInput {
382
+ return null;
383
+ }
384
+
385
+ simpleValidate(rubric: Rubric): PerseusScore {
386
+ return Passage.validate(this.getUserInput(), rubric);
387
+ }
388
+
389
+ /* eslint-disable react/sort-comp */
390
+ static validate(state: UserInput, rubric: Rubric): PerseusScore {
391
+ return {
392
+ type: "points",
393
+ earned: 0,
394
+ total: 0,
395
+ message: null,
396
+ };
397
+ }
398
+ /* eslint-enable react/sort-comp */
399
+
400
+ /**
401
+ * Rendering
402
+ *
403
+ * Functions to render the passage widget.
404
+ */
405
+
406
+ _renderInstructions(parseState: PassageParseState): React.Element<any> {
407
+ const firstQuestionNumber = parseState.firstQuestionRef;
408
+ const firstSentenceRef = parseState.firstSentenceRef;
409
+
410
+ let instructions = "";
411
+ if (firstQuestionNumber) {
412
+ instructions += i18n._(
413
+ "The symbol %(questionSymbol)s indicates that question " +
414
+ "%(questionNumber)s references this portion of the " +
415
+ "passage.",
416
+ {
417
+ questionSymbol: "[[" + firstQuestionNumber + "]]",
418
+ questionNumber: firstQuestionNumber,
419
+ },
420
+ );
421
+ }
422
+ if (firstSentenceRef) {
423
+ instructions += i18n._(
424
+ " The symbol %(sentenceSymbol)s indicates that the " +
425
+ "following sentence is referenced in a question.",
426
+ {
427
+ sentenceSymbol: "[" + firstSentenceRef + "]",
428
+ },
429
+ );
430
+ }
431
+ const parsedInstructions = PassageMarkdown.parse(instructions);
432
+ return (
433
+ <div className="perseus-widget-passage-instructions">
434
+ {PassageMarkdown.output(parsedInstructions)}
435
+ </div>
436
+ );
437
+ }
438
+
439
+ shouldRenderJipt(): boolean {
440
+ // Mostly copied from `renderer.jsx`. If we're doing JIPT, we want to
441
+ // render our content differently.
442
+ const {JIPT} = getDependencies();
443
+ return JIPT.useJIPT && this.props.passageText.indexOf("crwdns") !== -1;
444
+ }
445
+
446
+ _renderContent(parsed: $ReadOnlyArray<any>): React.Element<any> {
447
+ // Wait until Aphrodite styles are applied before enabling highlights,
448
+ // so that we measure the correct positions.
449
+ const enabled = this.state.stylesAreApplied;
450
+
451
+ // Highlights are read-only in review mode.
452
+ const editable = !this.props.reviewModeRubric;
453
+ return (
454
+ <HighlightableContent
455
+ editable={editable}
456
+ enabled={enabled}
457
+ onSerializedHighlightsUpdate={
458
+ this._handleSerializedHighlightsUpdate
459
+ }
460
+ serializedHighlights={this.props.highlights}
461
+ >
462
+ {/* eslint-disable-next-line react/no-string-refs */}
463
+ <div ref="content">
464
+ <LineHeightMeasurer
465
+ // TODO(mdr): We found a new Flow error when upgrading:
466
+ // "e (null) This type is incompatible with this._lineHeightMeasurer (LineHeightMeasurer)"
467
+ // $FlowFixMe[incompatible-type](0.52.0->0.53.0)
468
+ ref={(e) => (this._lineHeightMeasurer = e)}
469
+ />
470
+ {PassageMarkdown.output(parsed)}
471
+ </div>
472
+ </HighlightableContent>
473
+ );
474
+ }
475
+
476
+ _hasFootnotes(): boolean {
477
+ const rawContent = this.props.footnotes;
478
+ const isEmpty = /^\s*$/.test(rawContent);
479
+ return !isEmpty;
480
+ }
481
+
482
+ _renderFootnotes(): React.Element<any> {
483
+ const rawContent = this.props.footnotes;
484
+ const parsed = PassageMarkdown.parse(rawContent);
485
+ return PassageMarkdown.output(parsed);
486
+ }
487
+
488
+ render(): React.Element<"div"> {
489
+ let lineNumbers;
490
+ const nLines = this.state.nLines;
491
+ if (this.props.showLineNumbers && nLines) {
492
+ // lineN is the line number in the current passage
493
+ // lineAt is the actual line number across multiple passages
494
+ lineNumbers = _.range(1, nLines + 1).map((lineN) => {
495
+ const lineAt = lineN + this.state.startLineNumbersAfter;
496
+ if (lineAt === 4) {
497
+ // we leave a space in i18n to disambiguate translations, see TP-5392
498
+ // I18N: a label next to a reading passage to denote the line number
499
+ const translatedLine = i18n._("Line ");
500
+ return (
501
+ <span key="line-marker" className="line-marker">
502
+ {translatedLine}
503
+ </span>
504
+ );
505
+ }
506
+ return <span key={lineN}>{lineAt}</span>;
507
+ });
508
+ }
509
+
510
+ const parseState: PassageParseState = {
511
+ firstSentenceRef: null,
512
+ firstQuestionRef: null,
513
+ };
514
+
515
+ // Replace the vertical double quote characters quoting text with
516
+ // an unicode left and right double quote characters. This would
517
+ // avoid rendering right or left unicode double quotes on both
518
+ // ends of a sentence wrapped by double quotes.
519
+ const re = /("{1})([^"]*)("{1})/g;
520
+ const doubleQuoteParsedContent = this.props.passageText.replace(
521
+ re,
522
+ "\u201c$2\u201d",
523
+ );
524
+ const parsedContent: $ReadOnlyArray<*> = PassageMarkdown.parse(
525
+ doubleQuoteParsedContent,
526
+ parseState,
527
+ );
528
+ // Check if the title has any non-empty text in it.
529
+ const hasTitle = /\S/.test(this.props.passageTitle);
530
+
531
+ return (
532
+ <div>
533
+ <div className="perseus-widget-passage-container">
534
+ {this._renderInstructions(parseState)}
535
+ <div className="perseus-widget-passage">
536
+ {hasTitle && (
537
+ <h3 className="passage-title">
538
+ <Renderer
539
+ content={this.props.passageTitle}
540
+ linterContext={this.props.linterContext}
541
+ />
542
+ </h3>
543
+ )}
544
+ {lineNumbers && (
545
+ <div className="line-numbers" aria-hidden={true}>
546
+ {lineNumbers}
547
+ </div>
548
+ )}
549
+ {!hasTitle && (
550
+ <h3 className="perseus-sr-only">
551
+ {i18n._("Beginning of reading passage.")}
552
+ </h3>
553
+ )}
554
+ <div className="passage-text">
555
+ {this.shouldRenderJipt() ? (
556
+ // If we're in JIPT mode, just pass off our
557
+ // content to a <Renderer /> which knows how
558
+ // to handle rendering JIPT text.
559
+ <Renderer content={this.props.passageText} />
560
+ ) : (
561
+ this._renderContent(parsedContent)
562
+ )}
563
+ </div>
564
+ {this._hasFootnotes() && [
565
+ <h4
566
+ key="footnote-start"
567
+ className="perseus-sr-only"
568
+ >
569
+ {i18n._(
570
+ "Beginning of reading passage footnotes.",
571
+ )}
572
+ </h4>,
573
+ <div key="footnotes" className="footnotes">
574
+ {this._renderFootnotes()}
575
+ </div>,
576
+ ]}
577
+ <div className="perseus-sr-only">
578
+ {i18n._("End of reading passage.")}
579
+ </div>
580
+ </div>
581
+ </div>
582
+ </div>
583
+ );
584
+ }
585
+ }
586
+
587
+ export default ({
588
+ name: "passage",
589
+ displayName: "Passage (SAT only)",
590
+ widget: Passage,
591
+ transform: (editorProps: any): RenderProps => {
592
+ return _.pick(
593
+ editorProps,
594
+ "passageTitle",
595
+ "passageText",
596
+ "footnotes",
597
+ "showLineNumbers",
598
+ );
599
+ },
600
+ isLintable: true,
601
+ }: WidgetExports<typeof Passage>);