Files
frontend-app-discussions/src/components/HTMLLoader.jsx

67 lines
1.8 KiB
JavaScript

import React, { useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import DOMPurify from 'dompurify';
import { logError } from '@edx/frontend-platform/logging';
import { useDebounce } from '../discussions/data/hooks';
const defaultSanitizeOptions = {
USE_PROFILES: { html: true },
ADD_ATTR: ['columnalign'],
};
function HTMLLoader({
htmlNode, componentId, cssClassName, testId, delay,
}) {
const sanitizedMath = DOMPurify.sanitize(htmlNode, { ...defaultSanitizeOptions });
const previewRef = useRef(null);
const debouncedPostContent = useDebounce(htmlNode, delay);
useEffect(() => {
let promise = Promise.resolve(); // Used to hold chain of typesetting calls
function typeset(code) {
promise = promise.then(() => {
if (typeof window?.MathJax !== 'undefined' && typeof window?.MathJax.startup !== 'undefined') {
window.MathJax.startup.defaultPageReady().then((window.MathJax?.typesetPromise(code())));
}
return null;
})
.catch((err) => logError(`Typeset failed: ${err.message}`));
return promise;
}
if (debouncedPostContent) {
typeset(() => {
if (previewRef.current !== null && typeof window?.MathJax !== 'undefined') {
previewRef.current.innerHTML = sanitizedMath;
}
});
}
}, [debouncedPostContent]);
return (
<div ref={previewRef} className={cssClassName} id={componentId} data-testid={testId} />
);
}
HTMLLoader.propTypes = {
htmlNode: PropTypes.node,
componentId: PropTypes.string,
cssClassName: PropTypes.string,
testId: PropTypes.string,
delay: PropTypes.number,
};
HTMLLoader.defaultProps = {
htmlNode: '',
componentId: null,
cssClassName: '',
testId: '',
delay: 0,
};
export default HTMLLoader;