diff --git a/ios/RNTAztecView/RCTAztecView.swift b/ios/RNTAztecView/RCTAztecView.swift index 21dca4e..5236ede 100644 --- a/ios/RNTAztecView/RCTAztecView.swift +++ b/ios/RNTAztecView/RCTAztecView.swift @@ -11,7 +11,8 @@ class RCTAztecView: Aztec.TextView { @objc var onContentSizeChange: RCTBubblingEventBlock? = nil @objc var onSelectionChange: RCTBubblingEventBlock? = nil @objc var onActiveFormatsChange: RCTBubblingEventBlock? = nil - + @objc var onActiveFormatAttributesChange: RCTBubblingEventBlock? = nil + private var previousContentSize: CGSize = .zero private lazy var placeholderLabel: UILabel = { @@ -19,6 +20,13 @@ class RCTAztecView: Aztec.TextView { return label }() + private let formatStringMap: [FormattingIdentifier: String] = [ + .bold: "Bold", + .italic: "italic", + .strikethrough: "strikethrough", + .link: "link", + ] + override init(defaultFont: UIFont, defaultParagraphStyle: ParagraphStyle, defaultMissingImage: UIImage) { super.init(defaultFont: defaultFont, defaultParagraphStyle: defaultParagraphStyle, defaultMissingImage: defaultMissingImage) commonInit() @@ -182,6 +190,35 @@ class RCTAztecView: Aztec.TextView { 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 + } // MARK: - Event Propagation @@ -202,17 +239,21 @@ class RCTAztecView: Aztec.TextView { } else { identifiers = formatIdentifiersForTypingAttributes() } - let formats = identifiers.compactMap( { (identifier) -> String? in - switch identifier { - case .bold: return "bold" - case .italic: return "italic" - case .strikethrough: return "strikethrough" - default: return nil - } - }) + let formats = identifiers.compactMap(formatString) onActiveFormatsChange(["formats": formats]) } + func propagateAttributesChanges() { + let attributes: [String: [String: Any]] = [ + "link": linkAttributes() + ] + onActiveFormatAttributesChange?(["attributes": attributes]) + } + + private func formatString(from identifier: FormattingIdentifier) -> String? { + return formatStringMap[identifier] + } + func propagateSelectionChanges() { guard let onSelectionChange = onSelectionChange else { return @@ -226,6 +267,7 @@ class RCTAztecView: Aztec.TextView { extension RCTAztecView: UITextViewDelegate { func textViewDidChangeSelection(_ textView: UITextView) { + propagateAttributesChanges() propagateFormatChanges() propagateSelectionChanges() } diff --git a/ios/RNTAztecView/RCTAztecViewManager.m b/ios/RNTAztecView/RCTAztecViewManager.m index 5937893..275946f 100644 --- a/ios/RNTAztecView/RCTAztecViewManager.m +++ b/ios/RNTAztecView/RCTAztecViewManager.m @@ -12,10 +12,13 @@ @interface RCT_EXTERN_MODULE(RCTAztecViewManager, NSObject) RCT_EXPORT_VIEW_PROPERTY(onSelectionChange, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(onActiveFormatsChange, RCTBubblingEventBlock) +RCT_EXPORT_VIEW_PROPERTY(onActiveFormatAttributesChange, RCTBubblingEventBlock) RCT_EXPORT_VIEW_PROPERTY(placeholder, NSString) RCT_EXPORT_VIEW_PROPERTY(placeholderTextColor, UIColor) 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) @end diff --git a/ios/RNTAztecView/RCTAztecViewManager.swift b/ios/RNTAztecView/RCTAztecViewManager.swift index 03cd606..bb445c4 100644 --- a/ios/RNTAztecView/RCTAztecViewManager.swift +++ b/ios/RNTAztecView/RCTAztecViewManager.swift @@ -18,6 +18,20 @@ public class RCTAztecViewManager: RCTViewManager { }, onNode: node) } + @objc + func removeLink(_ node: NSNumber) { + executeBlock({ (aztecView) in + aztecView.removeLink() + }, onNode: node) + } + + @objc + func setLink(_ node: NSNumber, url: String, title: String?) { + executeBlock({ (aztecView) in + aztecView.setLink(with: url, and: title) + }, onNode: node) + } + @objc public override func view() -> UIView { let view = RCTAztecView( diff --git a/src/AztecView.js b/src/AztecView.js index fa668a9..ea7b228 100644 --- a/src/AztecView.js +++ b/src/AztecView.js @@ -3,6 +3,8 @@ import React from 'react'; import ReactNative, {requireNativeComponent, ViewPropTypes, UIManager, ColorPropType, TouchableWithoutFeedback} from 'react-native'; import TextInputState from 'react-native/lib/TextInputState'; +const AztecManager = UIManager.RCTAztecView; + class AztecView extends React.Component { static propTypes = { @@ -22,25 +24,35 @@ class AztecView extends React.Component { onBackspace: PropTypes.func, onScroll: PropTypes.func, onActiveFormatsChange: PropTypes.func, + onActiveFormatAttributesChange: PropTypes.func, onSelectionChange: PropTypes.func, onHTMLContentWithCursor: PropTypes.func, ...ViewPropTypes, // include the default view properties } - applyFormat(format) { + dispatch(command, params) { + params = params || []; UIManager.dispatchViewManagerCommand( ReactNative.findNodeHandle(this), - UIManager.RCTAztecView.Commands.applyFormat, - [format], - ); + command, + params, + ); + } + + applyFormat(format) { + this.dispatch(AztecManager.Commands.applyFormat, [format]) + } + + removeLink() { + this.dispatch(AztecManager.Commands.removeLink) + } + + setLink(url, title) { + this.dispatch(AztecManager.Commands.setLink, [url, title]) } requestHTMLWithCursor() { - UIManager.dispatchViewManagerCommand( - ReactNative.findNodeHandle(this), - UIManager.RCTAztecView.Commands.returnHTMLWithCursor, - [], - ); + this.dispatch(AztecManager.Commands.returnHTMLWithCursor) } _onActiveFormatsChange = (event) => { @@ -52,6 +64,15 @@ class AztecView extends React.Component { onActiveFormatsChange(formats); } + _onActiveFormatAttributesChange = (event) => { + if (!this.props.onActiveFormatAttributesChange) { + return; + } + const attributes = event.nativeEvent.attributes; + const { onActiveFormatAttributesChange } = this.props; + onActiveFormatAttributesChange(attributes); + } + _onContentSizeChange = (event) => { if (!this.props.onContentSizeChange) { return; @@ -134,6 +155,7 @@ class AztecView extends React.Component {