import React, { useCallback, useEffect, useState } from 'react'; import { ActionRow, AlertModal, Button } from '@openedx/paragon'; import { Editor } from '@tinymce/tinymce-react'; import { useLocation, useParams } from 'react-router-dom'; // TinyMCE so the global var exists /* eslint-disable-next-line @typescript-eslint/no-unused-vars, import/no-extraneous-dependencies */ import tinymce from 'tinymce/tinymce'; import { useIntl } from '@edx/frontend-platform/i18n'; import { MAX_UPLOAD_FILE_SIZE } from '../data/constants'; import messages from '../discussions/messages'; import { uploadFile } from '../discussions/posts/data/api'; import 'tinymce/plugins/code'; // Theme import 'tinymce/themes/silver'; // Toolbar icons import 'tinymce/icons/default'; // Editor styles import 'tinymce/skins/ui/oxide/skin.css'; // importing the plugin js. import 'tinymce/plugins/autolink'; import 'tinymce/plugins/autoresize'; import 'tinymce/plugins/autosave'; import 'tinymce/plugins/codesample'; import 'tinymce/plugins/image'; import 'tinymce/plugins/imagetools'; import 'tinymce/plugins/link'; import 'tinymce/plugins/lists'; import 'tinymce/plugins/emoticons'; import 'tinymce/plugins/emoticons/js/emojis'; import 'tinymce/plugins/charmap'; import 'tinymce/plugins/paste'; /* eslint import/no-webpack-loader-syntax: off */ // eslint-disable-next-line import/no-unresolved import edxBrandCss from '!!raw-loader!sass-loader!../index.scss'; // eslint-disable-next-line import/no-unresolved import contentCss from '!!raw-loader!tinymce/skins/content/default/content.min.css'; // eslint-disable-next-line import/no-unresolved import contentUiCss from '!!raw-loader!tinymce/skins/ui/oxide/content.min.css'; /* istanbul ignore next */ const TinyMCEEditor = (props) => { // note that skin and content_css is disabled to avoid the normal // loading process and is instead loaded as a string via content_style const locationObj = useLocation(); const { courseId, postId } = useParams(); const [showImageWarning, setShowImageWarning] = useState(false); const intl = useIntl(); const enableInContextSidebar = Boolean(new URLSearchParams(locationObj.search).get('inContextSidebar') !== null); /* istanbul ignore next */ const setup = useCallback((editor) => { editor.ui.registry.addButton('openedx_code', { icon: 'sourcecode', onAction: () => { editor.execCommand('CodeSample'); }, }); editor.ui.registry.addButton('openedx_html', { text: 'HTML', onAction: () => { editor.execCommand('mceCodeEditor'); }, }); }, []); const uploadHandler = useCallback(async (blobInfo, success, failure) => { try { const blob = blobInfo.blob(); const imageSize = blobInfo.blob().size / 1024; if (imageSize > MAX_UPLOAD_FILE_SIZE) { failure(`Images size should not exceed ${MAX_UPLOAD_FILE_SIZE} KB`); return; } const filename = blobInfo.filename(); const { location } = await uploadFile(blob, filename, courseId, postId || 'root'); const img = new Image(); img.onload = () => { if (img.height > 999 || img.width > 999) { setShowImageWarning(true); } }; img.src = location; success(location); } catch (e) { failure(e.toString(), { remove: true }); } }, [courseId, postId]); const handleClose = useCallback(() => { setShowImageWarning(false); }, []); let contentStyle; // In the test environment this causes an error so set styles to empty since they aren't needed for testing. try { contentStyle = [contentCss, contentUiCss, edxBrandCss].join('\n'); } catch (err) { contentStyle = ''; } // eslint-disable-next-line consistent-return useEffect(() => { if (enableInContextSidebar) { const checkToxDialogVisibility = () => { const toxDialog = document.querySelector('.tox-dialog'); if (toxDialog) { toxDialog.style.alignSelf = 'start'; toxDialog.style.marginTop = '50px'; } }; const observer = new MutationObserver(checkToxDialogVisibility); // Observe changes to the entire document observer.observe(document, { childList: true, subtree: true }); // Clean up the observer when the component unmounts return () => { observer.disconnect(); }; } }, [enableInContextSidebar]); return ( <> )} >

{intl.formatMessage(messages.imageWarningMessage)}

); }; export default React.memo(TinyMCEEditor);