diff --git a/RNTAztecView.podspec b/RNTAztecView.podspec
index 926e71e..7d079bf 100644
--- a/RNTAztecView.podspec
+++ b/RNTAztecView.podspec
@@ -9,11 +9,11 @@ Pod::Spec.new do |s|
s.license = package['license']
s.homepage = 'https://github.com/wordpress-mobile/react-native-aztec'
s.authors = 'Automattic'
- s.source = { :git => 'https://github.com/wordpress-mobile/react-native-aztec.git' }
+ s.source = { :git => 'https://github.com/ewindso/react-native-aztec' }
s.source_files = 'ios/RNTAztecView/*.{h,m,swift}'
s.public_header_files = 'ios/RNTAztecView/*.h'
s.requires_arc = true
- s.platforms = { :ios => "10.0" }
+ s.platforms = { :ios => "11.0" }
s.xcconfig = {'OTHER_LDFLAGS' => '-lxml2',
'HEADER_SEARCH_PATHS' => '/usr/include/libxml2'}
s.dependency 'React'
diff --git a/android/build.gradle b/android/build.gradle
index aae3b77..2cc6d52 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -12,7 +12,7 @@ buildscript {
wordpressUtilsVersion = '1.22'
espressoVersion = '3.0.1'
- aztecVersion = 'v1.3.14'
+ aztecVersion = 'v1.3.45'
}
repositories {
diff --git a/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/BetterLinkMovementMethod.java b/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/BetterLinkMovementMethod.java
new file mode 100644
index 0000000..8c7b3ef
--- /dev/null
+++ b/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/BetterLinkMovementMethod.java
@@ -0,0 +1,455 @@
+package org.wordpress.mobile.ReactNativeAztec;
+
+import android.app.Activity;
+import android.graphics.RectF;
+import android.text.Layout;
+import android.text.Selection;
+import android.text.Spannable;
+import android.text.Spanned;
+import android.text.method.LinkMovementMethod;
+import android.text.style.BackgroundColorSpan;
+import android.text.style.ClickableSpan;
+import android.text.style.URLSpan;
+import android.text.util.Linkify;
+import android.view.HapticFeedbackConstants;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.widget.TextView;
+
+/**
+ * Handles URL clicks on TextViews. Unlike the default implementation, this:
+ *
+ *
+ * - Reliably applies a highlight color on links when they're touched.
+ * - Let's you handle single and long clicks on URLs
+ * - Correctly identifies focused URLs (Unlike the default implementation where a click is registered even if it's
+ * made outside of the URL's bounds if there is no more text in that direction.)
+ *
+ */
+public class BetterLinkMovementMethod extends LinkMovementMethod {
+
+ private static BetterLinkMovementMethod singleInstance;
+ private static final int LINKIFY_NONE = -2;
+
+ private OnLinkClickListener onLinkClickListener;
+ private OnLinkLongClickListener onLinkLongClickListener;
+ private final RectF touchedLineBounds = new RectF();
+ private boolean isUrlHighlighted;
+ private ClickableSpan clickableSpanUnderTouchOnActionDown;
+ private int activeTextViewHashcode;
+ private LongPressTimer ongoingLongPressTimer;
+ private boolean wasLongPressRegistered;
+
+ public interface OnLinkClickListener {
+ /**
+ * @param textView The TextView on which a click was registered.
+ * @param url The clicked URL.
+ * @return True if this click was handled. False to let Android handle the URL.
+ */
+ boolean onClick(TextView textView, String url);
+ }
+
+ public interface OnLinkLongClickListener {
+ /**
+ * @param textView The TextView on which a long-click was registered.
+ * @param url The long-clicked URL.
+ * @return True if this long-click was handled. False to let Android handle the URL (as a short-click).
+ */
+ boolean onLongClick(TextView textView, String url);
+ }
+
+ /**
+ * Return a new instance of BetterLinkMovementMethod.
+ */
+ public static BetterLinkMovementMethod newInstance() {
+ return new BetterLinkMovementMethod();
+ }
+
+ /**
+ * @param linkifyMask One of {@link Linkify#ALL}, {@link Linkify#PHONE_NUMBERS}, {@link Linkify#MAP_ADDRESSES},
+ * {@link Linkify#WEB_URLS} and {@link Linkify#EMAIL_ADDRESSES}.
+ * @param textViews The TextViews on which a {@link BetterLinkMovementMethod} should be registered.
+ * @return The registered {@link BetterLinkMovementMethod} on the TextViews.
+ */
+ public static BetterLinkMovementMethod linkify(int linkifyMask, TextView... textViews) {
+ BetterLinkMovementMethod movementMethod = newInstance();
+ for (TextView textView : textViews) {
+ addLinks(linkifyMask, movementMethod, textView);
+ }
+ return movementMethod;
+ }
+
+ /**
+ * Like {@link #linkify(int, TextView...)}, but can be used for TextViews with HTML links.
+ *
+ * @param textViews The TextViews on which a {@link BetterLinkMovementMethod} should be registered.
+ * @return The registered {@link BetterLinkMovementMethod} on the TextViews.
+ */
+ public static BetterLinkMovementMethod linkifyHtml(TextView... textViews) {
+ return linkify(LINKIFY_NONE, textViews);
+ }
+
+ /**
+ * Recursively register a {@link BetterLinkMovementMethod} on every TextView inside a layout.
+ *
+ * @param linkifyMask One of {@link Linkify#ALL}, {@link Linkify#PHONE_NUMBERS}, {@link Linkify#MAP_ADDRESSES},
+ * {@link Linkify#WEB_URLS} and {@link Linkify#EMAIL_ADDRESSES}.
+ * @return The registered {@link BetterLinkMovementMethod} on the TextViews.
+ */
+ public static BetterLinkMovementMethod linkify(int linkifyMask, ViewGroup viewGroup) {
+ BetterLinkMovementMethod movementMethod = newInstance();
+ rAddLinks(linkifyMask, viewGroup, movementMethod);
+ return movementMethod;
+ }
+
+ /**
+ * Like {@link #linkify(int, TextView...)}, but can be used for TextViews with HTML links.
+ *
+ * @return The registered {@link BetterLinkMovementMethod} on the TextViews.
+ */
+ @SuppressWarnings("unused")
+ public static BetterLinkMovementMethod linkifyHtml(ViewGroup viewGroup) {
+ return linkify(LINKIFY_NONE, viewGroup);
+ }
+
+ /**
+ * Recursively register a {@link BetterLinkMovementMethod} on every TextView inside a layout.
+ *
+ * @param linkifyMask One of {@link Linkify#ALL}, {@link Linkify#PHONE_NUMBERS}, {@link Linkify#MAP_ADDRESSES},
+ * {@link Linkify#WEB_URLS} and {@link Linkify#EMAIL_ADDRESSES}.
+ * @return The registered {@link BetterLinkMovementMethod} on the TextViews.
+ */
+ public static BetterLinkMovementMethod linkify(int linkifyMask, Activity activity) {
+ // Find the layout passed to setContentView().
+ ViewGroup activityLayout = ((ViewGroup) ((ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT)).getChildAt(0));
+
+ BetterLinkMovementMethod movementMethod = newInstance();
+ rAddLinks(linkifyMask, activityLayout, movementMethod);
+ return movementMethod;
+ }
+
+ /**
+ * Like {@link #linkify(int, TextView...)}, but can be used for TextViews with HTML links.
+ *
+ * @return The registered {@link BetterLinkMovementMethod} on the TextViews.
+ */
+ @SuppressWarnings("unused")
+ public static BetterLinkMovementMethod linkifyHtml(Activity activity) {
+ return linkify(LINKIFY_NONE, activity);
+ }
+
+ /**
+ * Get a static instance of BetterLinkMovementMethod. Do note that registering a click listener on the returned
+ * instance is not supported because it will potentially be shared on multiple TextViews.
+ */
+ @SuppressWarnings("unused")
+ public static BetterLinkMovementMethod getInstance() {
+ if (singleInstance == null) {
+ singleInstance = new BetterLinkMovementMethod();
+ }
+ return singleInstance;
+ }
+
+ protected BetterLinkMovementMethod() {
+ }
+
+ /**
+ * Set a listener that will get called whenever any link is clicked on the TextView.
+ */
+ public BetterLinkMovementMethod setOnLinkClickListener(OnLinkClickListener clickListener) {
+ if (this == singleInstance) {
+ throw new UnsupportedOperationException("Setting a click listener on the instance returned by getInstance() is not supported to avoid memory " +
+ "leaks. Please use newInstance() or any of the linkify() methods instead.");
+ }
+
+ this.onLinkClickListener = clickListener;
+ return this;
+ }
+
+ /**
+ * Set a listener that will get called whenever any link is clicked on the TextView.
+ */
+ public BetterLinkMovementMethod setOnLinkLongClickListener(OnLinkLongClickListener longClickListener) {
+ if (this == singleInstance) {
+ throw new UnsupportedOperationException("Setting a long-click listener on the instance returned by getInstance() is not supported to avoid " +
+ "memory leaks. Please use newInstance() or any of the linkify() methods instead.");
+ }
+
+ this.onLinkLongClickListener = longClickListener;
+ return this;
+ }
+
+// ======== PUBLIC APIs END ======== //
+
+ private static void rAddLinks(int linkifyMask, ViewGroup viewGroup, BetterLinkMovementMethod movementMethod) {
+ for (int i = 0; i < viewGroup.getChildCount(); i++) {
+ View child = viewGroup.getChildAt(i);
+
+ if (child instanceof ViewGroup) {
+ // Recursively find child TextViews.
+ rAddLinks(linkifyMask, ((ViewGroup) child), movementMethod);
+ } else if (child instanceof TextView) {
+ TextView textView = (TextView) child;
+ addLinks(linkifyMask, movementMethod, textView);
+ }
+ }
+ }
+
+ private static void addLinks(int linkifyMask, BetterLinkMovementMethod movementMethod, TextView textView) {
+ textView.setMovementMethod(movementMethod);
+ if (linkifyMask != LINKIFY_NONE) {
+ Linkify.addLinks(textView, linkifyMask);
+ }
+ }
+
+ @Override
+ public boolean onTouchEvent(final TextView textView, Spannable text, MotionEvent event) {
+ if (activeTextViewHashcode != textView.hashCode()) {
+ // Bug workaround: TextView stops calling onTouchEvent() once any URL is highlighted.
+ // A hacky solution is to reset any "autoLink" property set in XML. But we also want
+ // to do this once per TextView.
+ activeTextViewHashcode = textView.hashCode();
+ textView.setAutoLinkMask(0);
+ }
+
+ final ClickableSpan clickableSpanUnderTouch = findClickableSpanUnderTouch(textView, text, event);
+ if (event.getAction() == MotionEvent.ACTION_DOWN) {
+ clickableSpanUnderTouchOnActionDown = clickableSpanUnderTouch;
+ }
+ final boolean touchStartedOverAClickableSpan = clickableSpanUnderTouchOnActionDown != null;
+
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ if (clickableSpanUnderTouch != null) {
+ highlightUrl(textView, clickableSpanUnderTouch, text);
+ }
+
+ if (touchStartedOverAClickableSpan && onLinkLongClickListener != null) {
+ LongPressTimer.OnTimerReachedListener longClickListener = new LongPressTimer.OnTimerReachedListener() {
+ @Override
+ public void onTimerReached() {
+ wasLongPressRegistered = true;
+ textView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+ removeUrlHighlightColor(textView);
+ dispatchUrlLongClick(textView, clickableSpanUnderTouch);
+ }
+ };
+ startTimerForRegisteringLongClick(textView, longClickListener);
+ }
+ return touchStartedOverAClickableSpan;
+
+ case MotionEvent.ACTION_UP:
+ // Register a click only if the touch started and ended on the same URL.
+ if (!wasLongPressRegistered && touchStartedOverAClickableSpan && clickableSpanUnderTouch == clickableSpanUnderTouchOnActionDown) {
+ dispatchUrlClick(textView, clickableSpanUnderTouch);
+ }
+ cleanupOnTouchUp(textView);
+
+ // Consume this event even if we could not find any spans to avoid letting Android handle this event.
+ // Android's TextView implementation has a bug where links get clicked even when there is no more text
+ // next to the link and the touch lies outside its bounds in the same direction.
+ return touchStartedOverAClickableSpan;
+
+ case MotionEvent.ACTION_CANCEL:
+ cleanupOnTouchUp(textView);
+ return false;
+
+ case MotionEvent.ACTION_MOVE:
+ // Stop listening for a long-press as soon as the user wanders off to unknown lands.
+ if (clickableSpanUnderTouch != clickableSpanUnderTouchOnActionDown) {
+ removeLongPressCallback(textView);
+ }
+
+ if (!wasLongPressRegistered) {
+ // Toggle highlight.
+ if (clickableSpanUnderTouch != null) {
+ highlightUrl(textView, clickableSpanUnderTouch, text);
+ } else {
+ removeUrlHighlightColor(textView);
+ }
+ }
+
+ return touchStartedOverAClickableSpan;
+
+ default:
+ return false;
+ }
+ }
+
+ private void cleanupOnTouchUp(TextView textView) {
+ wasLongPressRegistered = false;
+ clickableSpanUnderTouchOnActionDown = null;
+ removeUrlHighlightColor(textView);
+ removeLongPressCallback(textView);
+ }
+
+ /**
+ * Determines the touched location inside the TextView's text and returns the ClickableSpan found under it (if any).
+ *
+ * @return The touched ClickableSpan or null.
+ */
+ protected ClickableSpan findClickableSpanUnderTouch(TextView textView, Spannable text, MotionEvent event) {
+ // So we need to find the location in text where touch was made, regardless of whether the TextView
+ // has scrollable text. That is, not the entire text is currently visible.
+ int touchX = (int) event.getX();
+ int touchY = (int) event.getY();
+
+ // Ignore padding.
+ touchX -= textView.getTotalPaddingLeft();
+ touchY -= textView.getTotalPaddingTop();
+
+ // Account for scrollable text.
+ touchX += textView.getScrollX();
+ touchY += textView.getScrollY();
+
+ final Layout layout = textView.getLayout();
+ final int touchedLine = layout.getLineForVertical(touchY);
+ final int touchOffset = layout.getOffsetForHorizontal(touchedLine, touchX);
+
+ touchedLineBounds.left = layout.getLineLeft(touchedLine);
+ touchedLineBounds.top = layout.getLineTop(touchedLine);
+ touchedLineBounds.right = layout.getLineWidth(touchedLine) + touchedLineBounds.left;
+ touchedLineBounds.bottom = layout.getLineBottom(touchedLine);
+
+ if (touchedLineBounds.contains(touchX, touchY)) {
+ // Find a ClickableSpan that lies under the touched area.
+ final Object[] spans = text.getSpans(touchOffset, touchOffset, ClickableSpan.class);
+ for (final Object span : spans) {
+ if (span instanceof ClickableSpan) {
+ return (ClickableSpan) span;
+ }
+ }
+ // No ClickableSpan found under the touched location.
+ return null;
+
+ } else {
+ // Touch lies outside the line's horizontal bounds where no spans should exist.
+ return null;
+ }
+ }
+
+ /**
+ * Adds a background color span at clickableSpan's location.
+ */
+ protected void highlightUrl(TextView textView, ClickableSpan clickableSpan, Spannable text) {
+ if (isUrlHighlighted) {
+ return;
+ }
+ isUrlHighlighted = true;
+
+ int spanStart = text.getSpanStart(clickableSpan);
+ int spanEnd = text.getSpanEnd(clickableSpan);
+ BackgroundColorSpan highlightSpan = new BackgroundColorSpan(textView.getHighlightColor());
+ text.setSpan(highlightSpan, spanStart, spanEnd, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
+
+ textView.setTag(R.id.bettermovementmethod_highlight_background_span, highlightSpan);
+
+ Selection.setSelection(text, spanStart, spanEnd);
+ }
+
+ /**
+ * Removes the highlight color under the Url.
+ */
+ protected void removeUrlHighlightColor(TextView textView) {
+ if (!isUrlHighlighted) {
+ return;
+ }
+ isUrlHighlighted = false;
+
+ Spannable text = (Spannable) textView.getText();
+ BackgroundColorSpan highlightSpan = (BackgroundColorSpan) textView.getTag(R.id.bettermovementmethod_highlight_background_span);
+ text.removeSpan(highlightSpan);
+
+ Selection.removeSelection(text);
+ }
+
+ protected void startTimerForRegisteringLongClick(TextView textView, LongPressTimer.OnTimerReachedListener longClickListener) {
+ ongoingLongPressTimer = new LongPressTimer();
+ ongoingLongPressTimer.setOnTimerReachedListener(longClickListener);
+ textView.postDelayed(ongoingLongPressTimer, ViewConfiguration.getLongPressTimeout());
+ }
+
+ /**
+ * Remove the long-press detection timer.
+ */
+ protected void removeLongPressCallback(TextView textView) {
+ if (ongoingLongPressTimer != null) {
+ textView.removeCallbacks(ongoingLongPressTimer);
+ ongoingLongPressTimer = null;
+ }
+ }
+
+ protected void dispatchUrlClick(TextView textView, ClickableSpan clickableSpan) {
+ ClickableSpanWithText clickableSpanWithText = ClickableSpanWithText.ofSpan(textView, clickableSpan);
+ boolean handled = onLinkClickListener != null && onLinkClickListener.onClick(textView, clickableSpanWithText.text());
+
+ if (!handled) {
+ // Let Android handle this click.
+ clickableSpanWithText.span().onClick(textView);
+ }
+ }
+
+ protected void dispatchUrlLongClick(TextView textView, ClickableSpan clickableSpan) {
+ ClickableSpanWithText clickableSpanWithText = ClickableSpanWithText.ofSpan(textView, clickableSpan);
+ boolean handled = onLinkLongClickListener != null && onLinkLongClickListener.onLongClick(textView, clickableSpanWithText.text());
+
+ if (!handled) {
+ // Let Android handle this long click as a short-click.
+ clickableSpanWithText.span().onClick(textView);
+ }
+ }
+
+ protected static final class LongPressTimer implements Runnable {
+ private OnTimerReachedListener onTimerReachedListener;
+
+ protected interface OnTimerReachedListener {
+ void onTimerReached();
+ }
+
+ @Override
+ public void run() {
+ onTimerReachedListener.onTimerReached();
+ }
+
+ public void setOnTimerReachedListener(OnTimerReachedListener listener) {
+ onTimerReachedListener = listener;
+ }
+ }
+
+ /**
+ * A wrapper to support all {@link ClickableSpan}s that may or may not provide URLs.
+ */
+ protected static class ClickableSpanWithText {
+ private ClickableSpan span;
+ private String text;
+
+ protected static ClickableSpanWithText ofSpan(TextView textView, ClickableSpan span) {
+ Spanned s = (Spanned) textView.getText();
+ String text;
+ if (span instanceof URLSpan) {
+ text = ((URLSpan) span).getURL();
+ } else {
+ int start = s.getSpanStart(span);
+ int end = s.getSpanEnd(span);
+ text = s.subSequence(start, end).toString();
+ }
+ return new ClickableSpanWithText(span, text);
+ }
+
+ protected ClickableSpanWithText(ClickableSpan span, String text) {
+ this.span = span;
+ this.text = text;
+ }
+
+ protected ClickableSpan span() {
+ return span;
+ }
+
+ protected String text() {
+ return text;
+ }
+ }
+}
\ No newline at end of file
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 dbee8a6..9861696 100644
--- a/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecManager.java
+++ b/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecManager.java
@@ -3,12 +3,15 @@
import android.graphics.Color;
import android.graphics.Typeface;
-import android.support.annotation.Nullable;
+import androidx.annotation.Nullable;
import android.text.Editable;
import android.text.TextWatcher;
+import android.text.InputType;
+import android.text.util.Linkify;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
+import android.view.ViewGroup.LayoutParams;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.bridge.ReactContext;
@@ -51,6 +54,9 @@ public class ReactAztecManager extends SimpleViewManager {
private static final int FOCUS_TEXT_INPUT = 1;
private static final int BLUR_TEXT_INPUT = 2;
+ private static final int SET_HTML = 3;
+ private static final int SCROLL_TO_BOTTOM = 4;
+ private static final int SEND_SPACE_AND_BACKSPACE = 5;
private static final int COMMAND_NOTIFY_APPLY_FORMAT = 100;
private static final int UNSET = -1;
@@ -59,6 +65,9 @@ public class ReactAztecManager extends SimpleViewManager {
// see https://github.com/wordpress-mobile/react-native-aztec/pull/79
private int mFocusTextInputCommandCode = FOCUS_TEXT_INPUT; // pre-init
private int mBlurTextInputCommandCode = BLUR_TEXT_INPUT; // pre-init
+ private int mSetHTMLCommandCode = SET_HTML;
+ private int mScrollToBottomCode = SCROLL_TO_BOTTOM;
+ private int mSendSpaceAndBackspaceCode = SEND_SPACE_AND_BACKSPACE;
private static final String TAG = "ReactAztecText";
@@ -87,6 +96,9 @@ protected ReactAztecText createViewInstance(ThemedReactContext reactContext) {
aztecText.setFocusableInTouchMode(true);
aztecText.setFocusable(true);
aztecText.setCalypsoMode(false);
+ aztecText.setLinksClickable(true);
+ aztecText.setAutoLinkMask(Linkify.WEB_URLS);
+ aztecText.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
return aztecText;
}
@@ -286,6 +298,15 @@ public void setPlaceholderTextColor(ReactAztecText view, @Nullable Integer color
}
}
+ @ReactProp(name = "autoCorrect")
+ public void setAutoCorrect(ReactAztecText view, Boolean autoCorrect) {
+ if (autoCorrect) {
+ view.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES | InputType.TYPE_TEXT_FLAG_MULTI_LINE | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT);
+ } else {
+ view.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES | InputType.TYPE_TEXT_FLAG_MULTI_LINE | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
+ }
+ }
+
@ReactProp(name = "maxImagesWidth")
public void setMaxImagesWidth(ReactAztecText view, int maxWidth) {
view.setMaxImagesWidth(maxWidth);
@@ -381,6 +402,9 @@ public Map getCommandsMap() {
.put("applyFormat", COMMAND_NOTIFY_APPLY_FORMAT)
.put("focusTextInput", mFocusTextInputCommandCode)
.put("blurTextInput", mBlurTextInputCommandCode)
+ .put("setHTML", mSetHTMLCommandCode)
+ .put("scrollToBottom", mScrollToBottomCode)
+ .put("sendSpaceAndBackspace", mSendSpaceAndBackspaceCode)
.build();
}
@@ -398,6 +422,15 @@ public void receiveCommand(final ReactAztecText parent, int commandType, @Nullab
} else if (commandType == mBlurTextInputCommandCode) {
parent.clearFocusFromJS();
return;
+ } else if (commandType == mSetHTMLCommandCode) {
+ final String html = args.getString(0);
+ setTextfromJS(parent, html);
+ return;
+ } else if (commandType == mScrollToBottomCode) {
+ Log.d("SCROLLING", "1");
+ parent.scrollToBottom();
+ } else if (commandType == mSendSpaceAndBackspaceCode) {
+ parent.sendSpaceAndBackspace();
}
super.receiveCommand(parent, commandType, args);
}
@@ -465,14 +498,6 @@ public void onTextChanged(CharSequence s, int start, int before, int count) {
return;
}
- // The event that contains the event counter and updates it must be sent first.
- // TODO: t7936714 merge these events
- mEventDispatcher.dispatchEvent(
- new ReactTextChangedEvent(
- mEditText.getId(),
- mEditText.toHtml(false),
- mEditText.incrementAndGetEventCounter()));
-
mEventDispatcher.dispatchEvent(
new ReactTextInputEvent(
mEditText.getId(),
@@ -484,6 +509,11 @@ public void onTextChanged(CharSequence s, int start, int before, int count) {
@Override
public void afterTextChanged(Editable s) {
+ mEventDispatcher.dispatchEvent(
+ new ReactTextChangedEvent(
+ mEditText.getId(),
+ mEditText.toHtml(false),
+ mEditText.incrementAndGetEventCounter()));
}
}
@@ -562,3 +592,4 @@ public void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
}
}
}
+
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 7a58d09..edeb58d 100644
--- a/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecText.java
+++ b/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecText.java
@@ -2,11 +2,16 @@
import android.content.Context;
import android.graphics.Rect;
-import android.support.annotation.Nullable;
+import androidx.annotation.Nullable;
import android.text.Editable;
import android.text.InputType;
import android.text.TextWatcher;
import android.view.inputmethod.InputMethodManager;
+import android.text.Spannable;
+import android.view.Gravity;
+import android.widget.TextView;
+import android.view.inputmethod.BaseInputConnection;
+import android.view.KeyEvent;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.bridge.ReactContext;
@@ -19,6 +24,7 @@
import com.facebook.react.views.textinput.ScrollWatcher;
import org.wordpress.aztec.AztecText;
+import org.wordpress.aztec.AlignmentRendering;
import org.wordpress.aztec.AztecTextFormat;
import org.wordpress.aztec.ITextFormat;
import org.wordpress.aztec.plugins.IAztecPlugin;
@@ -56,10 +62,7 @@ public ReactAztecText(ThemedReactContext reactContext) {
super(reactContext);
this.setAztecKeyListener(new ReactAztecText.OnAztecKeyListener() {
@Override
- public boolean onEnterKey() {
- if (shouldHandleOnEnter) {
- return onEnter();
- }
+ public boolean onEnterKey(Spannable text, boolean firedAfterTextChanged, int selStart, int selEnd) {
return false;
}
@Override
@@ -80,6 +83,19 @@ public void onSelectionChanged(int selStart, int selEnd) {
}
});
this.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES | InputType.TYPE_TEXT_FLAG_MULTI_LINE);
+ this.setGravity(Gravity.TOP | Gravity.START);
+
+ BetterLinkMovementMethod linkClick = BetterLinkMovementMethod.newInstance();
+
+ linkClick.setOnLinkClickListener(new BetterLinkMovementMethod.OnLinkClickListener() {
+ @Override
+ public boolean onClick(TextView textView, String url) {
+ hideSoftKeyboard();
+ return false;
+ }
+ });
+
+ this.setMovementMethod(linkClick);
}
@Override
@@ -127,10 +143,35 @@ public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
}*/
setFocusableInTouchMode(true);
boolean focused = super.requestFocus(direction, previouslyFocusedRect);
+
+ final int scrollAmount = this.getLayout().getLineTop(this.getLineCount()) - this.getHeight();
+ if (scrollAmount > 0) {
+ this.scrollTo(0, scrollAmount + 50);
+ }
+
+ super.setSelection(this.length());
+
showSoftKeyboard();
return focused;
}
+ public void sendSpaceAndBackspace() {
+ BaseInputConnection inputConnection = new BaseInputConnection(this, true);
+ inputConnection.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_SPACE));
+ inputConnection.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL));
+ }
+
+ public void scrollToBottom() {
+ final int scrollAmount = this.getLayout().getLineTop(this.getLineCount()) - this.getHeight() + 50;
+
+ // only scroll if the user is already at the bottom. ignore otherwise
+ if (scrollAmount > 0 && this.getSelectionStart() >= this.getText().toString().length() - 1) {
+ this.scrollTo(0, scrollAmount);
+
+ super.setSelection(this.length());
+ }
+ }
+
private boolean showSoftKeyboard() {
return mInputMethodManager.showSoftInput(this, 0);
}
@@ -194,6 +235,10 @@ private void updateToolbarButtons(ArrayList appliedStyles) {
if (currentStyle == AztecTextFormat.FORMAT_STRIKETHROUGH) {
formattingOptions.add("strikethrough");
}
+
+ if (currentStyle == AztecTextFormat.FORMAT_UNDERLINE) {
+ formattingOptions.add("underline");
+ }
}
// Check if the same formatting event was already sent
@@ -331,6 +376,9 @@ public void applyFormat(String format) {
case ("strikethrough"):
newFormats.add(AztecTextFormat.FORMAT_STRIKETHROUGH);
break;
+ case ("underline"):
+ newFormats.add(AztecTextFormat.FORMAT_UNDERLINE);
+ break;
}
if (newFormats.size() == 0) {
@@ -413,3 +461,4 @@ public void afterTextChanged(Editable s) {
}
}
}
+
diff --git a/android/src/main/res/values/betterlinkmovementmethod.xml b/android/src/main/res/values/betterlinkmovementmethod.xml
new file mode 100644
index 0000000..eac24ae
--- /dev/null
+++ b/android/src/main/res/values/betterlinkmovementmethod.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/ios/RNTAztecView.xcodeproj/project.pbxproj b/ios/RNTAztecView.xcodeproj/project.pbxproj
index 27063dc..3bf45e8 100644
--- a/ios/RNTAztecView.xcodeproj/project.pbxproj
+++ b/ios/RNTAztecView.xcodeproj/project.pbxproj
@@ -7,6 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
+ 397407BB263770E700DE50C1 /* MediaProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 397407BA263770E600DE50C1 /* MediaProvider.swift */; };
7ECFA93C21C39B5000FC131B /* HeadingBlockFormatHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ECFA93B21C39B5000FC131B /* HeadingBlockFormatHandler.swift */; };
7ECFA94021C39BA000FC131B /* BlockFormatHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ECFA93F21C39BA000FC131B /* BlockFormatHandler.swift */; };
7ECFA94221C39BBB00FC131B /* BlockModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ECFA94121C39BBB00FC131B /* BlockModel.swift */; };
@@ -53,6 +54,7 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
+ 397407BA263770E600DE50C1 /* MediaProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaProvider.swift; sourceTree = ""; };
7ECFA93B21C39B5000FC131B /* HeadingBlockFormatHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadingBlockFormatHandler.swift; sourceTree = ""; };
7ECFA93F21C39BA000FC131B /* BlockFormatHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockFormatHandler.swift; sourceTree = ""; };
7ECFA94121C39BBB00FC131B /* BlockModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockModel.swift; sourceTree = ""; };
@@ -105,6 +107,7 @@
7ECFA94121C39BBB00FC131B /* BlockModel.swift */,
7ECFA93B21C39B5000FC131B /* HeadingBlockFormatHandler.swift */,
7ECFA93F21C39BA000FC131B /* BlockFormatHandler.swift */,
+ 397407BA263770E600DE50C1 /* MediaProvider.swift */,
);
path = RNTAztecView;
sourceTree = "";
@@ -217,6 +220,7 @@
buildActionMask = 2147483647;
files = (
7ECFA94021C39BA000FC131B /* BlockFormatHandler.swift in Sources */,
+ 397407BB263770E700DE50C1 /* MediaProvider.swift in Sources */,
7ECFA93C21C39B5000FC131B /* HeadingBlockFormatHandler.swift in Sources */,
F13BF4A020ECF5450047D3F9 /* RCTAztecViewManager.swift in Sources */,
F1A879D020EE90C000FABD31 /* RCTAztecView.swift in Sources */,
@@ -287,7 +291,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 10.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@@ -341,7 +345,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 10.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
diff --git a/ios/RNTAztecView/HeadingBlockFormatHandler.swift b/ios/RNTAztecView/HeadingBlockFormatHandler.swift
index 214f513..b9bb9c0 100644
--- a/ios/RNTAztecView/HeadingBlockFormatHandler.swift
+++ b/ios/RNTAztecView/HeadingBlockFormatHandler.swift
@@ -11,15 +11,15 @@ struct HeadingBlockFormatHandler: BlockFormatHandler {
}
self.level = level
headerFormatter = HeaderFormatter(headerLevel: level)
- }
+ }
func forceTypingFormat(on textView: RCTAztecView) {
- var attributes = textView.typingAttributesSwifted
-
- attributes = paragraphFormatter.remove(from: attributes)
- attributes = headerFormatter.apply(to: attributes, andStore: nil)
-
- textView.typingAttributesSwifted = attributes
+// var attributes = textView.typingAttributesSwifted
+//
+// attributes = paragraphFormatter.remove(from: attributes)
+// attributes = headerFormatter.apply(to: attributes, andStore: nil)
+//
+// textView.typingAttributesSwifted = attributes
}
private static func headerLevel(from levelString: String) -> Header.HeaderType? {
diff --git a/ios/RNTAztecView/MediaProvider.swift b/ios/RNTAztecView/MediaProvider.swift
new file mode 100644
index 0000000..37875ef
--- /dev/null
+++ b/ios/RNTAztecView/MediaProvider.swift
@@ -0,0 +1,61 @@
+import Aztec
+import Foundation
+
+class MediaProvider: Aztec.TextViewAttachmentDelegate {
+
+ func textView(_ textView: TextView, attachment: NSTextAttachment, imageAt url: URL, onSuccess success: @escaping (UIImage) -> Void, onFailure failure: @escaping () -> Void) {
+
+ DispatchQueue.main.async {
+ let image = UIImage()
+
+ success(image)
+ }
+ }
+
+ func textView(_ textView: TextView, urlFor imageAttachment: ImageAttachment) -> URL? {
+ return URL(string: "www.google.com")
+ }
+
+ func textView(_ textView: TextView, placeholderFor attachment: NSTextAttachment) -> UIImage {
+ return UIImage()
+ }
+
+ func textView(_ textView: TextView, deletedAttachment attachment: MediaAttachment) {
+ textView.setNeedsDisplay()
+ }
+
+ func textView(_ textView: TextView, selected attachment: NSTextAttachment, atPosition position: CGPoint) {
+ }
+
+ func textView(_ textView: TextView, deselected attachment: NSTextAttachment, atPosition position: CGPoint) {
+ }
+
+
+}
+
+extension MediaProvider: Aztec.TextViewAttachmentImageProvider {
+ func textView(_ textView: TextView, shouldRender attachment: NSTextAttachment) -> Bool {
+ return true
+ }
+
+ func textView(_ textView: TextView, boundsFor attachment: NSTextAttachment, with lineFragment: CGRect) -> CGRect {
+ return CGRect(x: 0, y: 0, width: 0, height: 0)
+ }
+
+ func textView(_ textView: TextView, imageFor attachment: NSTextAttachment, with size: CGSize) -> UIImage? {
+ let image = UIImage()
+
+ return image;
+
+// return imageWithImage(image: image, scaledToSize: size)
+ }
+
+// func imageWithImage(image:UIImage, scaledToSize newSize:CGSize) -> UIImage{
+// UIGraphicsBeginImageContextWithOptions(newSize, false, 0.0);
+// image.draw(in: CGRect(origin: CGPoint.zero, size: CGSize(width: newSize.width, height: newSize.height)))
+// let newImage:UIImage = UIGraphicsGetImageFromCurrentImageContext()!
+// UIGraphicsEndImageContext()
+// return newImage
+// }
+
+}
diff --git a/ios/RNTAztecView/RCTAztecView.swift b/ios/RNTAztecView/RCTAztecView.swift
index 3eafeef..2bcdcf8 100644
--- a/ios/RNTAztecView/RCTAztecView.swift
+++ b/ios/RNTAztecView/RCTAztecView.swift
@@ -2,6 +2,8 @@ import Aztec
import Foundation
import UIKit
+var lastTimeShouldChangeCalled = 0.0;
+
class RCTAztecView: Aztec.TextView {
@objc var onBackspace: RCTBubblingEventBlock? = nil
@objc var onChange: RCTBubblingEventBlock? = nil
@@ -12,6 +14,7 @@ class RCTAztecView: Aztec.TextView {
@objc var onSelectionChange: RCTBubblingEventBlock? = nil
@objc var onActiveFormatsChange: RCTBubblingEventBlock? = nil
@objc var onActiveFormatAttributesChange: RCTBubblingEventBlock? = nil
+ @objc var autoCorrect: Bool = false
@objc var blockType: NSDictionary? = nil {
didSet {
guard let block = blockType, let tag = block["tag"] as? String else {
@@ -27,6 +30,10 @@ class RCTAztecView: Aztec.TextView {
}
}
+ override func didSetProps(_ changedProps: [String]!) {
+ autocorrectionType = self.autoCorrect ? .yes : .no
+ }
+
private var previousContentSize: CGSize = .zero
private lazy var placeholderLabel: UILabel = {
@@ -37,6 +44,7 @@ class RCTAztecView: Aztec.TextView {
private let formatStringMap: [FormattingIdentifier: String] = [
.bold: "bold",
.italic: "italic",
+ .underline: "underline",
.strikethrough: "strikethrough",
.link: "link",
]
@@ -44,6 +52,8 @@ class RCTAztecView: Aztec.TextView {
override init(defaultFont: UIFont, defaultParagraphStyle: ParagraphStyle, defaultMissingImage: UIImage) {
super.init(defaultFont: defaultFont, defaultParagraphStyle: defaultParagraphStyle, defaultMissingImage: defaultMissingImage)
commonInit()
+
+
}
required init?(coder aDecoder: NSCoder) {
@@ -61,6 +71,7 @@ class RCTAztecView: Aztec.TextView {
placeholderLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: contentInset.left + textContainerInset.left + textContainer.lineFragmentPadding),
placeholderLabel.topAnchor.constraint(equalTo: topAnchor, constant: contentInset.top + textContainerInset.top)
])
+ autocorrectionType = autoCorrect ? .yes : .no;
}
// MARK - View Height: Match to content height
@@ -87,9 +98,9 @@ class RCTAztecView: Aztec.TextView {
// MARK: - Edits
open override func insertText(_ text: String) {
- guard !interceptEnter(text) else {
- return
- }
+// guard !interceptEnter(text) else {
+// return
+// }
super.insertText(text)
updatePlaceholderVisibility()
@@ -210,6 +221,7 @@ class RCTAztecView: Aztec.TextView {
switch format {
case "bold": toggleBold(range: selectedRange)
case "italic": toggleItalic(range: selectedRange)
+ case "underline": toggleUnderline(range: selectedRange)
case "strikethrough": toggleStrikethrough(range: selectedRange)
default: print("Format not recognized")
}
@@ -234,6 +246,11 @@ class RCTAztecView: Aztec.TextView {
}
removeLink(inRange: expandedRange)
}
+
+ @objc
+ func hideKeyboard() {
+ self.resignFirstResponder()
+ }
func linkAttributes() -> [String: Any] {
var attributes: [String: Any] = ["isActive": false]
@@ -301,7 +318,24 @@ extension RCTAztecView: UITextViewDelegate {
func textViewDidChange(_ textView: UITextView) {
forceTypingAttributesIfNeeded()
propagateFormatChanges()
- propagateContentChanges()
+
+ let nowTime = NSDate().timeIntervalSince1970
+
+ if (nowTime - Double(lastTimeShouldChangeCalled) > 0.1) {
+ propagateContentChanges()
+ }
+ }
+
+ func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
+ lastTimeShouldChangeCalled = NSDate().timeIntervalSince1970
+
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
+ self.forceTypingAttributesIfNeeded()
+ self.propagateFormatChanges()
+ self.propagateContentChanges()
+ }
+
+ return true
}
func textViewDidBeginEditing(_ textView: UITextView) {
diff --git a/ios/RNTAztecView/RCTAztecViewManager.m b/ios/RNTAztecView/RCTAztecViewManager.m
index 3a8858e..6bae310 100644
--- a/ios/RNTAztecView/RCTAztecViewManager.m
+++ b/ios/RNTAztecView/RCTAztecViewManager.m
@@ -18,8 +18,13 @@ @interface RCT_EXTERN_MODULE(RCTAztecViewManager, NSObject)
RCT_EXPORT_VIEW_PROPERTY(placeholder, NSString)
RCT_EXPORT_VIEW_PROPERTY(placeholderTextColor, UIColor)
+RCT_EXPORT_VIEW_PROPERTY(autoCorrect, BOOL)
+
RCT_EXTERN_METHOD(applyFormat:(nonnull NSNumber *)node format:(NSString *)format)
RCT_EXTERN_METHOD(setLink:(nonnull NSNumber *)node url:(nonnull NSString *)url title:(nullable NSString *)title)
RCT_EXTERN_METHOD(removeLink:(nonnull NSNumber *)node)
+RCT_EXTERN_METHOD(focusTextInput:(nonnull NSNumber *)node)
+RCT_EXTERN_METHOD(blurTextInput:(nonnull NSNumber *)node)
+RCT_EXTERN_METHOD(setHTML:(nonnull NSNumber *)node html:(NSString *)html)
@end
diff --git a/ios/RNTAztecView/RCTAztecViewManager.swift b/ios/RNTAztecView/RCTAztecViewManager.swift
index bb445c4..ea937f9 100644
--- a/ios/RNTAztecView/RCTAztecViewManager.swift
+++ b/ios/RNTAztecView/RCTAztecViewManager.swift
@@ -4,9 +4,12 @@ import Foundation
@objc (RCTAztecViewManager)
public class RCTAztecViewManager: RCTViewManager {
- public var attachmentDelegate: Aztec.TextViewAttachmentDelegate?
- public var imageProvider: Aztec.TextViewAttachmentImageProvider?
-
+ public static var attachmentDelegate: Aztec.TextViewAttachmentDelegate?
+ public static var imageProvider: Aztec.TextViewAttachmentImageProvider?
+
+ @objc
+ public static var defaultFont: UIFont?
+
public override static func requiresMainQueueSetup() -> Bool {
return true
}
@@ -17,7 +20,32 @@ public class RCTAztecViewManager: RCTViewManager {
aztecView.apply(format: format)
}, onNode: node)
}
+
+ @objc
+ func focusTextInput(_ node: NSNumber) {
+ executeBlock({ (aztecView) in
+ aztecView.becomeFirstResponder()
+
+ let newPosition = aztecView.endOfDocument
+ aztecView.selectedTextRange = aztecView.textRange(from: newPosition, to: newPosition)
+ }, onNode: node)
+ }
+
+ @objc
+ func setHTML(_ node: NSNumber, html: String) {
+ executeBlock({ (aztecView) in
+ aztecView.setHTML(html)
+ aztecView.updatePlaceholderVisibility()
+ }, onNode: node)
+ }
+ @objc
+ func blurTextInput(_ node: NSNumber) {
+ executeBlock({ (aztecView) in
+ aztecView.hideKeyboard()
+ }, onNode: node)
+ }
+
@objc
func removeLink(_ node: NSNumber) {
executeBlock({ (aztecView) in
@@ -31,21 +59,43 @@ public class RCTAztecViewManager: RCTViewManager {
aztecView.setLink(with: url, and: title)
}, onNode: node)
}
-
+
+
+
@objc
public override func view() -> UIView {
+ if (RCTAztecViewManager.defaultFont == nil) {
+ RCTAztecViewManager.defaultFont = UIFont(name: "Inter-Regular", size: 16.0);
+ }
let view = RCTAztecView(
defaultFont: defaultFont,
defaultParagraphStyle: .default,
defaultMissingImage: UIImage())
- view.isScrollEnabled = false
+ view.isScrollEnabled = true
+
+ view.autocorrectionType = .no
+
+ let defaultMediaProvider = MediaProvider()
+
+ if (RCTAztecViewManager.attachmentDelegate == nil) {
+ RCTAztecViewManager.attachmentDelegate = defaultMediaProvider
+ }
+
+ if (RCTAztecViewManager.imageProvider == nil) {
+ RCTAztecViewManager.imageProvider = defaultMediaProvider
+ }
- view.textAttachmentDelegate = attachmentDelegate
- if let imageProvider = imageProvider {
+ view.textAttachmentDelegate = RCTAztecViewManager.attachmentDelegate
+
+ if let imageProvider = RCTAztecViewManager.imageProvider {
view.registerAttachmentImageProvider(imageProvider)
}
-
+
+ if #available(iOS 13, *) {
+ view.overrideUserInterfaceStyle = .light
+ }
+
return view
}
@@ -59,20 +109,20 @@ public class RCTAztecViewManager: RCTViewManager {
}
}
- private var defaultFont: UIFont {
- if let font = UIFont(name: "NotoSerif", size: 16) {
- return font
- }
+ private var defaultFont: UIFont {
+ if let font = UIFont(name: "NotoSerif", size: 16) {
+ return font
+ }
- let defaultFont = UIFont.systemFont(ofSize: 16)
- guard let url = Bundle.main.url(forResource: "NotoSerif-Regular", withExtension: "ttf") else {
- return defaultFont
- }
- CTFontManagerRegisterFontsForURL(url as CFURL, CTFontManagerScope.process, nil)
- if let font = UIFont(name: "NotoSerif", size: 16) {
- return font
- }
+ let defaultFont = UIFont.systemFont(ofSize: 16)
+ guard let url = Bundle.main.url(forResource: "NotoSerif-Regular", withExtension: "ttf") else {
+ return defaultFont
+ }
+ CTFontManagerRegisterFontsForURL(url as CFURL, CTFontManagerScope.process, nil)
+ if let font = UIFont(name: "NotoSerif", size: 16) {
+ return font
+ }
- return defaultFont
- }
+ return defaultFont
+ }
}
diff --git a/package.json b/package.json
index 5fe3c65..4ff60eb 100644
--- a/package.json
+++ b/package.json
@@ -1,27 +1,59 @@
{
- "name": "react-native-aztec",
- "version": "0.1.5",
- "license": "(MPL-2.0 OR GPL-2.0)",
- "scripts": {
- "install-aztec-ios": "cd ./ios && carthage bootstrap --platform iOS --cache-builds",
- "clean": "yarn clean-watchman; yarn clean-node; yarn clean-react; yarn clean-metro; yarn clean-jest;",
- "clean-jest": "rm -rf $TMPDIR/jest_*;",
- "clean-metro": "rm -rf $TMPDIR/metro-cache-*; rm -rf $TMPDIR/metro-bundler-cache-*;",
- "clean-node": "rm -rf node_modules/;",
- "clean-react": "rm -rf $TMPDIR/react-*; rm -rf $TMPDIR/react-native-packager-cache-*;",
- "clean-watchman": "command -v watchman >/dev/null 2>&1 && watchman watch-del-all;",
- "clean:install": "yarn clean && yarn install"
+ "_from": "git+https://github.com/ewindso/react-native-aztec.git",
+ "_id": "react-native-aztec@0.1.5",
+ "_inBundle": false,
+ "_integrity": "",
+ "_location": "/react-native-aztec",
+ "_phantomChildren": {
+ "asap": "2.0.6",
+ "isomorphic-fetch": "2.2.1",
+ "loose-envify": "1.4.0",
+ "object-assign": "4.1.1",
+ "setimmediate": "1.0.5",
+ "ua-parser-js": "0.7.23"
},
- "peerDependencies": {
- "react": "16.6.1",
- "react-native": "0.57.5"
+ "_requested": {
+ "type": "git",
+ "raw": "react-native-aztec@git+https://github.com/ewindso/react-native-aztec.git",
+ "name": "react-native-aztec",
+ "escapedName": "react-native-aztec",
+ "rawSpec": "git+https://github.com/ewindso/react-native-aztec.git",
+ "saveSpec": "git+https://github.com/ewindso/react-native-aztec.git",
+ "fetchSpec": "https://github.com/ewindso/react-native-aztec.git",
+ "gitCommittish": null
},
+ "_requiredBy": [
+ "/"
+ ],
+ "_resolved": "git+https://github.com/ewindso/react-native-aztec.git#ff1999f5e6075c4361e1b1051e66a45f7007bc6e",
+ "_spec": "react-native-aztec@git+https://github.com/ewindso/react-native-aztec.git",
+ "_where": "/Users/elijahwindsor/Github/GrowthDayMobile",
+ "bundleDependencies": false,
"dependencies": {
"prop-types": "15.6.0"
},
+ "deprecated": false,
+ "description": "# react-native-aztec",
"devDependencies": {
"babel-cli": "^6.26.0",
"babel-preset-flow": "^6.23.0",
"flow-bin": "^0.69.0"
- }
+ },
+ "license": "(MPL-2.0 OR GPL-2.0)",
+ "name": "react-native-aztec",
+ "peerDependencies": {
+ "react": "16.13.1",
+ "react-native": "0.63.2"
+ },
+ "scripts": {
+ "clean": "yarn clean-watchman; yarn clean-node; yarn clean-react; yarn clean-metro; yarn clean-jest;",
+ "clean-jest": "rm -rf $TMPDIR/jest_*;",
+ "clean-metro": "rm -rf $TMPDIR/metro-cache-*; rm -rf $TMPDIR/metro-bundler-cache-*;",
+ "clean-node": "rm -rf node_modules/;",
+ "clean-react": "rm -rf $TMPDIR/react-*; rm -rf $TMPDIR/react-native-packager-cache-*;",
+ "clean-watchman": "command -v watchman >/dev/null 2>&1 && watchman watch-del-all;",
+ "clean:install": "yarn clean && yarn install",
+ "install-aztec-ios": "cd ./ios && carthage bootstrap --platform iOS --cache-builds"
+ },
+ "version": "0.1.5"
}
diff --git a/src/AztecView.js b/src/AztecView.js
index f1cb465..10ca440 100644
--- a/src/AztecView.js
+++ b/src/AztecView.js
@@ -1,7 +1,8 @@
import PropTypes from 'prop-types';
import React from 'react';
-import ReactNative, {requireNativeComponent, ViewPropTypes, UIManager, ColorPropType, TouchableWithoutFeedback} from 'react-native';
-import TextInputState from 'react-native/lib/TextInputState';
+import ReactNative, {Platform, ViewPropTypes, UIManager, ColorPropType, TouchableWithoutFeedback} from 'react-native';
+import TextInputState from 'react-native/Libraries/Components/TextInput/TextInputState';
+import RCTAztecView from './RCTAztecView';
const AztecManager = UIManager.RCTAztecView;
@@ -14,6 +15,7 @@ class AztecView extends React.Component {
text: PropTypes.object,
placeholder: PropTypes.string,
placeholderTextColor: ColorPropType,
+ autoCorrect: PropTypes.boolean,
color: ColorPropType,
maxImagesWidth: PropTypes.number,
minImagesWidth: PropTypes.number,
@@ -33,15 +35,22 @@ class AztecView extends React.Component {
...ViewPropTypes, // include the default view properties
}
- dispatch(command, params) {
+ dispatch = (command, params) => {
params = params || [];
- UIManager.dispatchViewManagerCommand(
- ReactNative.findNodeHandle(this),
- command,
- params,
- );
+
+ try {
+ UIManager.dispatchViewManagerCommand(
+ ReactNative.findNodeHandle(this),
+ command,
+ params,
+ );
+ } catch(error) {
+ console.log({error})
+ }
}
+ _inputRef = null;
+
applyFormat(format) {
this.dispatch(AztecManager.Commands.applyFormat, [format])
}
@@ -54,6 +63,33 @@ class AztecView extends React.Component {
this.dispatch(AztecManager.Commands.setLink, [url, title])
}
+ focus() {
+ }
+
+ focusEndOfDocument() {
+ this.dispatch(AztecManager.Commands.focusTextInput)
+ }
+
+ blur() {
+ this.dispatch(AztecManager.Commands.blurTextInput)
+ }
+
+ setHTML(html) {
+ this.dispatch(AztecManager.Commands.setHTML, [html])
+ }
+
+ scrollToBottom() {
+ if (Platform.OS === 'android') {
+ this.dispatch(AztecManager.Commands.scrollToBottom);
+ }
+ }
+
+ sendSpaceAndBackspace() {
+ if(Platform.OS === 'android') {
+ this.dispatch(AztecManager.Commands.sendSpaceAndBackspace);
+ }
+ }
+
requestHTMLWithCursor() {
this.dispatch(AztecManager.Commands.returnHTMLWithCursor)
}
@@ -83,6 +119,8 @@ class AztecView extends React.Component {
const size = event.nativeEvent.contentSize;
const { onContentSizeChange } = this.props;
onContentSizeChange(size);
+
+ this.scrollToBottom();
}
_onEnter = (event) => {
@@ -126,7 +164,6 @@ class AztecView extends React.Component {
_onBlur = (event) => {
this.selectionEndCaretY = null;
- TextInputState.blurTextInput(ReactNative.findNodeHandle(this));
if (!this.props.onBlur) {
return;
@@ -151,14 +188,6 @@ class AztecView extends React.Component {
}
}
- blur = () => {
- TextInputState.blurTextInput(ReactNative.findNodeHandle(this));
- }
-
- focus = () => {
- TextInputState.focusTextInput(ReactNative.findNodeHandle(this));
- }
-
isFocused = () => {
const focusedField = TextInputState.currentlyFocusedField();
return focusedField && ( focusedField === ReactNative.findNodeHandle(this) );
@@ -167,6 +196,12 @@ class AztecView extends React.Component {
_onPress = (event) => {
this.focus(event); // Call to move the focus in RN way (TextInputState)
this._onFocus(event); // Check if there are listeners set on the focus event
+
+ if (Platform.OS === 'android') {
+ setTimeout(() => {
+ this.sendSpaceAndBackspace();
+ }, 250);
+ }
}
render() {
@@ -174,6 +209,7 @@ class AztecView extends React.Component {
return (
this._inputRef = ref}
onActiveFormatsChange={ this._onActiveFormatsChange }
onActiveFormatAttributesChange={ this._onActiveFormatAttributesChange }
onContentSizeChange = { this._onContentSizeChange }
@@ -192,6 +228,4 @@ class AztecView extends React.Component {
}
}
-const RCTAztecView = requireNativeComponent('RCTAztecView', AztecView);
-
export default AztecView
\ No newline at end of file
diff --git a/src/RCTAztecView.js b/src/RCTAztecView.js
new file mode 100644
index 0000000..28ee0c9
--- /dev/null
+++ b/src/RCTAztecView.js
@@ -0,0 +1,5 @@
+import {requireNativeComponent} from 'react-native';
+
+const RCTAztecView = requireNativeComponent('RCTAztecView');
+
+export default RCTAztecView
\ No newline at end of file