From 6da37d2f10bda737dbada10270086fd2552ab635 Mon Sep 17 00:00:00 2001 From: Braden MacDonald Date: Tue, 28 Jan 2025 11:41:11 -0800 Subject: [PATCH 1/5] fix: clicking library name in Studio header would show 404 --- src/header/Header.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/header/Header.tsx b/src/header/Header.tsx index ebd3199893..d3a7d478d2 100644 --- a/src/header/Header.tsx +++ b/src/header/Header.tsx @@ -3,7 +3,6 @@ import { getConfig } from '@edx/frontend-platform'; import { useIntl } from '@edx/frontend-platform/i18n'; import { StudioHeader } from '@edx/frontend-component-header'; import { type Container, useToggle } from '@openedx/paragon'; -import { generatePath, useHref } from 'react-router-dom'; import { getWaffleFlags } from '../data/selectors'; import { SearchModal } from '../search-modal'; @@ -32,7 +31,6 @@ const Header = ({ containerProps = {}, }: HeaderProps) => { const intl = useIntl(); - const libraryHref = useHref('/library/:libraryId'); const waffleFlags = useSelector(getWaffleFlags); const [isShowSearchModalOpen, openSearchModal, closeSearchModal] = useToggle(false); @@ -63,7 +61,7 @@ const Header = ({ const getOutlineLink = () => { if (isLibrary) { - return generatePath(libraryHref, { libraryId: contextId }); + return `/library/${contextId}`; } return waffleFlags.useNewCourseOutlinePage ? `/course/${contextId}` : `${studioBaseUrl}/course/${contextId}`; }; From ca763e8796327089fa5ffd1b402fd297dfcfc420 Mon Sep 17 00:00:00 2001 From: Braden MacDonald Date: Fri, 31 Jan 2025 16:01:57 -0800 Subject: [PATCH 2/5] fix: when ExpandableTextArea is in a modal, the selection toolbar could not be clicked --- .../sharedComponents/TinyMceWidget/hooks.js | 58 ++++++++++++------- 1 file changed, 38 insertions(+), 20 deletions(-) diff --git a/src/editors/sharedComponents/TinyMceWidget/hooks.js b/src/editors/sharedComponents/TinyMceWidget/hooks.js index d9c5016ced..3bb23a0c78 100644 --- a/src/editors/sharedComponents/TinyMceWidget/hooks.js +++ b/src/editors/sharedComponents/TinyMceWidget/hooks.js @@ -148,6 +148,40 @@ export const getImageResizeHandler = ({ editor, imagesRef, setImage }) => () => }); }; +/** + * Fix TinyMCE editors used in Paragon modals, by re-parenting their modal
+ * from the body to the Paragon modal container. + * + * This fixes a problem where clicking on any modal/popup within TinyMCE (e.g. + * the emoji inserter, the link inserter, the floating format toolbar - + * quickbars, etc.) would cause the parent Paragon modal to close, because + * Paragon sees it as a "click outside" event. Also fixes some hover effects by + * ensuring the layering of the divs is correct. + * + * This could potentially cause problems if there are TinyMCE editors being used + * both on the parent page and inside a Paragon modal popup, but I don't think + * we have that situation. + * + * Note: we can't just do this on init, because the quickbars plugin used by + * ExpandableTextEditors creates its modal DIVs later. Ideally we could listen + * for some kind of "modal open" event, but I haven't been able to find anything + * like that so for now we do this quite frequently, every time there is a + * "selectionchange" event (which is pretty often). + */ +export const reparentTinyMceModals = () => { + const modalLayer = document.querySelector('.pgn__modal-layer'); + if (!modalLayer) { + return; + } + const tinymceAuxDivs = document.querySelectorAll('.tox.tox-tinymce-aux'); + for (const tinymceAux of tinymceAuxDivs) { + if (tinymceAux.parentElement !== modalLayer) { + // Move this tinyMCE modal div into the paragon modal layer. + modalLayer.appendChild(tinymceAux); + } + } +}; + export const setupCustomBehavior = ({ updateContent, openImgModal, @@ -221,30 +255,14 @@ export const setupCustomBehavior = ({ } editor.on('init', /* istanbul ignore next */ () => { - // Moving TinyMce aux modal inside the Editor modal - // if the editor is on modal mode. - // This is to avoid issues using the aux modal: - // * Avoid close aux modal when clicking the content inside. - // * When the user opens the `Edit Source Code` modal, this adds `data-focus-on-hidden` - // to the TinyMce aux modal, making it unusable. - const modalLayer = document.querySelector('.pgn__modal-layer'); - const tinymceAux = document.querySelector('.tox.tox-tinymce-aux'); - - if (modalLayer && tinymceAux) { - modalLayer.appendChild(tinymceAux); + if (editor.bodyElement?.closest('.pgn__modal')) { + // This editor is inside a Paragon modal. Use this hack to avoid interference with TinyMCE's own modal popups: + reparentTinyMceModals(); + editor.on('selectionchange', reparentTinyMceModals); } }); editor.on('ExecCommand', /* istanbul ignore next */ (e) => { - // Remove `data-focus-on-hidden` and `aria-hidden` on TinyMce aux modal used on emoticons, formulas, etc. - // When using the Editor in modal mode, it may happen that the editor modal is rendered - // before the TinyMce aux modal, which adds these attributes, making the TinyMce aux modal unusable. - const modalElement = document.querySelector('.tox.tox-silver-sink.tox-tinymce-aux'); - if (modalElement) { - modalElement.removeAttribute('data-focus-on-hidden'); - modalElement.removeAttribute('aria-hidden'); - } - if (editorType === 'text' && e.command === 'mceFocus') { const initialContent = editor.getContent(); const newContent = module.replaceStaticWithAsset({ From b2d4a3df9f2b8112cbf1e466bb75ba58ab072874 Mon Sep 17 00:00:00 2001 From: Braden MacDonald Date: Tue, 4 Feb 2025 09:52:15 -0800 Subject: [PATCH 3/5] fix: in ExpandableTextArea, shrink the "insert toolbar" that blocks the input --- .../sharedComponents/TinyMceWidget/pluginConfig.js | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/editors/sharedComponents/TinyMceWidget/pluginConfig.js b/src/editors/sharedComponents/TinyMceWidget/pluginConfig.js index ed64c3e883..720aacc8ba 100644 --- a/src/editors/sharedComponents/TinyMceWidget/pluginConfig.js +++ b/src/editors/sharedComponents/TinyMceWidget/pluginConfig.js @@ -64,16 +64,9 @@ const pluginConfig = ({ placeholder, editorType, enableImageUpload }) => { [editImageSettings], ]), quickbarsInsertToolbar: toolbar ? false : mapToolbars([ - [buttons.undo, buttons.redo], - [buttons.formatSelect], - [buttons.bold, buttons.italic, buttons.underline, buttons.foreColor], - [ - buttons.align.justify, - buttons.bullist, - buttons.numlist, - ], - [imageUploadButton, buttons.blockQuote, buttons.codeBlock], - [buttons.table, buttons.emoticons, buttons.charmap, buttons.removeFormat, buttons.a11ycheck], + // To keep from blocking the whole text input field when it's empty, this "insert" toolbar + // used with ExpandableTextArea is kept as minimal as we can. + [imageUploadButton, buttons.table], ]), quickbarsSelectionToolbar: toolbar ? false : mapToolbars([ [buttons.undo, buttons.redo], From 57a3ae25ddf34077747f517bcead69396cb6e4c5 Mon Sep 17 00:00:00 2001 From: Braden MacDonald Date: Tue, 11 Feb 2025 10:38:50 -0800 Subject: [PATCH 4/5] chore: ignore coverage of modal fixer --- src/editors/sharedComponents/TinyMceWidget/hooks.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editors/sharedComponents/TinyMceWidget/hooks.js b/src/editors/sharedComponents/TinyMceWidget/hooks.js index 3bb23a0c78..f9d962cdc1 100644 --- a/src/editors/sharedComponents/TinyMceWidget/hooks.js +++ b/src/editors/sharedComponents/TinyMceWidget/hooks.js @@ -168,7 +168,7 @@ export const getImageResizeHandler = ({ editor, imagesRef, setImage }) => () => * like that so for now we do this quite frequently, every time there is a * "selectionchange" event (which is pretty often). */ -export const reparentTinyMceModals = () => { +export const reparentTinyMceModals = /* istanbul ignore next */ () => { const modalLayer = document.querySelector('.pgn__modal-layer'); if (!modalLayer) { return; From 0d44e3e171eaf83cd0b70fde68228116bee3a56b Mon Sep 17 00:00:00 2001 From: Braden MacDonald Date: Wed, 12 Feb 2025 10:31:56 -0800 Subject: [PATCH 5/5] fix: make sure emoji/formula modals are working in the text editor too --- src/editors/sharedComponents/TinyMceWidget/hooks.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/editors/sharedComponents/TinyMceWidget/hooks.js b/src/editors/sharedComponents/TinyMceWidget/hooks.js index f9d962cdc1..49ac8a574f 100644 --- a/src/editors/sharedComponents/TinyMceWidget/hooks.js +++ b/src/editors/sharedComponents/TinyMceWidget/hooks.js @@ -255,7 +255,10 @@ export const setupCustomBehavior = ({ } editor.on('init', /* istanbul ignore next */ () => { - if (editor.bodyElement?.closest('.pgn__modal')) { + // Check if this editor is inside a (Paragon) modal. + // The way we get the editor's root
depends on whether or not this particular editor is using an iframe: + const editorDiv = editor.bodyElement ?? editor.container; + if (editorDiv?.closest('.pgn__modal')) { // This editor is inside a Paragon modal. Use this hack to avoid interference with TinyMCE's own modal popups: reparentTinyMceModals(); editor.on('selectionchange', reparentTinyMceModals);