diff --git a/.eslintrc.js b/.eslintrc.js index eec4ee7f..95058e25 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -5,6 +5,9 @@ const config = createConfig('eslint', { rules: { 'import/no-unresolved': 'off', }, + globals: { + MathJax: true, + }, }); config.rules['react/function-component-definition'][1].unnamedComponents = 'arrow-function'; diff --git a/package-lock.json b/package-lock.json index f3c664cc..d9341014 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,7 @@ "@zip.js/zip.js": "^2.7.30", "axios": "^1.5.1", "classnames": "^2.3.2", - "core-js": "3.34.0", + "core-js": "3.35.0", "file-saver": "^2.0.5", "filesize": "^8.0.6", "jest-when": "^3.6.0", @@ -7404,9 +7404,9 @@ } }, "node_modules/core-js": { - "version": "3.34.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.34.0.tgz", - "integrity": "sha512-aDdvlDder8QmY91H88GzNi9EtQi2TjvQhpCX6B1v/dAZHU1AuLgHvRh54RiOerpEhEW46Tkf+vgAViB/CWC0ag==", + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.35.0.tgz", + "integrity": "sha512-ntakECeqg81KqMueeGJ79Q5ZgQNR+6eaE8sxGCx62zMbAIj65q+uYvatToew3m6eAGdU4gNZwpZ34NMe4GYswg==", "hasInstallScript": true, "funding": { "type": "opencollective", diff --git a/public/index.html b/public/index.html index 7974c023..4340feb9 100644 --- a/public/index.html +++ b/public/index.html @@ -1,10 +1,10 @@ - + - Open Response Assessment| <%= process.env.SITE_NAME %> - - - + Open Response Assessment| <%= process.env.SITE_NAME %> + + + - + + + + +
diff --git a/src/components/Prompt/index.jsx b/src/components/Prompt/index.jsx index 8f685ab6..a6f42a28 100644 --- a/src/components/Prompt/index.jsx +++ b/src/components/Prompt/index.jsx @@ -10,6 +10,8 @@ import { useViewStep } from 'hooks/routing'; import messages from './messages'; import usePromptHooks from './hooks'; +import './index.scss'; + const Prompt = ({ prompt, defaultOpen }) => { const { open, toggleOpen } = usePromptHooks({ defaultOpen }); const { formatMessage } = useIntl(); @@ -33,7 +35,7 @@ const Prompt = ({ prompt, defaultOpen }) => { .replaceAll(staticRegex.link, `a href="${process.env.LMS_BASE_URL}/${baseAssetUrl}$1"`); return ( {title})} open={open} onToggle={toggleOpen}> -
+
); }; diff --git a/src/components/Prompt/index.scss b/src/components/Prompt/index.scss new file mode 100644 index 00000000..f8c8e8a4 --- /dev/null +++ b/src/components/Prompt/index.scss @@ -0,0 +1,3 @@ +.prompt > pre { + text-wrap: wrap; +} \ No newline at end of file diff --git a/src/components/TextResponse/index.jsx b/src/components/TextResponse/index.jsx index 3b86d1c6..ec7b448b 100644 --- a/src/components/TextResponse/index.jsx +++ b/src/components/TextResponse/index.jsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import PropTypes from 'prop-types'; import { useSubmissionConfig } from 'hooks/app'; @@ -7,11 +7,20 @@ import './index.scss'; const TextResponse = ({ response }) => { const { textResponseConfig } = useSubmissionConfig(); + const textResponseRef = React.useRef(null); + + const { editorType, allowLatexPreview } = textResponseConfig || {}; + + useEffect(() => { + if (allowLatexPreview) { + MathJax.Hub.Queue(['Typeset', MathJax.Hub, textResponseRef.current]); + } + }, [allowLatexPreview, response]); return ( -
- {textResponseConfig.editorType === 'text' ? ( -
{response}
+
+ {editorType === 'text' ? ( +
) : (
)} diff --git a/src/components/TextResponse/index.scss b/src/components/TextResponse/index.scss index 04e4bc69..d8d24011 100644 --- a/src/components/TextResponse/index.scss +++ b/src/components/TextResponse/index.scss @@ -1,4 +1,4 @@ -.pre-like-textarea { +.div-textarea { white-space: pre-wrap; word-wrap: break-word; word-break: break-all; diff --git a/src/data/services/lms/types/blockInfo.ts b/src/data/services/lms/types/blockInfo.ts index 02810574..3c511aa7 100644 --- a/src/data/services/lms/types/blockInfo.ts +++ b/src/data/services/lms/types/blockInfo.ts @@ -16,7 +16,7 @@ export interface TextResponseConfig { enabled: boolean, optional: boolean, editorType: 'text' | 'tinymce', - allowLatexPreviews: boolean, + allowLatexPreview: boolean, } export interface FileResponseConfig { diff --git a/src/setupTest.js b/src/setupTest.js index 7f11ec8f..944db905 100644 --- a/src/setupTest.js +++ b/src/setupTest.js @@ -108,3 +108,10 @@ Object.defineProperty(window, 'matchMedia', { dispatchEvent: jest.fn(), })), }); + +global.MathJax = { + Hub: { + Queue: jest.fn(), + Config: jest.fn(), + }, +}; diff --git a/src/views/SubmissionView/TextResponseEditor/LaTexPreview.jsx b/src/views/SubmissionView/TextResponseEditor/LaTexPreview.jsx new file mode 100644 index 00000000..e3bbd7a7 --- /dev/null +++ b/src/views/SubmissionView/TextResponseEditor/LaTexPreview.jsx @@ -0,0 +1,22 @@ +import React, { useEffect } from 'react'; +import PropTypes from 'prop-types'; + +const LatexPreview = ({ latexValue }) => { + const latexPreviewEl = React.useRef(null); + + useEffect(() => { + MathJax.Hub.Queue(['Typeset', MathJax.Hub, latexPreviewEl.current]); + }, [latexValue]); + + return ( +
+
+
+ ); +}; + +LatexPreview.propTypes = { + latexValue: PropTypes.string.isRequired, +}; + +export default LatexPreview; diff --git a/src/views/SubmissionView/TextResponseEditor/index.jsx b/src/views/SubmissionView/TextResponseEditor/index.jsx index 8e480047..d86e9404 100644 --- a/src/views/SubmissionView/TextResponseEditor/index.jsx +++ b/src/views/SubmissionView/TextResponseEditor/index.jsx @@ -1,10 +1,14 @@ -import React from 'react'; +import React, { useCallback } from 'react'; import PropTypes from 'prop-types'; +import { Button } from '@edx/paragon'; +import { useIntl } from '@edx/frontend-platform/i18n'; import { useSubmissionConfig } from 'hooks/app'; import TextEditor from './TextEditor'; import RichTextEditor from './RichTextEditor'; +import LatexPreview from './LaTexPreview'; +import messages from './messages'; const TextResponseEditor = ({ value, onChange }) => { const { textResponseConfig } = useSubmissionConfig(); @@ -12,7 +16,15 @@ const TextResponseEditor = ({ value, onChange }) => { optional, enabled, editorType, + allowLatexPreview, } = textResponseConfig || {}; + const { formatMessage } = useIntl(); + + const [latexValue, setLatexValue] = React.useState(''); + + const previewLaTex = useCallback(() => { + setLatexValue(value); + }, [value]); if (!enabled) { return null; @@ -23,6 +35,14 @@ const TextResponseEditor = ({ value, onChange }) => { return (
+ { + allowLatexPreview && ( +
+ + +
+ ) + }
); }; diff --git a/src/views/SubmissionView/TextResponseEditor/messages.js b/src/views/SubmissionView/TextResponseEditor/messages.js index 898e7c09..e3752524 100644 --- a/src/views/SubmissionView/TextResponseEditor/messages.js +++ b/src/views/SubmissionView/TextResponseEditor/messages.js @@ -21,6 +21,11 @@ const messages = defineMessages({ description: 'Label for the optional indicator', id: 'frontend-app-ora.TextResponse.optional', }, + previewLaTexButton: { + defaultMessage: 'Preview in LaTeX', + description: 'Label for the preview LaTeX button', + id: 'frontend-app-ora.TextResponse.previewLaTexButton', + }, }); export default messages;