diff --git a/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecHtmlWithCursorEvent.java b/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecHtmlWithCursorEvent.java new file mode 100644 index 00000000000000..95cb48710bcddf --- /dev/null +++ b/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecHtmlWithCursorEvent.java @@ -0,0 +1,46 @@ +package org.wordpress.mobile.ReactNativeAztec; + +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.WritableMap; +import com.facebook.react.uimanager.events.Event; +import com.facebook.react.uimanager.events.RCTEventEmitter; + +/** + * Event emitted by AztecText native view when full content with cursor is requested by JS + */ +class ReactAztecHtmlWithCursorEvent extends Event { + + private static final String EVENT_NAME = "topHTMLWithCursorRequested"; + + private String mText; + private int mCursorPosition; + + public ReactAztecHtmlWithCursorEvent(int viewId, String text, int cursorPosition) { + super(viewId); + mText = text; + mCursorPosition = cursorPosition; + } + + @Override + public String getEventName() { + return EVENT_NAME; + } + + @Override + public boolean canCoalesce() { + return false; + } + + @Override + public void dispatch(RCTEventEmitter rctEventEmitter) { + rctEventEmitter.receiveEvent(getViewTag(), getEventName(), serializeEventData()); + } + + private WritableMap serializeEventData() { + WritableMap eventData = Arguments.createMap(); + eventData.putInt("target", getViewTag()); + eventData.putString("text", mText); + eventData.putInt("cursorPosition", mCursorPosition); + return eventData; + } +} diff --git a/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecManager.java b/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecManager.java index 3b33f795ad6b31..155f240e714790 100644 --- a/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecManager.java +++ b/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecManager.java @@ -81,6 +81,11 @@ public Map getExportedCustomBubblingEventTypeConstants() { MapBuilder.of( "phasedRegistrationNames", MapBuilder.of("bubbled", "onEnter"))) + .put( + "topHTMLWithCursorRequested", + MapBuilder.of( + "phasedRegistrationNames", + MapBuilder.of("bubbled", "onHTMLContentWithCursor"))) .put( "topFocus", MapBuilder.of( @@ -193,11 +198,15 @@ public boolean onEnterKey() { } private static final int COMMAND_NOTIFY_APPLY_FORMAT = 1; + private static final int COMMAND_RETURN_HTML_WITH_CURSOR = 2; private static final String TAG = "ReactAztecText"; @Override public Map getCommandsMap() { - return MapBuilder.of("applyFormat", COMMAND_NOTIFY_APPLY_FORMAT); + return MapBuilder.builder() + .put("applyFormat", COMMAND_NOTIFY_APPLY_FORMAT) + .put("returnHTMLWithCursor", COMMAND_RETURN_HTML_WITH_CURSOR) + .build(); } @Override @@ -211,6 +220,10 @@ public void receiveCommand(final ReactAztecText parent, int commandType, @Nullab parent.applyFormat(format); return; } + case COMMAND_RETURN_HTML_WITH_CURSOR: { + parent.emitHTMLWithCursorEvent(); + return; + } default: super.receiveCommand(parent, commandType, args); } diff --git a/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecText.java b/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecText.java index 58dddb0c521dd8..124394c2bbb990 100644 --- a/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecText.java +++ b/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecText.java @@ -215,6 +215,21 @@ public void setIsSettingTextFromJS(boolean mIsSettingTextFromJS) { this.mIsSettingTextFromJS = mIsSettingTextFromJS; } + void emitHTMLWithCursorEvent() { + disableTextChangedListener(); + String content = toPlainHtml(true); + int cursorPosition = content.indexOf("aztec_cursor"); + if (cursorPosition != -1) { + content = content.replaceFirst("aztec_cursor", ""); + } else { + cursorPosition = 0; // Something went wrong in Aztec - default to 0 if not found. + } + enableTextChangedListener(); + ReactContext reactContext = (ReactContext) getContext(); + EventDispatcher eventDispatcher = reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher(); + eventDispatcher.dispatchEvent(new ReactAztecHtmlWithCursorEvent( getId(), content, cursorPosition)); + } + public void applyFormat(String format) { ArrayList newFormats = new ArrayList<>(); switch (format) { diff --git a/src/AztecView.js b/src/AztecView.js index ab041f59db495f..2bdd92c2e7c5ba 100644 --- a/src/AztecView.js +++ b/src/AztecView.js @@ -16,6 +16,7 @@ class AztecView extends React.Component { onEnter: PropTypes.func, onScroll: PropTypes.func, onActiveFormatsChange: PropTypes.func, + onHTMLContentWithCursor: PropTypes.func, ...ViewPropTypes, // include the default view properties } @@ -27,6 +28,14 @@ class AztecView extends React.Component { ); } + requestHTMLWithCursor() { + UIManager.dispatchViewManagerCommand( + ReactNative.findNodeHandle(this), + UIManager.RCTAztecView.Commands.returnHTMLWithCursor, + [], + ); + } + _onActiveFormatsChange = (event) => { if (!this.props.onActiveFormatsChange) { return; @@ -54,12 +63,24 @@ class AztecView extends React.Component { onEnter(); } + _onHTMLContentWithCursor = (event) => { + if (!this.props.onHTMLContentWithCursor) { + return; + } + + const text = event.nativeEvent.text; + const cursorPosition = event.nativeEvent.cursorPosition; + const { onHTMLContentWithCursor } = this.props; + onHTMLContentWithCursor(text, cursorPosition); + } + render() { const { onActiveFormatsChange, ...otherProps } = this.props return ( );