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 b67cae08e932eb..ccc3682a2c661e 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 @@ -4,6 +4,7 @@ import android.graphics.Bitmap; import android.os.Bundle; import android.os.Handler; +import android.view.ActionMode; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; @@ -24,7 +25,7 @@ import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; -import org.wordpress.android.util.AppLog;; +import org.wordpress.android.util.AppLog; import org.wordpress.mobile.FileUtils; import java.util.ArrayList; @@ -41,6 +42,8 @@ public class GutenbergWebViewActivity extends AppCompatActivity { 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_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();"; private static final String JAVA_SCRIPT_INTERFACE_NAME = "wpwebkit"; protected WebView mWebView; @@ -49,6 +52,7 @@ public class GutenbergWebViewActivity extends AppCompatActivity { protected TextView mForegroundViewTitle; protected TextView mForegroundViewSubtitle; protected boolean mIsRedirected; + protected ActionMode mActionMode = null; private ProgressBar mProgressBar; private boolean mIsGutenbergReady; @@ -85,6 +89,24 @@ private void showTroubleshootingInstructions() { mForegroundViewImage.setVisibility(ImageView.VISIBLE); } + @Override + public void onActionModeStarted(ActionMode mode) { + if (mActionMode == null) { + mActionMode = mode; + } + mWebView.evaluateJavascript(INJECT_ON_SHOW_CONTEXT_MENU_SCRIPT, + value -> AppLog.e(AppLog.T.EDITOR, value)); + super.onActionModeStarted(mode); + } + + @Override + public void onActionModeFinished(ActionMode mode) { + mActionMode = null; + mWebView.evaluateJavascript(INJECT_ON_HIDE_CONTEXT_MENU_SCRIPT, + value -> AppLog.e(AppLog.T.EDITOR, value)); + super.onActionModeFinished(mode); + } + @SuppressLint("SetJavaScriptEnabled") protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -267,6 +289,9 @@ public void onPageFinished(WebView view, String url) { String injectGutenbergObserver = getFileContentFromAssets("gutenberg-web-single-block/gutenberg-observer.js"); evaluateJavaScript(injectGutenbergObserver); + + String behaviorOverrides = getFileContentFromAssets("gutenberg-web-single-block/editor-behavior-overrides.js"); + evaluateJavaScript(behaviorOverrides); } }); } @@ -400,5 +425,14 @@ public void postMessage(String content) { public void gutenbergReady() { GutenbergWebViewActivity.this.runOnUiThread(() -> onGutenbergReady()); } + + @JavascriptInterface + public void hideTextSelectionContextMenu() { + if (mActionMode != null) { + GutenbergWebViewActivity.this.runOnUiThread(() -> { + GutenbergWebViewActivity.this.mActionMode.finish(); + }); + } + } } } 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 new file mode 100644 index 00000000000000..a456549b9501ab --- /dev/null +++ b/packages/react-native-bridge/common/gutenberg-web-single-block/editor-behavior-overrides.js @@ -0,0 +1,59 @@ +// 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; + } + + // 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 +); diff --git a/packages/react-native-editor/CHANGELOG.md b/packages/react-native-editor/CHANGELOG.md index 47263df34f0f5c..422a0b07fe4ca4 100644 --- a/packages/react-native-editor/CHANGELOG.md +++ b/packages/react-native-editor/CHANGELOG.md @@ -11,6 +11,7 @@ For each user feature we should also add a importance categorization label to i ## Unreleased - [*] [Embed block] Fix inline preview cut-off when editing URL [#35321] +- [*] [Unsupported Block Editor] Fix text selection bug for Android [#34668] ## 1.63.0 - [**] [Embed block] Add the top 5 specific embed blocks to the Block inserter list [#34967]