From 5bd1081c37967518ed3a1427600ae5d5d3c06cae Mon Sep 17 00:00:00 2001 From: David Calhoun <438664+dcalhoun@users.noreply.github.com> Date: Fri, 24 Feb 2023 15:08:30 -0500 Subject: [PATCH 1/9] fix: Enable access to block settings within UBE The "Show more settings" menu item is no longer included in the block toolbar after #46709 merged. Rather than relying upon that menu item, this conditionally displays the sidebar toggle button whenever a block is selected. --- .../content-functions.js | 46 ++++++++++++++++ .../editor-style-overrides.css | 54 ++++++------------- 2 files changed, 61 insertions(+), 39 deletions(-) diff --git a/packages/react-native-bridge/common/gutenberg-web-single-block/content-functions.js b/packages/react-native-bridge/common/gutenberg-web-single-block/content-functions.js index d6e7d51375d44..8404aa99f22bf 100644 --- a/packages/react-native-bridge/common/gutenberg-web-single-block/content-functions.js +++ b/packages/react-native-bridge/common/gutenberg-web-single-block/content-functions.js @@ -76,3 +76,49 @@ window.startObservingGutenberg = () => { window.subscribed = true; } }; + +const editor = document.querySelector( '#editor' ); + +function _toggleBlockSelectedClass( isBlockSelected ) { + if ( isBlockSelected ) { + editor.classList.add( 'is-block-selected' ); + } else { + editor.classList.remove( 'is-block-selected' ); + } +} + +/** @typedef {import('@wordpress/data').WPDataRegistry} WPDataRegistry */ + +/** + * Toggle the `is-block-selected` class on the editor container when a block is + * selected. This is used to hide the sidebar toggle button when a block is not + * selected. + * + * @param {WPDataRegistry} registry Data registry. + * @return {WPDataRegistry} Modified data registry. + */ +function toggleBlockSelectedStyles( registry ) { + return { + dispatch: ( namespace ) => { + const namespaceName = + typeof namespace === 'string' ? namespace : namespace.name; + const actions = { ...registry.dispatch( namespaceName ) }; + + const originalSelectBlockAction = actions.selectBlock; + actions.selectBlock = ( ...args ) => { + _toggleBlockSelectedClass( true ); + return originalSelectBlockAction( ...args ); + }; + + const originalClearSelectedBlockAction = actions.clearSelectedBlock; + actions.clearSelectedBlock = ( ...args ) => { + _toggleBlockSelectedClass( false ); + return originalClearSelectedBlockAction( ...args ); + }; + + return actions; + }, + }; +} + +window.wp.data.use( toggleBlockSelectedStyles ); diff --git a/packages/react-native-bridge/common/gutenberg-web-single-block/editor-style-overrides.css b/packages/react-native-bridge/common/gutenberg-web-single-block/editor-style-overrides.css index 7aa208abe5537..f83b30078dfb7 100644 --- a/packages/react-native-bridge/common/gutenberg-web-single-block/editor-style-overrides.css +++ b/packages/react-native-bridge/common/gutenberg-web-single-block/editor-style-overrides.css @@ -17,14 +17,22 @@ display: none; } -/* - Hiddes the top bar header by setting its height to 0 - We can\'t remove it since the block toolbar is a child of it. - */ +/* Right align post header children as we will only display one child */ .edit-post-header { - height: 0px; - padding: 0px; - overflow: hidden; + justify-content: flex-end; +} + +/* Hide post controls unrelated to editing a single block */ +.edit-post-header__toolbar, +.edit-post-layout .edit-post-header .edit-post-header__settings > *, +.interface-pinned-items > * { + display: none; +} + +/* Display the sidebar toggle button whenever a block is selected */ +.edit-post-layout .edit-post-header .edit-post-header__settings .interface-pinned-items, +.is-block-selected .edit-post-header__settings .interface-pinned-items > button:first-child { + display: flex; } /* Move the block toolbar to the top */ @@ -69,38 +77,6 @@ display: none; } -/* - Load second button in component menu group but hide it from view. - This is to fix a Chrome-specific bug that occurs if this button is set to "display: none;" - For additional context, see: https://github.com/WordPress/gutenberg/pull/33740 -*/ -.components-dropdown-menu__menu - > .components-menu-group - > div - > button:nth-child( 2 ) { - display: block; - min-height: 0; - height: 0; - padding: 0; -} - -.components-menu-group > div > button:nth-child( 2 ) > span { - display: none; -} - -.components-button:focus:not( :disabled ) { - box-shadow: none; -} - -/* Remove \'delete block\' button inside \'...\' button in block toolbar */ -.components-dropdown-menu__menu > div:not(:first-child) { - display: none; -} - -.components-dropdown-menu__menu > div:first-child { - padding-bottom: 0; -} - /* Some Themes can overwrite values on \'editor-styles-wrapper\'. This will ensure that the top padding is correct on our single-block version of gutenberg web. From fcfb7d4f4898fd7ee50e149e19994497beea5331 Mon Sep 17 00:00:00 2001 From: David Calhoun <438664+dcalhoun@users.noreply.github.com> Date: Fri, 24 Feb 2023 16:12:50 -0500 Subject: [PATCH 2/9] fix: Disable white space stripping that breaks nested CSS selectors CSS selectors rely upon a single white space between selectors to represent an ancestor relationship. Globally removing white space in the stylesheet breaks this functionality, as it transforms the selector to target a single element with all the selectors. The white space stripping should likely be replaced with a proper CSS minification long term. --- .../ReactNativeGutenbergBridge/GutenbergWebViewActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native-bridge/android/react-native-bridge/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/GutenbergWebViewActivity.java b/packages/react-native-bridge/android/react-native-bridge/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/GutenbergWebViewActivity.java index 2b3302b75e257..1e5c7a0f52c7b 100644 --- a/packages/react-native-bridge/android/react-native-bridge/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/GutenbergWebViewActivity.java +++ b/packages/react-native-bridge/android/react-native-bridge/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/GutenbergWebViewActivity.java @@ -327,7 +327,7 @@ private void injectCssScript() { mWebView.evaluateJavascript(injectCssScript, message -> { if (message != null) { String editorStyle = getFileContentFromAssets("gutenberg-web-single-block/editor-style-overrides.css"); - editorStyle = removeWhiteSpace(removeNewLines(editorStyle)); + editorStyle = removeNewLines(editorStyle); evaluateJavaScript(String.format(INJECT_CSS_SCRIPT_TEMPLATE, editorStyle)); String injectWPBarsCssScript = getFileContentFromAssets("gutenberg-web-single-block/wp-bar-override.css"); From a5b08b149fe89cb677794c0ba9ee05a26a90abd2 Mon Sep 17 00:00:00 2001 From: David Calhoun <438664+dcalhoun@users.noreply.github.com> Date: Fri, 24 Feb 2023 16:15:43 -0500 Subject: [PATCH 3/9] fix: Hide block actions unrelated to editing a single block Hide the entire "block settings" drop-down menu now that we no longer rely upon it to access the "Show more settings" menu option that was removed entirely. --- .../gutenberg-web-single-block/editor-style-overrides.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/react-native-bridge/common/gutenberg-web-single-block/editor-style-overrides.css b/packages/react-native-bridge/common/gutenberg-web-single-block/editor-style-overrides.css index f83b30078dfb7..f8f2e8fe2b4cd 100644 --- a/packages/react-native-bridge/common/gutenberg-web-single-block/editor-style-overrides.css +++ b/packages/react-native-bridge/common/gutenberg-web-single-block/editor-style-overrides.css @@ -44,6 +44,11 @@ top: 0px; } +/* Hide block actions unrelated to editing a single block */ +.block-editor-block-settings-menu { + display: none; +} + /* Moves the whole editor to the top. There was an extra top margin after removing the WP Admin bar. From 25dddd00244a7ce2d972dd6f0e13e5328fe1c120 Mon Sep 17 00:00:00 2001 From: David Calhoun <438664+dcalhoun@users.noreply.github.com> Date: Fri, 24 Feb 2023 16:18:19 -0500 Subject: [PATCH 4/9] refactor: Relocate script toggling block settings visibility This relates more to editor behavior than the post content. --- .../content-functions.js | 46 ----- .../editor-behavior-overrides.js | 175 ++++++++++++------ .../FallbackJavascriptInjection.swift | 2 + ...utenbergWebSingleBlockViewController.swift | 1 + .../react-native-bridge/ios/SourceFile.swift | 1 + 5 files changed, 124 insertions(+), 101 deletions(-) diff --git a/packages/react-native-bridge/common/gutenberg-web-single-block/content-functions.js b/packages/react-native-bridge/common/gutenberg-web-single-block/content-functions.js index 8404aa99f22bf..d6e7d51375d44 100644 --- a/packages/react-native-bridge/common/gutenberg-web-single-block/content-functions.js +++ b/packages/react-native-bridge/common/gutenberg-web-single-block/content-functions.js @@ -76,49 +76,3 @@ window.startObservingGutenberg = () => { window.subscribed = true; } }; - -const editor = document.querySelector( '#editor' ); - -function _toggleBlockSelectedClass( isBlockSelected ) { - if ( isBlockSelected ) { - editor.classList.add( 'is-block-selected' ); - } else { - editor.classList.remove( 'is-block-selected' ); - } -} - -/** @typedef {import('@wordpress/data').WPDataRegistry} WPDataRegistry */ - -/** - * Toggle the `is-block-selected` class on the editor container when a block is - * selected. This is used to hide the sidebar toggle button when a block is not - * selected. - * - * @param {WPDataRegistry} registry Data registry. - * @return {WPDataRegistry} Modified data registry. - */ -function toggleBlockSelectedStyles( registry ) { - return { - dispatch: ( namespace ) => { - const namespaceName = - typeof namespace === 'string' ? namespace : namespace.name; - const actions = { ...registry.dispatch( namespaceName ) }; - - const originalSelectBlockAction = actions.selectBlock; - actions.selectBlock = ( ...args ) => { - _toggleBlockSelectedClass( true ); - return originalSelectBlockAction( ...args ); - }; - - const originalClearSelectedBlockAction = actions.clearSelectedBlock; - actions.clearSelectedBlock = ( ...args ) => { - _toggleBlockSelectedClass( false ); - return originalClearSelectedBlockAction( ...args ); - }; - - return actions; - }, - }; -} - -window.wp.data.use( toggleBlockSelectedStyles ); diff --git a/packages/react-native-bridge/common/gutenberg-web-single-block/editor-behavior-overrides.js b/packages/react-native-bridge/common/gutenberg-web-single-block/editor-behavior-overrides.js index 0cfa0e9985fa0..e20eb8ae4b5ad 100644 --- a/packages/react-native-bridge/common/gutenberg-web-single-block/editor-behavior-overrides.js +++ b/packages/react-native-bridge/common/gutenberg-web-single-block/editor-behavior-overrides.js @@ -1,59 +1,124 @@ -// Listeners for native context menu visibility changes. -let isContextMenuVisible = false; -const hideContextMenuListeners = []; - -window.onShowContextMenu = () => { - isContextMenuVisible = true; -}; -window.onHideContextMenu = () => { - isContextMenuVisible = false; - while ( hideContextMenuListeners.length > 0 ) { - const listener = hideContextMenuListeners.pop(); - listener(); - } -}; - -/* -This is a fix for a text selection quirk in the UBE. -It notifies the Android app to dismiss the text selection -context menu when certain menu items are tapped. This is -done via the 'hideTextSelectionContextMenu' method, which -is sent back to the Android app, where the dismissal is -then handle. See PR for further details: -https://github.com/WordPress/gutenberg/pull/34668 -*/ -window.addEventListener( - 'click', - ( event ) => { - const selected = document.getSelection(); - if ( ! isContextMenuVisible || ! selected || ! selected.toString() ) { - return; +/** + * Detects whether the user agent is Android. + * + * @return {boolean} Whether the user agent is Android. + */ +function isAndroid() { + return !! window.navigator.userAgent.match( /Android/ ); +} + +/** + * This is a fix for a text selection quirk in the UBE. It notifies the Android + * app to dismiss the text selection context menu when certain menu items are + * tapped. This is done via the 'hideTextSelectionContextMenu' method, which + * is sent back to the Android app, where the dismissal is then handle. + * + * @return {void} + * @see https://github.com/WordPress/gutenberg/pull/34668 + */ +function manageTextSelectonContextMenu() { + // Listeners for native context menu visibility changes. + let isContextMenuVisible = false; + const hideContextMenuListeners = []; + + window.onShowContextMenu = () => { + isContextMenuVisible = true; + }; + window.onHideContextMenu = () => { + isContextMenuVisible = false; + while ( hideContextMenuListeners.length > 0 ) { + const listener = hideContextMenuListeners.pop(); + listener(); } + }; - // Check if the event is triggered by a dropdown - // toggle button. - const dropdownToggles = document.querySelectorAll( - '.components-dropdown-menu > button' - ); - let currentToggle; - for ( const node of dropdownToggles.values() ) { - if ( node.contains( event.target ) ) { - currentToggle = node; - break; + window.addEventListener( + 'click', + ( event ) => { + const selected = document.getSelection(); + if ( + ! isContextMenuVisible || + ! selected || + ! selected.toString() + ) { + return; } - } - // Hide text selection context menu when the click - // is triggered by a dropdown toggle. - // - // NOTE: The event propagation is prevented because - // it will be dispatched after the context menu - // is hidden. - if ( currentToggle ) { - event.stopPropagation(); - hideContextMenuListeners.push( () => currentToggle.click() ); - window.wpwebkit.hideTextSelectionContextMenu(); - } - }, - true -); + // Check if the event is triggered by a dropdown + // toggle button. + const dropdownToggles = document.querySelectorAll( + '.components-dropdown-menu > button' + ); + let currentToggle; + for ( const node of dropdownToggles.values() ) { + if ( node.contains( event.target ) ) { + currentToggle = node; + break; + } + } + + // Hide text selection context menu when the click + // is triggered by a dropdown toggle. + // + // NOTE: The event propagation is prevented because + // it will be dispatched after the context menu + // is hidden. + if ( currentToggle ) { + event.stopPropagation(); + hideContextMenuListeners.push( () => currentToggle.click() ); + window.wpwebkit.hideTextSelectionContextMenu(); + } + }, + true + ); +} + +if ( isAndroid() ) { + manageTextSelectonContextMenu(); +} + +const editor = document.querySelector( '#editor' ); + +function _toggleBlockSelectedClass( isBlockSelected ) { + if ( isBlockSelected ) { + editor.classList.add( 'is-block-selected' ); + } else { + editor.classList.remove( 'is-block-selected' ); + } +} + +/** @typedef {import('@wordpress/data').WPDataRegistry} WPDataRegistry */ + +/** + * Toggle the `is-block-selected` class on the editor container when a block is + * selected. This is used to hide the sidebar toggle button when a block is not + * selected. + * + * @param {WPDataRegistry} registry Data registry. + * @return {WPDataRegistry} Modified data registry. + */ +function toggleBlockSelectedStyles( registry ) { + return { + dispatch: ( namespace ) => { + const namespaceName = + typeof namespace === 'string' ? namespace : namespace.name; + const actions = { ...registry.dispatch( namespaceName ) }; + + const originalSelectBlockAction = actions.selectBlock; + actions.selectBlock = ( ...args ) => { + _toggleBlockSelectedClass( true ); + return originalSelectBlockAction( ...args ); + }; + + const originalClearSelectedBlockAction = actions.clearSelectedBlock; + actions.clearSelectedBlock = ( ...args ) => { + _toggleBlockSelectedClass( false ); + return originalClearSelectedBlockAction( ...args ); + }; + + return actions; + }, + }; +} + +window.wp.data.use( toggleBlockSelectedStyles ); diff --git a/packages/react-native-bridge/ios/GutenbergWebFallback/FallbackJavascriptInjection.swift b/packages/react-native-bridge/ios/GutenbergWebFallback/FallbackJavascriptInjection.swift index 13ae9dd1f0573..3d0d9e021fb82 100644 --- a/packages/react-native-bridge/ios/GutenbergWebFallback/FallbackJavascriptInjection.swift +++ b/packages/react-native-bridge/ios/GutenbergWebFallback/FallbackJavascriptInjection.swift @@ -19,6 +19,7 @@ public struct FallbackJavascriptInjection { public let preventAutosavesScript: WKUserScript public let getHtmlContentScript = "window.getHTMLPostContent()".toJsScript() public let gutenbergObserverScript: WKUserScript + public let editorBehaviorScript: WKUserScript /// Init an instance of GutenbergWebJavascriptInjection or throws if any of the required sources doesn't exist. /// This helps to cach early any possible error due to missing source files. @@ -44,6 +45,7 @@ public struct FallbackJavascriptInjection { injectEditorCssScript = try getInjectCssScript(with: .editorStyle) preventAutosavesScript = try script(with: .preventAutosaves) gutenbergObserverScript = try script(with: .gutenbergObserver) + editorBehaviorScript = try script(with: .editorBehavior) let localStorageJsonString = try SourceFile.localStorage.getContent().removingSpacesAndNewLines() let scriptString = String(format: injectLocalStorageScriptTemplate, userId, localStorageJsonString) diff --git a/packages/react-native-bridge/ios/GutenbergWebFallback/GutenbergWebSingleBlockViewController.swift b/packages/react-native-bridge/ios/GutenbergWebFallback/GutenbergWebSingleBlockViewController.swift index aa83095f058f1..8d11028728a89 100644 --- a/packages/react-native-bridge/ios/GutenbergWebFallback/GutenbergWebSingleBlockViewController.swift +++ b/packages/react-native-bridge/ios/GutenbergWebFallback/GutenbergWebSingleBlockViewController.swift @@ -76,6 +76,7 @@ open class GutenbergWebSingleBlockViewController: UIViewController { onGutenbergReadyScripts().forEach(evaluateJavascript) evaluateJavascript(jsInjection.preventAutosavesScript) evaluateJavascript(jsInjection.insertBlockScript) + evaluateJavascript(jsInjection.editorBehaviorScript) DispatchQueue.main.async { [weak self] in self?.removeCoverViewAnimated() } diff --git a/packages/react-native-bridge/ios/SourceFile.swift b/packages/react-native-bridge/ios/SourceFile.swift index 2f75e22278c0f..c9bac0b66e7f6 100644 --- a/packages/react-native-bridge/ios/SourceFile.swift +++ b/packages/react-native-bridge/ios/SourceFile.swift @@ -53,4 +53,5 @@ extension SourceFile { static let preventAutosaves = SourceFile(name: "prevent-autosaves", type: .js) static let gutenbergObserver = SourceFile(name: "gutenberg-observer", type: .js) static let supportedBlocks = SourceFile(name: "supported-blocks", type: .json) + static let editorBehavior = SourceFile(name: "editor-behavior-overrides", type: .js) } From ea8d18e4437f26bdadf3a18246c08ab97572cd33 Mon Sep 17 00:00:00 2001 From: David Calhoun <438664+dcalhoun@users.noreply.github.com> Date: Mon, 27 Feb 2023 16:45:11 -0500 Subject: [PATCH 5/9] fix: Apply styles and script to editor canvas iframe The editor canvas now relies upon an iframe. It is not possible to style elements within an iframe from the parent context. This copies the styles from the parent conext to the iframe. Additionally, the logic selecting the first block also failed due to the block not existing when it was invoked. This relocates that logic until after the iframe is ready. --- .../GutenbergWebViewActivity.java | 8 ++--- .../content-functions.js | 7 ---- .../editor-behavior-overrides.js | 33 +++++++++++++++++++ .../gutenberg-web-single-block/inject-css.js | 3 +- .../FallbackJavascriptInjection.swift | 2 +- .../react-native-bridge/ios/SourceFile.swift | 4 +++ 6 files changed, 44 insertions(+), 13 deletions(-) diff --git a/packages/react-native-bridge/android/react-native-bridge/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/GutenbergWebViewActivity.java b/packages/react-native-bridge/android/react-native-bridge/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/GutenbergWebViewActivity.java index 1e5c7a0f52c7b..c4ae7e350f4cb 100644 --- a/packages/react-native-bridge/android/react-native-bridge/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/GutenbergWebViewActivity.java +++ b/packages/react-native-bridge/android/react-native-bridge/src/main/java/org/wordpress/mobile/ReactNativeGutenbergBridge/GutenbergWebViewActivity.java @@ -40,7 +40,7 @@ public class GutenbergWebViewActivity extends AppCompatActivity { public static final String ARG_BLOCK_CONTENT = "block_content"; private static final String INJECT_LOCAL_STORAGE_SCRIPT_TEMPLATE = "localStorage.setItem('WP_DATA_USER_%d','%s')"; - private static final String INJECT_CSS_SCRIPT_TEMPLATE = "window.injectCss('%s')"; + private static final String INJECT_CSS_SCRIPT_TEMPLATE = "window.injectCss('%s', '%s')"; private static final String INJECT_GET_HTML_POST_CONTENT_SCRIPT = "window.getHTMLPostContent();"; private static final String INJECT_ON_SHOW_CONTEXT_MENU_SCRIPT = "window.onShowContextMenu();"; private static final String INJECT_ON_HIDE_CONTEXT_MENU_SCRIPT = "window.onHideContextMenu();"; @@ -328,15 +328,15 @@ private void injectCssScript() { if (message != null) { String editorStyle = getFileContentFromAssets("gutenberg-web-single-block/editor-style-overrides.css"); editorStyle = removeNewLines(editorStyle); - evaluateJavaScript(String.format(INJECT_CSS_SCRIPT_TEMPLATE, editorStyle)); + evaluateJavaScript(String.format(INJECT_CSS_SCRIPT_TEMPLATE, editorStyle, "editor-style-overrides")); String injectWPBarsCssScript = getFileContentFromAssets("gutenberg-web-single-block/wp-bar-override.css"); injectWPBarsCssScript = removeWhiteSpace(removeNewLines(injectWPBarsCssScript)); - evaluateJavaScript(String.format(INJECT_CSS_SCRIPT_TEMPLATE, injectWPBarsCssScript)); + evaluateJavaScript(String.format(INJECT_CSS_SCRIPT_TEMPLATE, injectWPBarsCssScript, "wp-bar-override")); String injectExternalCssScript = getOnGutenbergReadyExternalStyles(); injectExternalCssScript = removeWhiteSpace(removeNewLines(injectExternalCssScript)); - evaluateJavaScript(String.format(INJECT_CSS_SCRIPT_TEMPLATE, injectExternalCssScript)); + evaluateJavaScript(String.format(INJECT_CSS_SCRIPT_TEMPLATE, injectExternalCssScript, "external-styles")); } }); } diff --git a/packages/react-native-bridge/common/gutenberg-web-single-block/content-functions.js b/packages/react-native-bridge/common/gutenberg-web-single-block/content-functions.js index d6e7d51375d44..611701c237b09 100644 --- a/packages/react-native-bridge/common/gutenberg-web-single-block/content-functions.js +++ b/packages/react-native-bridge/common/gutenberg-web-single-block/content-functions.js @@ -25,19 +25,12 @@ window.getHTMLPostContent = () => { }; window.insertBlock = ( blockHTML ) => { - const { blockEditorSelect, blockEditorDispatch } = - window.getBlockEditorStore(); - // Setup the editor with the inserted block. const post = window.wp.data.select( 'core/editor' ).getCurrentPost(); window.wp.data .dispatch( 'core/editor' ) .setupEditor( post, { content: blockHTML } ); - // Select the first block. - const clientId = blockEditorSelect.getBlocks()[ 0 ].clientId; - blockEditorDispatch.selectBlock( clientId ); - window.contentIncerted = true; }; diff --git a/packages/react-native-bridge/common/gutenberg-web-single-block/editor-behavior-overrides.js b/packages/react-native-bridge/common/gutenberg-web-single-block/editor-behavior-overrides.js index e20eb8ae4b5ad..8a6066d81b949 100644 --- a/packages/react-native-bridge/common/gutenberg-web-single-block/editor-behavior-overrides.js +++ b/packages/react-native-bridge/common/gutenberg-web-single-block/editor-behavior-overrides.js @@ -122,3 +122,36 @@ function toggleBlockSelectedStyles( registry ) { } window.wp.data.use( toggleBlockSelectedStyles ); + +// The editor-canvas iframe relies upon `srcdoc`, which does not trigger a +// `load` event. Thus, we must poll for the iframe to be ready. +let editorOverridesAttempts = 0; +const editorOverridesInterval = setInterval( () => { + editorOverridesAttempts++; + const editorCanvasFrameHead = document.querySelector( + 'iframe[name="editor-canvas"]' + ).contentWindow.document.head; + + if ( editorCanvasFrameHead ) { + clearInterval( editorOverridesInterval ); + + // Clone the editor styles so that they can be copied to the iframe, as + // elements within an iframe cannot be styled from the parent context. + const editorOverrideStyles = document + .querySelector( '#editor-style-overrides' ) + .cloneNode( true ); + editorOverrideStyles.id = 'editor-styles-overrides-2'; + editorCanvasFrameHead.appendChild( editorOverrideStyles ); + + // Select the first block. + const { blockEditorSelect, blockEditorDispatch } = + window.getBlockEditorStore(); + const clientId = blockEditorSelect.getBlocks()[ 0 ].clientId; + blockEditorDispatch.selectBlock( clientId ); + } + + // Safeguard against infinite loop. + if ( editorOverridesAttempts > 30 ) { + clearInterval( editorOverridesInterval ); + } +}, 300 ); diff --git a/packages/react-native-bridge/common/gutenberg-web-single-block/inject-css.js b/packages/react-native-bridge/common/gutenberg-web-single-block/inject-css.js index 60ac677bd20f5..483e742e780bc 100644 --- a/packages/react-native-bridge/common/gutenberg-web-single-block/inject-css.js +++ b/packages/react-native-bridge/common/gutenberg-web-single-block/inject-css.js @@ -1,8 +1,9 @@ const injectCss = ` -window.injectCss = (css) => { +window.injectCss = (css, id) => { const style = document.createElement('style'); style.innerHTML = css; style.type = 'text/css'; + style.id = id; document.head.appendChild(style); } `; diff --git a/packages/react-native-bridge/ios/GutenbergWebFallback/FallbackJavascriptInjection.swift b/packages/react-native-bridge/ios/GutenbergWebFallback/FallbackJavascriptInjection.swift index 3d0d9e021fb82..ee75dc0968a58 100644 --- a/packages/react-native-bridge/ios/GutenbergWebFallback/FallbackJavascriptInjection.swift +++ b/packages/react-native-bridge/ios/GutenbergWebFallback/FallbackJavascriptInjection.swift @@ -32,7 +32,7 @@ public struct FallbackJavascriptInjection { } func getInjectCssScript(with source: SourceFile) throws -> WKUserScript { - "window.injectCss(`\(try source.getContent())`)".toJsScript() + "window.injectCss(`\(try source.getContent())`, `\(source.getName())`)".toJsScript() } userContentScripts = [ diff --git a/packages/react-native-bridge/ios/SourceFile.swift b/packages/react-native-bridge/ios/SourceFile.swift index c9bac0b66e7f6..2386f84580464 100644 --- a/packages/react-native-bridge/ios/SourceFile.swift +++ b/packages/react-native-bridge/ios/SourceFile.swift @@ -29,6 +29,10 @@ public struct SourceFile { } extension SourceFile { + public func getName() -> String { + return self.name + } + public func jsScript(with argument: String? = nil) throws -> WKUserScript { let content = try getContent() let formatted = String(format: content, argument ?? []) From 7c1773be08eb9cb577699f45d9477ba5cc8b9ce8 Mon Sep 17 00:00:00 2001 From: David Calhoun <438664+dcalhoun@users.noreply.github.com> Date: Mon, 27 Feb 2023 19:26:17 -0500 Subject: [PATCH 6/9] fix: Expand conditional checks for partial DOM trees On Android, there were times where the iframe was present, but the nested window was not yet ready. --- .../editor-behavior-overrides.js | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/packages/react-native-bridge/common/gutenberg-web-single-block/editor-behavior-overrides.js b/packages/react-native-bridge/common/gutenberg-web-single-block/editor-behavior-overrides.js index 8a6066d81b949..c62d775fa5715 100644 --- a/packages/react-native-bridge/common/gutenberg-web-single-block/editor-behavior-overrides.js +++ b/packages/react-native-bridge/common/gutenberg-web-single-block/editor-behavior-overrides.js @@ -128,30 +128,42 @@ window.wp.data.use( toggleBlockSelectedStyles ); let editorOverridesAttempts = 0; const editorOverridesInterval = setInterval( () => { editorOverridesAttempts++; - const editorCanvasFrameHead = document.querySelector( + const editorOverrideStyles = document.querySelector( + '#editor-style-overrides' + ); + const editorCanvasFrame = document.querySelector( 'iframe[name="editor-canvas"]' - ).contentWindow.document.head; + ); - if ( editorCanvasFrameHead ) { + if ( + editorOverrideStyles && + editorCanvasFrame && + editorCanvasFrame.contentWindow && + editorCanvasFrame.contentWindow.document && + editorCanvasFrame.contentWindow.document.head + ) { clearInterval( editorOverridesInterval ); // Clone the editor styles so that they can be copied to the iframe, as // elements within an iframe cannot be styled from the parent context. - const editorOverrideStyles = document - .querySelector( '#editor-style-overrides' ) - .cloneNode( true ); - editorOverrideStyles.id = 'editor-styles-overrides-2'; - editorCanvasFrameHead.appendChild( editorOverrideStyles ); + const editorOverrideStylesClone = + editorOverrideStyles.cloneNode( true ); + editorOverrideStylesClone.id = 'editor-styles-overrides-2'; + editorCanvasFrame.contentWindow.document.head.appendChild( + editorOverrideStylesClone + ); // Select the first block. const { blockEditorSelect, blockEditorDispatch } = window.getBlockEditorStore(); - const clientId = blockEditorSelect.getBlocks()[ 0 ].clientId; - blockEditorDispatch.selectBlock( clientId ); + const firstBlock = blockEditorSelect.getBlocks()[ 0 ]; + if ( firstBlock ) { + blockEditorDispatch.selectBlock( firstBlock.clientId ); + } } - // Safeguard against infinite loop. - if ( editorOverridesAttempts > 30 ) { + // Safeguard against an infinite loop. + if ( editorOverridesAttempts > 100 ) { clearInterval( editorOverridesInterval ); } }, 300 ); From b7f10f58bf2c4fd1bbb4dab771fdaf765f58fe4e Mon Sep 17 00:00:00 2001 From: David Calhoun <438664+dcalhoun@users.noreply.github.com> Date: Mon, 27 Feb 2023 20:16:58 -0500 Subject: [PATCH 7/9] refactor: Rename for brevity --- .../editor-behavior-overrides.js | 38 +++++++++---------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/packages/react-native-bridge/common/gutenberg-web-single-block/editor-behavior-overrides.js b/packages/react-native-bridge/common/gutenberg-web-single-block/editor-behavior-overrides.js index c62d775fa5715..a9d3921a6b664 100644 --- a/packages/react-native-bridge/common/gutenberg-web-single-block/editor-behavior-overrides.js +++ b/packages/react-native-bridge/common/gutenberg-web-single-block/editor-behavior-overrides.js @@ -125,32 +125,30 @@ window.wp.data.use( toggleBlockSelectedStyles ); // The editor-canvas iframe relies upon `srcdoc`, which does not trigger a // `load` event. Thus, we must poll for the iframe to be ready. -let editorOverridesAttempts = 0; -const editorOverridesInterval = setInterval( () => { - editorOverridesAttempts++; - const editorOverrideStyles = document.querySelector( - '#editor-style-overrides' - ); - const editorCanvasFrame = document.querySelector( +let overrideAttempts = 0; +const overrideInterval = setInterval( () => { + overrideAttempts++; + const overrideStyles = document.querySelector( '#editor-style-overrides' ); + const canvasIframe = document.querySelector( 'iframe[name="editor-canvas"]' ); if ( - editorOverrideStyles && - editorCanvasFrame && - editorCanvasFrame.contentWindow && - editorCanvasFrame.contentWindow.document && - editorCanvasFrame.contentWindow.document.head + overrideStyles && + canvasIframe && + canvasIframe.contentWindow && + canvasIframe.contentWindow.document && + canvasIframe.contentWindow.document.head && + canvasIframe.contentWindow.document.head.childElementCount > 0 ) { - clearInterval( editorOverridesInterval ); + clearInterval( overrideInterval ); // Clone the editor styles so that they can be copied to the iframe, as // elements within an iframe cannot be styled from the parent context. - const editorOverrideStylesClone = - editorOverrideStyles.cloneNode( true ); - editorOverrideStylesClone.id = 'editor-styles-overrides-2'; - editorCanvasFrame.contentWindow.document.head.appendChild( - editorOverrideStylesClone + const overrideStylesClone = overrideStyles.cloneNode( true ); + overrideStylesClone.id = 'editor-styles-overrides-2'; + canvasIframe.contentWindow.document.head.appendChild( + overrideStylesClone ); // Select the first block. @@ -163,7 +161,7 @@ const editorOverridesInterval = setInterval( () => { } // Safeguard against an infinite loop. - if ( editorOverridesAttempts > 100 ) { - clearInterval( editorOverridesInterval ); + if ( overrideAttempts > 100 ) { + clearInterval( overrideInterval ); } }, 300 ); From a7eb9a220f7105d6a194b0e0228fbbe2484ae727 Mon Sep 17 00:00:00 2001 From: David Calhoun <438664+dcalhoun@users.noreply.github.com> Date: Mon, 27 Feb 2023 20:21:52 -0500 Subject: [PATCH 8/9] fix: Avoid React removing appended iframe styles Append the styles to the `document` element, as React will remove the mutation to the `head` element. --- .../editor-behavior-overrides.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/react-native-bridge/common/gutenberg-web-single-block/editor-behavior-overrides.js b/packages/react-native-bridge/common/gutenberg-web-single-block/editor-behavior-overrides.js index a9d3921a6b664..09dcd6447824d 100644 --- a/packages/react-native-bridge/common/gutenberg-web-single-block/editor-behavior-overrides.js +++ b/packages/react-native-bridge/common/gutenberg-web-single-block/editor-behavior-overrides.js @@ -136,10 +136,8 @@ const overrideInterval = setInterval( () => { if ( overrideStyles && canvasIframe && - canvasIframe.contentWindow && - canvasIframe.contentWindow.document && - canvasIframe.contentWindow.document.head && - canvasIframe.contentWindow.document.head.childElementCount > 0 + canvasIframe.contentDocument && + canvasIframe.contentDocument.documentElement ) { clearInterval( overrideInterval ); @@ -147,7 +145,9 @@ const overrideInterval = setInterval( () => { // elements within an iframe cannot be styled from the parent context. const overrideStylesClone = overrideStyles.cloneNode( true ); overrideStylesClone.id = 'editor-styles-overrides-2'; - canvasIframe.contentWindow.document.head.appendChild( + // Append to document rather than the head, as React will remove this + // mutation. + canvasIframe.contentDocument.documentElement.appendChild( overrideStylesClone ); From 723362338c0584713d97e6ea46805e469ebe31b9 Mon Sep 17 00:00:00 2001 From: David Calhoun <438664+dcalhoun@users.noreply.github.com> Date: Tue, 28 Feb 2023 11:57:12 -0500 Subject: [PATCH 9/9] docs: Add change log entry --- packages/react-native-editor/CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/react-native-editor/CHANGELOG.md b/packages/react-native-editor/CHANGELOG.md index 8b8c395a67d9e..e34f5bfd753ee 100644 --- a/packages/react-native-editor/CHANGELOG.md +++ b/packages/react-native-editor/CHANGELOG.md @@ -12,6 +12,9 @@ For each user feature we should also add a importance categorization label to i ## Unreleased - [*] Add metadata parameter to media upload events [#48103] +## 1.89.1 +- [*] Fix inaccessible block settings within the unsupported block editor [#48435] + ## 1.89.0 * No User facing changes *