-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: Enable access to block settings within UBE (#48435)
* 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. * 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. * 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. * refactor: Relocate script toggling block settings visibility This relates more to editor behavior than the post content. * 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. * 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. * refactor: Rename for brevity * fix: Avoid React removing appended iframe styles Append the styles to the `document` element, as React will remove the mutation to the `head` element. * docs: Add change log entry
- Loading branch information
Showing
9 changed files
with
201 additions
and
107 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
216 changes: 162 additions & 54 deletions
216
packages/react-native-bridge/common/gutenberg-web-single-block/editor-behavior-overrides.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,59 +1,167 @@ | ||
// 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(); | ||
// 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 ); | ||
|
||
// 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 overrideAttempts = 0; | ||
const overrideInterval = setInterval( () => { | ||
overrideAttempts++; | ||
const overrideStyles = document.querySelector( '#editor-style-overrides' ); | ||
const canvasIframe = document.querySelector( | ||
'iframe[name="editor-canvas"]' | ||
); | ||
|
||
if ( | ||
overrideStyles && | ||
canvasIframe && | ||
canvasIframe.contentDocument && | ||
canvasIframe.contentDocument.documentElement | ||
) { | ||
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 overrideStylesClone = overrideStyles.cloneNode( true ); | ||
overrideStylesClone.id = 'editor-styles-overrides-2'; | ||
// Append to document rather than the head, as React will remove this | ||
// mutation. | ||
canvasIframe.contentDocument.documentElement.appendChild( | ||
overrideStylesClone | ||
); | ||
|
||
// Select the first block. | ||
const { blockEditorSelect, blockEditorDispatch } = | ||
window.getBlockEditorStore(); | ||
const firstBlock = blockEditorSelect.getBlocks()[ 0 ]; | ||
if ( firstBlock ) { | ||
blockEditorDispatch.selectBlock( firstBlock.clientId ); | ||
} | ||
}, | ||
true | ||
); | ||
} | ||
|
||
// Safeguard against an infinite loop. | ||
if ( overrideAttempts > 100 ) { | ||
clearInterval( overrideInterval ); | ||
} | ||
}, 300 ); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
3 changes: 2 additions & 1 deletion
3
packages/react-native-bridge/common/gutenberg-web-single-block/inject-css.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.