Skip to content

Commit

Permalink
Merge pull request #275 from wordpress-mobile/feature/rich-text-formats
Browse files Browse the repository at this point in the history
Use format-library for the formatting bar
  • Loading branch information
hypest authored Feb 18, 2019
2 parents ba2a006 + 558c2ff commit c03900d
Show file tree
Hide file tree
Showing 17 changed files with 1,937 additions and 2,042 deletions.
1,844 changes: 923 additions & 921 deletions bundle/android/App.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion bundle/android/App.js.map

Large diffs are not rendered by default.

1,856 changes: 929 additions & 927 deletions bundle/ios/App.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion bundle/ios/App.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion react-native-aztec/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ buildscript {
wordpressUtilsVersion = '1.22'
espressoVersion = '3.0.1'

aztecVersion = 'v1.3.19'
aztecVersion = 'e02ec1387c739d73645e498a2e5ed1b3916b2587'
}

repositories {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@ class ReactAztecEnterEvent extends Event<ReactAztecEnterEvent> {
private String mText;
private int mSelectionStart;
private int mSelectionEnd;
private int mEventCount;

public ReactAztecEnterEvent(int viewId, String text, int selectionStart, int selectionEnd) {
public ReactAztecEnterEvent(int viewId, String text, int selectionStart, int selectionEnd, int eventCount) {
super(viewId);
mText = text;
mSelectionStart = selectionStart;
mSelectionEnd = selectionEnd;
mEventCount = eventCount;
}

@Override
Expand All @@ -44,6 +46,7 @@ private WritableMap serializeEventData() {
eventData.putString("text", mText);
eventData.putInt("selectionStart", mSelectionStart);
eventData.putInt("selectionEnd", mSelectionEnd);
eventData.putInt("eventCount", mEventCount);
return eventData;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,15 @@
import org.wordpress.aztec.plugins.wpcomments.toolbar.MoreToolbarButton;

import java.util.Map;
import java.util.ArrayList;
import java.util.Arrays;

public class ReactAztecManager extends SimpleViewManager<ReactAztecText> {

public static final String REACT_CLASS = "RCTAztecView";

private static final int FOCUS_TEXT_INPUT = 1;
private static final int BLUR_TEXT_INPUT = 2;
private static final int COMMAND_NOTIFY_APPLY_FORMAT = 100;
private static final int UNSET = -1;

// we define the same codes in ReactAztecText as they have for ReactNative's TextInput, so
Expand Down Expand Up @@ -165,6 +166,7 @@ public void setText(ReactAztecText view, ReadableMap inputMap) {
// Don't think there is necessity of this branch, but justin case we want to
// force a 2nd setText from JS side to Native, just set a high eventCount
int eventCount = inputMap.getInt("eventCount");

if (view.mNativeEventCount < eventCount) {
setTextfromJS(view, inputMap.getString("text"));
}
Expand All @@ -173,10 +175,24 @@ public void setText(ReactAztecText view, ReadableMap inputMap) {

private void setTextfromJS(ReactAztecText view, String text) {
view.setIsSettingTextFromJS(true);
view.disableOnSelectionListener();
view.fromHtml(text, true);
view.enableOnSelectionListener();
view.setIsSettingTextFromJS(false);
}

@ReactProp(name = "activeFormats", defaultBoolean = false)
public void setActiveFormats(final ReactAztecText view, @Nullable ReadableArray activeFormats) {
if (activeFormats != null) {
String[] activeFormatsArray = new String[activeFormats.size()];
for (int i = 0; i < activeFormats.size(); i++) {
activeFormatsArray[i] = activeFormats.getString(i);
}
view.setActiveFormats(Arrays.asList(activeFormatsArray));
} else {
view.setActiveFormats(new ArrayList<String>());
}
}

/*
The code below was taken from the class ReactTextInputManager
Expand Down Expand Up @@ -346,11 +362,6 @@ public void setOnContentSizeChange(final ReactAztecText view, boolean onContentS
}
}

@ReactProp(name = "onActiveFormatsChange", defaultBoolean = false)
public void setOnActiveFormatsChange(final ReactAztecText view, boolean onActiveFormatsChange) {
view.shouldHandleActiveFormatsChange = onActiveFormatsChange;
}

@ReactProp(name = "onSelectionChange", defaultBoolean = false)
public void setOnSelectionChange(final ReactAztecText view, boolean onSelectionChange) {
view.shouldHandleOnSelectionChange = onSelectionChange;
Expand Down Expand Up @@ -378,7 +389,6 @@ public void setOnBackspaceHandling(final ReactAztecText view, boolean onBackspac
@Override
public Map<String, Integer> getCommandsMap() {
return MapBuilder.<String, Integer>builder()
.put("applyFormat", COMMAND_NOTIFY_APPLY_FORMAT)
.put("focusTextInput", mFocusTextInputCommandCode)
.put("blurTextInput", mBlurTextInputCommandCode)
.build();
Expand All @@ -387,12 +397,7 @@ public Map<String, Integer> getCommandsMap() {
@Override
public void receiveCommand(final ReactAztecText parent, int commandType, @Nullable ReadableArray args) {
Assertions.assertNotNull(parent);
if (commandType == COMMAND_NOTIFY_APPLY_FORMAT) {
final String format = args.getString(0);
Log.d(TAG, String.format("Apply format: %s", format));
parent.applyFormat(format);
return;
} else if (commandType == mFocusTextInputCommandCode) {
if (commandType == mFocusTextInputCommandCode) {
parent.requestFocusFromJS();
return;
} else if (commandType == mBlurTextInputCommandCode) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@ class ReactAztecSelectionChangeEvent extends Event<ReactAztecSelectionChangeEven
private String mText;
private int mSelectionStart;
private int mSelectionEnd;
private int mEventCount;

public ReactAztecSelectionChangeEvent(int viewId, String text, int selectionStart, int selectionEnd) {
public ReactAztecSelectionChangeEvent(int viewId, String text, int selectionStart, int selectionEnd, int eventCount) {
super(viewId);
mText = text;
mSelectionStart = selectionStart;
mSelectionEnd = selectionEnd;
mEventCount = eventCount;
}

@Override
Expand All @@ -45,6 +47,7 @@ private WritableMap serializeEventData() {
eventData.putString("text", mText);
eventData.putInt("selectionStart", mSelectionStart);
eventData.putInt("selectionEnd", mSelectionEnd);
eventData.putInt("eventCount", mEventCount);
return eventData;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Set;
import java.util.HashSet;
import java.util.Map;
import java.util.HashMap;

public class ReactAztecText extends AztecText {

Expand All @@ -52,6 +56,17 @@ public class ReactAztecText extends AztecText {
boolean shouldHandleOnSelectionChange = false;
boolean shouldHandleActiveFormatsChange = false;

private static final HashMap<ITextFormat, String> typingFormatsMap = new HashMap<ITextFormat, String>() {
{
put(AztecTextFormat.FORMAT_BOLD, "bold");
put(AztecTextFormat.FORMAT_STRONG, "bold");
put(AztecTextFormat.FORMAT_EMPHASIS, "italic");
put(AztecTextFormat.FORMAT_ITALIC, "italic");
put(AztecTextFormat.FORMAT_CITE, "italic");
put(AztecTextFormat.FORMAT_STRIKETHROUGH, "strikethrough");
}
};

public ReactAztecText(ThemedReactContext reactContext) {
super(reactContext);

Expand Down Expand Up @@ -232,7 +247,7 @@ private void propagateSelectionChanges(int selStart, int selEnd) {
ReactContext reactContext = (ReactContext) getContext();
EventDispatcher eventDispatcher = reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher();
eventDispatcher.dispatchEvent(
new ReactAztecSelectionChangeEvent(getId(), content, selStart, selEnd)
new ReactAztecSelectionChangeEvent(getId(), content, selStart, selEnd, incrementAndGetEventCounter())
);
}

Expand Down Expand Up @@ -296,7 +311,7 @@ private boolean onEnter() {
ReactContext reactContext = (ReactContext) getContext();
EventDispatcher eventDispatcher = reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher();
eventDispatcher.dispatchEvent(
new ReactAztecEnterEvent(getId(), content, cursorPositionStart, cursorPositionEnd)
new ReactAztecEnterEvent(getId(), content, cursorPositionStart, cursorPositionEnd, incrementAndGetEventCounter())
);
return true;
}
Expand All @@ -321,66 +336,27 @@ private boolean onBackspace() {
return true;
}

public void applyFormat(String format) {
ArrayList<ITextFormat> newFormats = new ArrayList<>();
switch (format) {
case ("bold"):
case ("strong"):
newFormats.add(AztecTextFormat.FORMAT_STRONG);
newFormats.add(AztecTextFormat.FORMAT_BOLD);
break;
case ("italic"):
newFormats.add(AztecTextFormat.FORMAT_ITALIC);
newFormats.add(AztecTextFormat.FORMAT_CITE);
break;
case ("strikethrough"):
newFormats.add(AztecTextFormat.FORMAT_STRIKETHROUGH);
break;
}

if (newFormats.size() == 0) {
return;
}

if (!isTextSelected()) {
final ArrayList<ITextFormat> newStylesList = getNewStylesList(newFormats);
setSelectedStyles(newStylesList);
// Update the toolbar state
updateToolbarButtons(newStylesList);
} else {
toggleFormatting(newFormats.get(0));
// Update the toolbar state
updateToolbarButtons(getSelectionStart(), getSelectionEnd());
}

// emit onChange because the underlying HTML has changed applying the style
ReactContext reactContext = (ReactContext) getContext();
EventDispatcher eventDispatcher = reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher();
eventDispatcher.dispatchEvent(
new ReactTextChangedEvent(
getId(),
toHtml(false),
incrementAndGetEventCounter())
);
}

// Removes all formats in the list but if none found, applies the first one
private ArrayList<ITextFormat> getNewStylesList(ArrayList<ITextFormat> newFormats) {
ArrayList<ITextFormat> textFormats = new ArrayList<>();
textFormats.addAll(getSelectedStyles());
boolean wasRemoved = false;
for (ITextFormat newFormat : newFormats) {
if (textFormats.contains(newFormat)) {
wasRemoved = true;
textFormats.remove(newFormat);
public void setActiveFormats(Iterable<String> newFormats) {
Set<ITextFormat> selectedStylesSet = new HashSet<>(getSelectedStyles());
Set<ITextFormat> newFormatsSet = new HashSet<>();
for (String newFormat : newFormats) {
switch (newFormat) {
case "bold":
newFormatsSet.add(AztecTextFormat.FORMAT_STRONG);
break;
case "italic":
newFormatsSet.add(AztecTextFormat.FORMAT_EMPHASIS);
break;
case "strikethrough":
newFormatsSet.add(AztecTextFormat.FORMAT_STRIKETHROUGH);
break;
}
}

if (!wasRemoved) {
textFormats.add(newFormats.get(0));
}

return textFormats;
selectedStylesSet.removeAll(typingFormatsMap.keySet());
selectedStylesSet.addAll(newFormatsSet);
ArrayList<ITextFormat> newStylesList = new ArrayList<>(selectedStylesSet);
setSelectedStyles(newStylesList);
updateToolbarButtons(newStylesList);
}

/**
Expand Down
72 changes: 13 additions & 59 deletions react-native-aztec/ios/RNTAztecView/RCTAztecView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ class RCTAztecView: Aztec.TextView {
@objc var onBlur: RCTBubblingEventBlock? = nil
@objc var onContentSizeChange: RCTBubblingEventBlock? = nil
@objc var onSelectionChange: RCTBubblingEventBlock? = nil
@objc var onActiveFormatsChange: RCTBubblingEventBlock? = nil
@objc var onActiveFormatAttributesChange: RCTBubblingEventBlock? = nil
@objc var blockType: NSDictionary? = nil {
didSet {
guard let block = blockType, let tag = block["tag"] as? String else {
Expand All @@ -20,6 +18,14 @@ class RCTAztecView: Aztec.TextView {
blockModel = BlockModel(tag: tag)
}
}
@objc var activeFormats: NSSet? = nil {
didSet {
let currentTypingAttributes = formattingIdentifiersForTypingAttributes()
for (key, value) in formatStringMap where currentTypingAttributes.contains(key) != activeFormats?.contains(value) {
toggleFormat(format: value)
}
}
}

var blockModel = BlockModel(tag: "") {
didSet {
Expand Down Expand Up @@ -353,44 +359,16 @@ class RCTAztecView: Aztec.TextView {

// MARK: - Formatting interface

@objc func apply(format: String) {
@objc func toggleFormat(format: String) {
let emptyRange = NSRange(location: selectedRange.location, length: 0)
switch format {
case "bold": toggleBold(range: selectedRange)
case "italic": toggleItalic(range: selectedRange)
case "strikethrough": toggleStrikethrough(range: selectedRange)
case "bold": toggleBold(range: emptyRange)
case "italic": toggleItalic(range: emptyRange)
case "strikethrough": toggleStrikethrough(range: emptyRange)
default: print("Format not recognized")
}
}

@objc
func setLink(with url: String, and title: String?) {
guard let url = URL(string: url) else {
return
}
if let title = title {
setLink(url, title: title, inRange: selectedRange)
} else {
setLink(url, inRange: selectedRange)
}
}

@objc
func removeLink() {
guard let expandedRange = linkFullRange(forRange: selectedRange) else {
return
}
removeLink(inRange: expandedRange)
}

func linkAttributes() -> [String: Any] {
var attributes: [String: Any] = ["isActive": false]
if let expandedRange = linkFullRange(forRange: selectedRange) {
attributes["url"] = linkURL(forRange: expandedRange)?.absoluteString ?? ""
attributes["isActive"] = true
}
return attributes
}

func forceTypingAttributesIfNeeded() {
if let formatHandler = HeadingBlockFormatHandler(block: blockModel) {
formatHandler.forceTypingFormat(on: self)
Expand All @@ -406,27 +384,6 @@ class RCTAztecView: Aztec.TextView {
}
}

func propagateFormatChanges() {
guard let onActiveFormatsChange = onActiveFormatsChange else {
return
}
let identifiers: Set<FormattingIdentifier>
if selectedRange.length > 0 {
identifiers = formattingIdentifiersSpanningRange(selectedRange)
} else {
identifiers = formattingIdentifiersForTypingAttributes()
}
let formats = identifiers.compactMap { formatStringMap[$0] }
onActiveFormatsChange(["formats": formats])
}

func propagateAttributesChanges() {
let attributes: [String: [String: Any]] = [
"link": linkAttributes()
]
onActiveFormatAttributesChange?(["attributes": attributes])
}

func propagateSelectionChanges() {
guard let onSelectionChange = onSelectionChange else {
return
Expand All @@ -440,14 +397,11 @@ class RCTAztecView: Aztec.TextView {
extension RCTAztecView: UITextViewDelegate {

func textViewDidChangeSelection(_ textView: UITextView) {
propagateAttributesChanges()
propagateFormatChanges()
propagateSelectionChanges()
}

func textViewDidChange(_ textView: UITextView) {
forceTypingAttributesIfNeeded()
propagateFormatChanges()
propagateContentChanges()
//Necessary to send height information to JS after pasting text.
textView.setNeedsLayout()
Expand Down
Loading

0 comments on commit c03900d

Please sign in to comment.