Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add latex support #168

Merged
merged 6 commits into from
Jan 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

62 changes: 56 additions & 6 deletions public/index.html
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<!doctype html>
<!DOCTYPE html>
<html lang="en-us">
<head>
<title>Open Response Assessment| <%= process.env.SITE_NAME %></title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="<%=htmlWebpackPlugin.options.FAVICON_URL%>" type="image/x-icon" />
<title>Open Response Assessment| <%= process.env.SITE_NAME %></title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="<%=htmlWebpackPlugin.options.FAVICON_URL%>" type="image/x-icon" />
<!-- Hotjar Tracking Code for https://www.edx.org/ -->
<script>
const isMobile = window.navigator.userAgent.includes("org.edx.mobile");
Expand All @@ -19,7 +19,57 @@
})(window,document,'https://static.hotjar.com/c/hotjar-','.js?sv=');
}
</script>
</head>
<script type="text/x-mathjax-config">
MathJax.Hub.Config({
styles: {
'.MathJax_SVG>svg': { 'max-width': '100%', },
// This is to resolve for people who use center mathjax with tables
'table>tbody>tr>td>.MathJax_SVG>svg': { 'max-width': 'inherit'},
},
CommonHTML: { linebreaks: { automatic: true } },
SVG: { linebreaks: { automatic: true } },
"HTML-CSS": { linebreaks: { automatic: true } },
tex2jax: {inlineMath: [ ['$','$'], ["\\(","\\)"]],
displayMath: [ ['$$','$$'], ["\\[","\\]"]],
processEscapes: true},
});
</script>
<script type="text/javascript">
// Activating Mathjax accessibility files
window.MathJax = {
menuSettings: {
collapsible: true,
autocollapse: false,
explorer: true,
},
};
window.addEventListener('resize', MJrenderer);

let t = -1;
let delay = 1000;
let oldWidth = document.documentElement.scrollWidth;
function MJrenderer() {
// don't rerender if the window is the same size as before
if (t >= 0) {
window.clearTimeout(t);
}
if (oldWidth !== document.documentElement.scrollWidth) {
t = window.setTimeout(function () {
oldWidth = document.documentElement.scrollWidth;
MathJax.Hub.Queue(['Rerender', MathJax.Hub]);
t = -1;
}, delay);
}
}
</script>
<!-- This must appear after all mathjax-config blocks, so it is after the imports from the other templates.
It can't be run through static.url because MathJax uses crazy url introspection to do lazy loading of
MathJax extension libraries -->
<script
type="text/javascript"
src="https://cdn.jsdelivr.net/npm/mathjax@2.7.5/MathJax.js?config=TeX-MML-AM_SVG"
></script>
</head>
<body>
<div id="root"></div>
</body>
Expand Down
4 changes: 3 additions & 1 deletion src/components/Prompt/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import messages from './messages';
import usePromptHooks from './hooks';

import './index.scss';

const Prompt = ({ prompt, defaultOpen }) => {
const { open, toggleOpen } = usePromptHooks({ defaultOpen });
const { formatMessage } = useIntl();
Expand All @@ -33,7 +35,7 @@
.replaceAll(staticRegex.link, `a href="${process.env.LMS_BASE_URL}/${baseAssetUrl}$1"`);
return (
<Collapsible title={(<h3 className="py-3">{title}</h3>)} open={open} onToggle={toggleOpen}>
<div dangerouslySetInnerHTML={{ __html: promptWithStaticAssets }} />
<div className="prompt" dangerouslySetInnerHTML={{ __html: promptWithStaticAssets }} />

Check warning on line 38 in src/components/Prompt/index.jsx

View workflow job for this annotation

GitHub Actions / test

Dangerous property 'dangerouslySetInnerHTML' found
</Collapsible>
);
};
Expand Down
3 changes: 3 additions & 0 deletions src/components/Prompt/index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.prompt > pre {
text-wrap: wrap;
}
17 changes: 13 additions & 4 deletions src/components/TextResponse/index.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';

import { useSubmissionConfig } from 'hooks/app';
Expand All @@ -7,13 +7,22 @@

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 (
<div className="my-2 p-2 bg-white">
{textResponseConfig.editorType === 'text' ? (
<pre className="pre-like-textarea p-1">{response}</pre>
<div ref={textResponseRef} className="my-2 p-2 bg-white">
{editorType === 'text' ? (
<div className="div-textarea p-1" dangerouslySetInnerHTML={{ __html: response }} />

Check warning on line 23 in src/components/TextResponse/index.jsx

View workflow job for this annotation

GitHub Actions / test

Dangerous property 'dangerouslySetInnerHTML' found
) : (
<div dangerouslySetInnerHTML={{ __html: response }} />

Check warning on line 25 in src/components/TextResponse/index.jsx

View workflow job for this annotation

GitHub Actions / test

Dangerous property 'dangerouslySetInnerHTML' found
)}
</div>
);
Expand Down
2 changes: 1 addition & 1 deletion src/components/TextResponse/index.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.pre-like-textarea {
.div-textarea {
white-space: pre-wrap;
word-wrap: break-word;
word-break: break-all;
Expand Down
2 changes: 1 addition & 1 deletion src/data/services/lms/types/blockInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export interface TextResponseConfig {
enabled: boolean,
optional: boolean,
editorType: 'text' | 'tinymce',
allowLatexPreviews: boolean,
allowLatexPreview: boolean,
}

export interface FileResponseConfig {
Expand Down
7 changes: 7 additions & 0 deletions src/setupTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,10 @@ Object.defineProperty(window, 'matchMedia', {
dispatchEvent: jest.fn(),
})),
});

global.MathJax = {
Hub: {
Queue: jest.fn(),
Config: jest.fn(),
},
};
22 changes: 22 additions & 0 deletions src/views/SubmissionView/TextResponseEditor/LaTexPreview.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<div ref={latexPreviewEl} className="mt-2">
<div dangerouslySetInnerHTML={{ __html: latexValue }} />

Check warning on line 13 in src/views/SubmissionView/TextResponseEditor/LaTexPreview.jsx

View workflow job for this annotation

GitHub Actions / test

Dangerous property 'dangerouslySetInnerHTML' found
</div>
);
};

LatexPreview.propTypes = {
latexValue: PropTypes.string.isRequired,
};

export default LatexPreview;
22 changes: 21 additions & 1 deletion src/views/SubmissionView/TextResponseEditor/index.jsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,30 @@
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();
const {
optional,
enabled,
editorType,
allowLatexPreview,
} = textResponseConfig || {};
const { formatMessage } = useIntl();

const [latexValue, setLatexValue] = React.useState('');

const previewLaTex = useCallback(() => {
setLatexValue(value);
}, [value]);

if (!enabled) {
return null;
Expand All @@ -23,6 +35,14 @@ const TextResponseEditor = ({ value, onChange }) => {
return (
<div className="mt-2">
<EditorComponent {...{ optional, value, onChange }} />
{
allowLatexPreview && (
<div>
<Button className="btn btn-primary btn-sm mt-2" onClick={previewLaTex}>{formatMessage(messages.previewLaTexButton)}</Button>
<LatexPreview latexValue={latexValue} />
</div>
)
}
</div>
);
};
Expand Down
5 changes: 5 additions & 0 deletions src/views/SubmissionView/TextResponseEditor/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Loading