@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.
- package/.babelrc.js +1 -1
- package/CHANGELOG.md +29 -0
- package/dist/es/index.css +4361 -0
- package/dist/es/index.js +7 -1
- package/dist/es/index.js.map +1 -1
- package/dist/index.css +4361 -0
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/strings.js +148 -46
- package/package.json +50 -4
- package/src/README.md +35 -0
- package/src/__stories__/item-renderer.stories.jsx +27 -0
- package/src/__stories__/server-item-renderer.stories.jsx +27 -0
- package/src/__testdata__/item-renderer_testdata.js +161 -0
- package/src/__testdata__/renderer_testdata.js +123 -0
- package/src/__tests__/__snapshots__/item-renderer_test.jsx.snap +233 -0
- package/src/__tests__/__snapshots__/perseus-markdown_test.jsx.snap +516 -0
- package/src/__tests__/__snapshots__/pure-markdown_test.js.snap +113 -0
- package/src/__tests__/__snapshots__/renderer_test.jsx.snap +576 -0
- package/src/__tests__/__snapshots__/server-item-renderer_test.jsx.snap +306 -0
- package/src/__tests__/a11y_test.jsx +304 -0
- package/src/__tests__/item-renderer_test.jsx +892 -0
- package/src/__tests__/mock-asset-loading-widget.jsx +57 -0
- package/src/__tests__/mock-widget.jsx +35 -0
- package/src/__tests__/perseus-markdown_test.jsx +747 -0
- package/src/__tests__/pure-markdown_test.js +552 -0
- package/src/__tests__/renderability_test.jsx +481 -0
- package/src/__tests__/renderer-api_test.jsx +333 -0
- package/src/__tests__/renderer_test.jsx +1792 -0
- package/src/__tests__/server-item-renderer_test.jsx +516 -0
- package/src/__tests__/test-items/image-item.js +25 -0
- package/src/__tests__/test-items/input-number-1-item.js +27 -0
- package/src/__tests__/test-items/input-number-2-item.js +39 -0
- package/src/__tests__/test-items/table-item.js +30 -0
- package/src/__tests__/tex-wrangler_test.js +83 -0
- package/src/__tests__/util_test.js +41 -0
- package/src/a11y.js +46 -0
- package/src/all-widgets.js +9 -0
- package/src/article-renderer.jsx +215 -0
- package/src/asset-context.js +30 -0
- package/src/basic-widgets.js +13 -0
- package/src/components/__stories__/button-group.stories.jsx +61 -0
- package/src/components/__stories__/fixed-to-responsive.stories.jsx +132 -0
- package/src/components/__stories__/graph.stories.jsx +27 -0
- package/src/components/__stories__/graphie.stories.jsx +28 -0
- package/src/components/__stories__/hud.stories.jsx +36 -0
- package/src/components/__stories__/icon.stories.jsx +56 -0
- package/src/components/__stories__/image-loader.stories.jsx +60 -0
- package/src/components/__stories__/info-tip.stories.jsx +33 -0
- package/src/components/__stories__/inline-icon.stories.jsx +39 -0
- package/src/components/__stories__/input-with-examples.stories.jsx +51 -0
- package/src/components/__stories__/lint.stories.jsx +40 -0
- package/src/components/__stories__/math-input.stories.jsx +29 -0
- package/src/components/__stories__/math-output.stories.jsx +24 -0
- package/src/components/__stories__/multi-button-group.stories.jsx +69 -0
- package/src/components/__stories__/number-input.stories.jsx +42 -0
- package/src/components/__stories__/possible-answers.stories.jsx +22 -0
- package/src/components/__stories__/prop-check-box.stories.jsx +62 -0
- package/src/components/__stories__/range-input.stories.jsx +28 -0
- package/src/components/__stories__/simple-keypad-input.stories.jsx +28 -0
- package/src/components/__stories__/sortable.stories.jsx +73 -0
- package/src/components/__stories__/stub-tag-editor.stories.jsx +44 -0
- package/src/components/__stories__/svg-image.stories.jsx +60 -0
- package/src/components/__stories__/tex.stories.jsx +18 -0
- package/src/components/__stories__/text-buttons.stories.jsx +51 -0
- package/src/components/__stories__/text-input.stories.jsx +34 -0
- package/src/components/__stories__/text-list-editor.stories.jsx +33 -0
- package/src/components/__stories__/zoomable-tex.stories.jsx +39 -0
- package/src/components/__stories__/zoomable.stories.jsx +40 -0
- package/src/components/__testdata__/sorter_testdata.js +20 -0
- package/src/components/__tests__/__snapshots__/sortable_test.jsx.snap +79 -0
- package/src/components/__tests__/__snapshots__/sorter_test.jsx.snap +297 -0
- package/src/components/__tests__/number-input_test.jsx +112 -0
- package/src/components/__tests__/sortable_test.jsx +234 -0
- package/src/components/__tests__/sorter_test.jsx +111 -0
- package/src/components/__tests__/zoomable_test.jsx +388 -0
- package/src/components/button-group.jsx +131 -0
- package/src/components/constants.js +15 -0
- package/src/components/fixed-to-responsive.jsx +164 -0
- package/src/components/graph.jsx +415 -0
- package/src/components/graphie-classes.jsx +148 -0
- package/src/components/graphie-movables.jsx +342 -0
- package/src/components/graphie.jsx +353 -0
- package/src/components/highlighting/highlightable-content.jsx +245 -0
- package/src/components/highlighting/highlights.js +208 -0
- package/src/components/highlighting/ranges.js +299 -0
- package/src/components/highlighting/types.js +73 -0
- package/src/components/highlighting/ui/highlight-renderer.jsx +179 -0
- package/src/components/highlighting/ui/highlight-set-renderer.jsx +190 -0
- package/src/components/highlighting/ui/highlight-tooltip.jsx +153 -0
- package/src/components/highlighting/ui/highlighting-ui.jsx +163 -0
- package/src/components/highlighting/ui/selection-tracker.jsx +195 -0
- package/src/components/highlighting/ui/types.js +21 -0
- package/src/components/highlighting/ui/util.js +166 -0
- package/src/components/highlighting/word-indexer.jsx +155 -0
- package/src/components/hud.jsx +179 -0
- package/src/components/icon.jsx +149 -0
- package/src/components/image-loader.jsx +175 -0
- package/src/components/info-tip/info-tip.jsx +146 -0
- package/src/components/info-tip.jsx +38 -0
- package/src/components/inline-icon.jsx +81 -0
- package/src/components/input-with-examples.jsx +231 -0
- package/src/components/lint.jsx +454 -0
- package/src/components/math-input.jsx +282 -0
- package/src/components/math-output.jsx +128 -0
- package/src/components/multi-button-group.jsx +137 -0
- package/src/components/new-tooltip/README.md +110 -0
- package/src/components/new-tooltip/__stories__/flexible-position.stories.jsx +437 -0
- package/src/components/new-tooltip/__stories__/new-tooltip.stories.jsx +665 -0
- package/src/components/new-tooltip/__stories__/tooltip-arrow.stories.jsx +70 -0
- package/src/components/new-tooltip/__stories__/tooltip-bubble.stories.jsx +98 -0
- package/src/components/new-tooltip/__tests__/style-constants_test.js +8 -0
- package/src/components/new-tooltip/__tests__/util_test.js +47 -0
- package/src/components/new-tooltip/flexible-position.jsx +182 -0
- package/src/components/new-tooltip/new-tooltip.jsx +399 -0
- package/src/components/new-tooltip/style-constants.js +13 -0
- package/src/components/new-tooltip/tooltip-arrow.jsx +184 -0
- package/src/components/new-tooltip/tooltip-bubble.jsx +223 -0
- package/src/components/new-tooltip/tooltip-portal.jsx +101 -0
- package/src/components/new-tooltip/tooltip-positioner.jsx +529 -0
- package/src/components/new-tooltip/types.js +183 -0
- package/src/components/new-tooltip/util.js +27 -0
- package/src/components/number-input.jsx +253 -0
- package/src/components/possible-answers.jsx +36 -0
- package/src/components/prop-check-box.jsx +80 -0
- package/src/components/range-input.jsx +61 -0
- package/src/components/simple-keypad-input.jsx +91 -0
- package/src/components/sortable.jsx +873 -0
- package/src/components/stub-tag-editor.jsx +54 -0
- package/src/components/svg-image.jsx +762 -0
- package/src/components/tex-buttons.jsx +322 -0
- package/src/components/tex.jsx +70 -0
- package/src/components/text-input.jsx +82 -0
- package/src/components/text-list-editor.jsx +157 -0
- package/src/components/tooltip.jsx +395 -0
- package/src/components/visibility-observer/__stories__/visibility-observer.stories.jsx +140 -0
- package/src/components/visibility-observer/get-scroll-parent.js +45 -0
- package/src/components/visibility-observer/visibility-observer.js +109 -0
- package/src/components/visibility-observer/visibility-observer.jsx +123 -0
- package/src/components/zoomable-tex.jsx +70 -0
- package/src/components/zoomable.jsx +304 -0
- package/src/components.js +17 -0
- package/src/dependencies.js +22 -0
- package/src/error-boundary.jsx +48 -0
- package/src/extra-widgets.js +75 -0
- package/src/gorgon/README +40 -0
- package/src/gorgon/__tests__/matcher_test.js +497 -0
- package/src/gorgon/__tests__/rule_test.js +101 -0
- package/src/gorgon/__tests__/rules_test.js +487 -0
- package/src/gorgon/__tests__/selector-parser_test.js +52 -0
- package/src/gorgon/__tests__/tree-transformer_test.js +446 -0
- package/src/gorgon/gorgon.js +290 -0
- package/src/gorgon/proptypes.js +29 -0
- package/src/gorgon/rule.js +413 -0
- package/src/gorgon/rules/absolute-url.js +24 -0
- package/src/gorgon/rules/all-rules.js +72 -0
- package/src/gorgon/rules/blockquoted-math.js +10 -0
- package/src/gorgon/rules/blockquoted-widget.js +10 -0
- package/src/gorgon/rules/double-spacing-after-terminal.js +12 -0
- package/src/gorgon/rules/extra-content-spacing.js +12 -0
- package/src/gorgon/rules/heading-level-1.js +14 -0
- package/src/gorgon/rules/heading-level-skip.js +20 -0
- package/src/gorgon/rules/heading-sentence-case.js +11 -0
- package/src/gorgon/rules/heading-title-case.js +63 -0
- package/src/gorgon/rules/image-alt-text.js +21 -0
- package/src/gorgon/rules/image-in-table.js +10 -0
- package/src/gorgon/rules/image-spaces-around-urls.js +35 -0
- package/src/gorgon/rules/image-widget.js +50 -0
- package/src/gorgon/rules/link-click-here.js +11 -0
- package/src/gorgon/rules/lint-utils.js +48 -0
- package/src/gorgon/rules/long-paragraph.js +14 -0
- package/src/gorgon/rules/math-adjacent.js +10 -0
- package/src/gorgon/rules/math-align-extra-break.js +11 -0
- package/src/gorgon/rules/math-align-linebreaks.js +43 -0
- package/src/gorgon/rules/math-empty.js +10 -0
- package/src/gorgon/rules/math-font-size.js +12 -0
- package/src/gorgon/rules/math-frac.js +10 -0
- package/src/gorgon/rules/math-nested.js +11 -0
- package/src/gorgon/rules/math-starts-with-space.js +12 -0
- package/src/gorgon/rules/math-text-empty.js +10 -0
- package/src/gorgon/rules/math-without-dollars.js +14 -0
- package/src/gorgon/rules/nested-lists.js +11 -0
- package/src/gorgon/rules/profanity.js +10 -0
- package/src/gorgon/rules/table-missing-cells.js +20 -0
- package/src/gorgon/rules/unbalanced-code-delimiters.js +14 -0
- package/src/gorgon/rules/unescaped-dollar.js +10 -0
- package/src/gorgon/rules/widget-in-table.js +10 -0
- package/src/gorgon/selector.js +506 -0
- package/src/gorgon/tree-transformer.js +588 -0
- package/src/hint-renderer.jsx +159 -0
- package/src/hints-renderer.jsx +359 -0
- package/src/icon-paths.js +120 -0
- package/src/index.js +149 -2
- package/src/init.js +49 -0
- package/src/interaction-tracker.js +60 -0
- package/src/interactive2/__tests__/movable-mock.js +48 -0
- package/src/interactive2/__tests__/movable-point_test.js +146 -0
- package/src/interactive2/interactive-util.js +152 -0
- package/src/interactive2/movable-helper-methods.js +160 -0
- package/src/interactive2/movable-line-options.js +392 -0
- package/src/interactive2/movable-line.js +322 -0
- package/src/interactive2/movable-point-options.js +190 -0
- package/src/interactive2/movable-point.jsx +603 -0
- package/src/interactive2/movable-polygon-options.js +351 -0
- package/src/interactive2/movable-polygon.js +368 -0
- package/src/interactive2/movable.js +273 -0
- package/src/interactive2/objective_.js +79 -0
- package/src/interactive2/types.js +3 -0
- package/src/interactive2/wrapped-defaults.js +86 -0
- package/src/interactive2/wrapped-ellipse.js +87 -0
- package/src/interactive2/wrapped-line.js +109 -0
- package/src/interactive2/wrapped-path.js +47 -0
- package/src/interactive2.js +91 -0
- package/src/item-renderer.jsx +518 -0
- package/src/item-version.js +12 -0
- package/src/jipt-paragraphs.js +44 -0
- package/src/jquery.mobile.vmouse.js +545 -0
- package/src/keypad-context.js +33 -0
- package/src/loading-context.js +20 -0
- package/src/logging/log.js +107 -0
- package/src/logging/log_test.js +41 -0
- package/src/mixins/changeable.jsx +115 -0
- package/src/mixins/editor-jsonify.jsx +13 -0
- package/src/mixins/provide-keypad.jsx +129 -0
- package/src/mixins/widget-jsonify-deprecated.jsx +23 -0
- package/src/mixins/widget-prop-blacklist.js +36 -0
- package/src/multi-items/__stories__/multi-renderer.stories.js +88 -0
- package/src/multi-items/__testdata__/multi-renderer_testdata.js +174 -0
- package/src/multi-items/__tests__/__snapshots__/multi-renderer_test.js.snap +994 -0
- package/src/multi-items/__tests__/items_test.js +245 -0
- package/src/multi-items/__tests__/multi-renderer_test.js +903 -0
- package/src/multi-items/__tests__/prop-type-builders_test.js +134 -0
- package/src/multi-items/__tests__/shapes_test.js +44 -0
- package/src/multi-items/__tests__/trees_test.js +316 -0
- package/src/multi-items/item-types.js +55 -0
- package/src/multi-items/items.js +176 -0
- package/src/multi-items/multi-renderer.jsx +543 -0
- package/src/multi-items/prop-type-builders.js +105 -0
- package/src/multi-items/shape-types.js +47 -0
- package/src/multi-items/shapes.js +53 -0
- package/src/multi-items/tree-types.js +42 -0
- package/src/multi-items/trees.js +345 -0
- package/src/multi-items.js +76 -0
- package/src/not-gorgon.js +129 -0
- package/src/perseus-api.jsx +270 -0
- package/src/perseus-env.js +26 -0
- package/src/perseus-error.js +19 -0
- package/src/perseus-markdown.jsx +433 -0
- package/src/perseus-types.js +1597 -0
- package/src/pure-markdown.js +347 -0
- package/src/question-paragraph.jsx +30 -0
- package/src/renderability.jsx +108 -0
- package/src/renderer.jsx +1932 -0
- package/src/server-item-renderer.jsx +467 -0
- package/src/sigfigs.jsx +492 -0
- package/src/styles/articles.less +748 -0
- package/src/styles/constants.js +119 -0
- package/src/styles/global-constants.js +350 -0
- package/src/styles/global-styles.js +307 -0
- package/src/styles/khan-exercise.css +1304 -0
- package/src/styles/media-queries.js +48 -0
- package/src/styles/perseus-renderer.less +1007 -0
- package/src/styles/shared.js +132 -0
- package/src/styles/util.less +47 -0
- package/src/styles/variables.boostrap.less +301 -0
- package/src/styles/variables.less +68 -0
- package/src/styles/variables.shared.less +356 -0
- package/src/styles/widgets/categorizer.less +39 -0
- package/src/styles/widgets/definition.less +86 -0
- package/src/styles/widgets/dropdown.less +3 -0
- package/src/styles/widgets/expression.less +147 -0
- package/src/styles/widgets/graded-group.less +36 -0
- package/src/styles/widgets/grapher.less +11 -0
- package/src/styles/widgets/group.less +19 -0
- package/src/styles/widgets/image.less +29 -0
- package/src/styles/widgets/interactive-graph.less +61 -0
- package/src/styles/widgets/label-image.less +41 -0
- package/src/styles/widgets/matcher.less +8 -0
- package/src/styles/widgets/matrix.less +120 -0
- package/src/styles/widgets/measurer.less +20 -0
- package/src/styles/widgets/orderer.less +97 -0
- package/src/styles/widgets/passage.less +151 -0
- package/src/styles/widgets/plotter.less +33 -0
- package/src/styles/widgets/radio.less +52 -0
- package/src/styles/widgets/reaction-diagram.less +35 -0
- package/src/styles/widgets/sequence.less +5 -0
- package/src/styles/widgets/sortable.less +127 -0
- package/src/styles/widgets/table.less +56 -0
- package/src/styles/widgets/transformer.less +28 -0
- package/src/styles/widgets/unit.less +14 -0
- package/src/tex-wrangler.js +176 -0
- package/src/traversal.jsx +141 -0
- package/src/types.js +538 -0
- package/src/util/a11y.js +24 -0
- package/src/util/answer-types.js +862 -0
- package/src/util/answer-types_test.js +115 -0
- package/src/util/colors.js +94 -0
- package/src/util/fix-passage-refs.jsx +122 -0
- package/src/util/graph-utils.js +16 -0
- package/src/util/graphie.js +1586 -0
- package/src/util/interactive.js +4078 -0
- package/src/util/katex-preprocess.js +16 -0
- package/src/util/math.js +296 -0
- package/src/util/mobile-native-utils.js +14 -0
- package/src/util/mobile-native-utils_test.js +24 -0
- package/src/util/register-all-widgets-for-testing.js +12 -0
- package/src/util/scroll-utils.js +162 -0
- package/src/util/sizing-utils.js +46 -0
- package/src/util/tex.js +203 -0
- package/src/util/url-utils.js +36 -0
- package/src/util.js +990 -0
- package/src/version.js +11 -0
- package/src/widget-container.jsx +174 -0
- package/src/widgets/__stories__/categorizer.stories.jsx +15 -0
- package/src/widgets/__stories__/cs-program.stories.jsx +15 -0
- package/src/widgets/__stories__/definition.stories.jsx +38 -0
- package/src/widgets/__stories__/dropdown.stories.jsx +15 -0
- package/src/widgets/__stories__/explanation.stories.jsx +19 -0
- package/src/widgets/__stories__/expression.stories.jsx +26 -0
- package/src/widgets/__stories__/graded-group-set.stories.jsx +32 -0
- package/src/widgets/__stories__/graded-group.stories.jsx +32 -0
- package/src/widgets/__stories__/grapher.stories.jsx +47 -0
- package/src/widgets/__stories__/group.stories.jsx +15 -0
- package/src/widgets/__stories__/iframe.stories.jsx +15 -0
- package/src/widgets/__stories__/image.stories.jsx +62 -0
- package/src/widgets/__stories__/input-number.stories.jsx +90 -0
- package/src/widgets/__stories__/interaction.stories.jsx +15 -0
- package/src/widgets/__stories__/interactive-graph.stories.jsx +61 -0
- package/src/widgets/__stories__/matcher.stories.jsx +15 -0
- package/src/widgets/__stories__/matrix.stories.jsx +15 -0
- package/src/widgets/__stories__/number-line.stories.jsx +15 -0
- package/src/widgets/__stories__/numeric-input.stories.jsx +19 -0
- package/src/widgets/__stories__/orderer.stories.jsx +15 -0
- package/src/widgets/__stories__/passage-ref.stories.jsx +19 -0
- package/src/widgets/__stories__/passage.stories.jsx +19 -0
- package/src/widgets/__stories__/radio.stories.jsx +82 -0
- package/src/widgets/__stories__/sorter.stories.jsx +15 -0
- package/src/widgets/__stories__/transformer.stories.jsx +15 -0
- package/src/widgets/__stories__/unit.stories.jsx +45 -0
- package/src/widgets/__stories__/video-transcript-link.stories.jsx +24 -0
- package/src/widgets/__stories__/video.stories.jsx +15 -0
- package/src/widgets/__testdata__/categorizer_testdata.js +50 -0
- package/src/widgets/__testdata__/cs-program_testdata.js +28 -0
- package/src/widgets/__testdata__/dropdown_testdata.js +34 -0
- package/src/widgets/__testdata__/explanation_testdata.js +56 -0
- package/src/widgets/__testdata__/expression_testdata.js +95 -0
- package/src/widgets/__testdata__/graded-group-set_testdata.js +358 -0
- package/src/widgets/__testdata__/graded-group_testdata.js +79 -0
- package/src/widgets/__testdata__/grapher_testdata.js +376 -0
- package/src/widgets/__testdata__/group_testdata.js +156 -0
- package/src/widgets/__testdata__/iframe_testdata.js +36 -0
- package/src/widgets/__testdata__/image_testdata.js +38 -0
- package/src/widgets/__testdata__/input-number_testdata.js +73 -0
- package/src/widgets/__testdata__/interaction_testdata.js +264 -0
- package/src/widgets/__testdata__/interactive-graph_testdata.js +646 -0
- package/src/widgets/__testdata__/matcher_testdata.js +34 -0
- package/src/widgets/__testdata__/matrix_testdata.js +29 -0
- package/src/widgets/__testdata__/number-line_testdata.js +31 -0
- package/src/widgets/__testdata__/numeric-input_testdata.js +266 -0
- package/src/widgets/__testdata__/orderer_testdata.js +58 -0
- package/src/widgets/__testdata__/passage-ref_testdata.js +41 -0
- package/src/widgets/__testdata__/passage_testdata.js +71 -0
- package/src/widgets/__testdata__/radio_testdata.js +210 -0
- package/src/widgets/__testdata__/sorter_testdata.js +20 -0
- package/src/widgets/__testdata__/transformer_testdata.js +109 -0
- package/src/widgets/__testdata__/video_testdata.js +22 -0
- package/src/widgets/__tests__/__snapshots__/categorizer_test.jsx.snap +928 -0
- package/src/widgets/__tests__/__snapshots__/cs-program_test.jsx.snap +75 -0
- package/src/widgets/__tests__/__snapshots__/definition_test.js.snap +99 -0
- package/src/widgets/__tests__/__snapshots__/dropdown_test.jsx.snap +143 -0
- package/src/widgets/__tests__/__snapshots__/explanation_test.js.snap +473 -0
- package/src/widgets/__tests__/__snapshots__/expression_test.jsx.snap +38 -0
- package/src/widgets/__tests__/__snapshots__/graded-group-set-jipt_test.jsx.snap +754 -0
- package/src/widgets/__tests__/__snapshots__/graded-group-set_test.jsx.snap +310 -0
- package/src/widgets/__tests__/__snapshots__/graded-group_test.jsx.snap +863 -0
- package/src/widgets/__tests__/__snapshots__/grapher_test.jsx.snap +2880 -0
- package/src/widgets/__tests__/__snapshots__/group_test.jsx.snap +1154 -0
- package/src/widgets/__tests__/__snapshots__/iframe_test.jsx.snap +69 -0
- package/src/widgets/__tests__/__snapshots__/image_test.jsx.snap +388 -0
- package/src/widgets/__tests__/__snapshots__/input-number_test.jsx.snap +978 -0
- package/src/widgets/__tests__/__snapshots__/interaction_test.jsx.snap +311 -0
- package/src/widgets/__tests__/__snapshots__/interactive-graph_test.jsx.snap +2915 -0
- package/src/widgets/__tests__/__snapshots__/matcher_test.jsx.snap +1039 -0
- package/src/widgets/__tests__/__snapshots__/matrix_test.jsx.snap +450 -0
- package/src/widgets/__tests__/__snapshots__/number-line_test.jsx.snap +888 -0
- package/src/widgets/__tests__/__snapshots__/numeric-input_test.jsx.snap +930 -0
- package/src/widgets/__tests__/__snapshots__/orderer_test.jsx.snap +275 -0
- package/src/widgets/__tests__/__snapshots__/passage-ref_test.jsx.snap +446 -0
- package/src/widgets/__tests__/__snapshots__/passage_test.jsx.snap +1988 -0
- package/src/widgets/__tests__/__snapshots__/radio_test.jsx.snap +7107 -0
- package/src/widgets/__tests__/__snapshots__/video_test.jsx.snap +163 -0
- package/src/widgets/__tests__/categorizer_test.jsx +205 -0
- package/src/widgets/__tests__/cs-program_test.jsx +45 -0
- package/src/widgets/__tests__/definition_test.js +152 -0
- package/src/widgets/__tests__/dropdown_test.jsx +106 -0
- package/src/widgets/__tests__/explanation_test.js +199 -0
- package/src/widgets/__tests__/expression_test.jsx +439 -0
- package/src/widgets/__tests__/graded-group-set-jipt_test.jsx +33 -0
- package/src/widgets/__tests__/graded-group-set_test.jsx +270 -0
- package/src/widgets/__tests__/graded-group_test.jsx +233 -0
- package/src/widgets/__tests__/grapher.cypress.jsx +486 -0
- package/src/widgets/__tests__/grapher_test.jsx +35 -0
- package/src/widgets/__tests__/group_test.jsx +512 -0
- package/src/widgets/__tests__/iframe_test.jsx +47 -0
- package/src/widgets/__tests__/image_test.jsx +65 -0
- package/src/widgets/__tests__/input-number_test.jsx +360 -0
- package/src/widgets/__tests__/interaction_test.jsx +33 -0
- package/src/widgets/__tests__/interactive-graph.cypress.jsx +697 -0
- package/src/widgets/__tests__/interactive-graph_test.jsx +105 -0
- package/src/widgets/__tests__/matcher_test.jsx +157 -0
- package/src/widgets/__tests__/matrix_test.jsx +84 -0
- package/src/widgets/__tests__/number-line_test.jsx +81 -0
- package/src/widgets/__tests__/numeric-input_test.jsx +574 -0
- package/src/widgets/__tests__/orderer_test.jsx +75 -0
- package/src/widgets/__tests__/passage-ref_test.jsx +166 -0
- package/src/widgets/__tests__/passage_test.jsx +133 -0
- package/src/widgets/__tests__/radio/base-radio_test.jsx +82 -0
- package/src/widgets/__tests__/radio_test.jsx +651 -0
- package/src/widgets/__tests__/renderQuestion.jsx +66 -0
- package/src/widgets/__tests__/unit_test.jsx +144 -0
- package/src/widgets/__tests__/video_test.jsx +55 -0
- package/src/widgets/categorizer.jsx +338 -0
- package/src/widgets/cs-program.jsx +227 -0
- package/src/widgets/definition.jsx +141 -0
- package/src/widgets/dropdown.jsx +143 -0
- package/src/widgets/explanation.jsx +315 -0
- package/src/widgets/expression.jsx +652 -0
- package/src/widgets/graded-group-answer-bar.jsx +152 -0
- package/src/widgets/graded-group-set.jsx +285 -0
- package/src/widgets/graded-group.jsx +448 -0
- package/src/widgets/grapher/util.jsx +806 -0
- package/src/widgets/grapher.jsx +663 -0
- package/src/widgets/group.jsx +213 -0
- package/src/widgets/iframe.jsx +198 -0
- package/src/widgets/image.jsx +310 -0
- package/src/widgets/input-number.jsx +411 -0
- package/src/widgets/interaction/arrow-picker.jsx +39 -0
- package/src/widgets/interaction/color-picker.jsx +69 -0
- package/src/widgets/interaction/dash-picker.jsx +46 -0
- package/src/widgets/interaction/element-container.jsx +97 -0
- package/src/widgets/interaction.jsx +742 -0
- package/src/widgets/interactive-graph.jsx +2820 -0
- package/src/widgets/label-image/__stories__/answer-choices.stories.jsx +93 -0
- package/src/widgets/label-image/__stories__/marker.stories.jsx +172 -0
- package/src/widgets/label-image/answer-choices.jsx +311 -0
- package/src/widgets/label-image/marker.jsx +365 -0
- package/src/widgets/label-image/types.js +33 -0
- package/src/widgets/label-image.jsx +841 -0
- package/src/widgets/label-image_test.js +840 -0
- package/src/widgets/lights-puzzle.jsx +356 -0
- package/src/widgets/matcher.jsx +271 -0
- package/src/widgets/matrix.jsx +616 -0
- package/src/widgets/measurer.jsx +215 -0
- package/src/widgets/molecule/__tests__/layout_test.jsx +159 -0
- package/src/widgets/molecule/__tests__/parser_test.jsx +142 -0
- package/src/widgets/molecule/molecule-drawing.jsx +175 -0
- package/src/widgets/molecule/molecule-layout.jsx +367 -0
- package/src/widgets/molecule/smiles-parser.jsx +300 -0
- package/src/widgets/molecule.jsx +172 -0
- package/src/widgets/number-line.jsx +858 -0
- package/src/widgets/numeric-input.jsx +622 -0
- package/src/widgets/orderer.jsx +753 -0
- package/src/widgets/passage/__tests__/passage-markdown_test.jsx +382 -0
- package/src/widgets/passage/get-line-height-for-node.js +10 -0
- package/src/widgets/passage/passage-markdown.jsx +416 -0
- package/src/widgets/passage-ref-target.jsx +70 -0
- package/src/widgets/passage-ref.jsx +198 -0
- package/src/widgets/passage.jsx +601 -0
- package/src/widgets/plotter.jsx +1198 -0
- package/src/widgets/radio/__stories__/base-radio.stories.jsx +40 -0
- package/src/widgets/radio/__stories__/choice-icon.stories.jsx +157 -0
- package/src/widgets/radio/__stories__/choice-none-above.stories.jsx +26 -0
- package/src/widgets/radio/__stories__/cross-out-button.stories.jsx +64 -0
- package/src/widgets/radio/__stories__/cross-out-menu.stories.jsx +59 -0
- package/src/widgets/radio/__stories__/cross-out-tooltip.stories.jsx +59 -0
- package/src/widgets/radio/__stories__/focus-ring.stories.jsx +38 -0
- package/src/widgets/radio/__stories__/option-status.stories.jsx +118 -0
- package/src/widgets/radio/__stories__/toggleable-radio-button.stories.jsx +28 -0
- package/src/widgets/radio/base-radio.jsx +642 -0
- package/src/widgets/radio/choice-icon.jsx +468 -0
- package/src/widgets/radio/choice-none-above.jsx +54 -0
- package/src/widgets/radio/choice.jsx +692 -0
- package/src/widgets/radio/choice.jsx.fixture.js +64 -0
- package/src/widgets/radio/cross-out-button.jsx +136 -0
- package/src/widgets/radio/cross-out-menu-wrapper.jsx +492 -0
- package/src/widgets/radio/cross-out-tooltip.jsx +129 -0
- package/src/widgets/radio/focus-ring.jsx +51 -0
- package/src/widgets/radio/option-status.jsx +89 -0
- package/src/widgets/radio/toggleable-radio-button.jsx +133 -0
- package/src/widgets/radio/util.js +32 -0
- package/src/widgets/radio/util_test.js +19 -0
- package/src/widgets/radio/widget.jsx +470 -0
- package/src/widgets/radio.jsx +156 -0
- package/src/widgets/reaction-diagram.jsx +137 -0
- package/src/widgets/sequence.jsx +140 -0
- package/src/widgets/simulator.jsx +808 -0
- package/src/widgets/sorter.jsx +146 -0
- package/src/widgets/table.jsx +394 -0
- package/src/widgets/transformer.jsx +2876 -0
- package/src/widgets/unit.jsx +372 -0
- package/src/widgets/video-transcript-link.jsx +84 -0
- package/src/widgets/video-transcript-link_test.js +211 -0
- package/src/widgets/video.jsx +126 -0
- package/src/widgets.js +431 -0
- package/src/zoom.js +539 -0
- package/README.md +0 -3
- 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>);
|