diff --git a/package.json b/package.json index e815bea407..2f2bad9932 100644 --- a/package.json +++ b/package.json @@ -109,7 +109,6 @@ "@ipld/dag-cbor": "^9.2.0", "@lingui/core": "^5.9.2", "@lingui/react": "^5.9.2", - "@mattermost/react-native-paste-input": "mattermost/react-native-paste-input", "@miblanchard/react-native-slider": "^2.6.0", "@mozzius/expo-dynamic-app-icon": "^1.8.0", "@react-native-async-storage/async-storage": "2.2.0", @@ -167,6 +166,7 @@ "expo-location": "~19.0.8", "expo-media-library": "~18.2.1", "expo-notifications": "~0.32.16", + "expo-paste-input": "^0.1.10", "expo-privacy-sensitive": "^0.1.0", "expo-screen-orientation": "~9.0.8", "expo-sharing": "~14.0.8", diff --git a/patches/@mattermost+react-native-paste-input+0.8.1.patch b/patches/@mattermost+react-native-paste-input+0.8.1.patch deleted file mode 100644 index c0a895715e..0000000000 --- a/patches/@mattermost+react-native-paste-input+0.8.1.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/node_modules/@mattermost/react-native-paste-input/android/src/main/java/com/mattermost/pasteinputtext/PasteTextInputManager.kt b/node_modules/@mattermost/react-native-paste-input/android/src/main/java/com/mattermost/pasteinputtext/PasteTextInputManager.kt -index 4ed2307..ede1181 100644 ---- a/node_modules/@mattermost/react-native-paste-input/android/src/main/java/com/mattermost/pasteinputtext/PasteTextInputManager.kt -+++ b/node_modules/@mattermost/react-native-paste-input/android/src/main/java/com/mattermost/pasteinputtext/PasteTextInputManager.kt -@@ -54,7 +54,7 @@ class PasteTextInputManager(context: ReactApplicationContext) : ReactTextInputMa - } - - override fun getExportedCustomBubblingEventTypeConstants(): MutableMap { -- val map = super.getExportedCustomBubblingEventTypeConstants()!! -+ val map = super.getExportedCustomBubblingEventTypeConstants().toMutableMap() - map["onPaste"] = MapBuilder.of( - "phasedRegistrationNames", - MapBuilder.of("bubbled", "onPaste") diff --git a/patches/@mattermost+react-native-paste-input+0.8.1.patch.disabled b/patches/@mattermost+react-native-paste-input+0.8.1.patch.disabled deleted file mode 100644 index a7f1461432..0000000000 --- a/patches/@mattermost+react-native-paste-input+0.8.1.patch.disabled +++ /dev/null @@ -1,264 +0,0 @@ -diff --git a/node_modules/@mattermost/react-native-paste-input/ios/PasteInputView.m b/node_modules/@mattermost/react-native-paste-input/ios/PasteInputView.m -index e916023..5049c33 100644 ---- a/node_modules/@mattermost/react-native-paste-input/ios/PasteInputView.m -+++ b/node_modules/@mattermost/react-native-paste-input/ios/PasteInputView.m -@@ -4,6 +4,7 @@ - // - // Created by Elias Nahum on 04-11-20. - // Copyright © 2020 Facebook. All rights reserved. -+// Updated to remove parent’s default text view - // - - #import "PasteInputView.h" -@@ -12,49 +13,78 @@ - - @implementation PasteInputView - { -- PasteInputTextView *_backedTextInputView; -+ // We'll store the custom text view in this ivar -+ PasteInputTextView *_customBackedTextView; - } - - - (instancetype)initWithBridge:(RCTBridge *)bridge - { -+ // Must call the super’s designated initializer - if (self = [super initWithBridge:bridge]) { -- _backedTextInputView = [[PasteInputTextView alloc] initWithFrame:self.bounds]; -- _backedTextInputView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; -- _backedTextInputView.textInputDelegate = self; -+ // 1. The parent (RCTMultilineTextInputView) has already created -+ // its own _backedTextInputView = [RCTUITextView new] in super init. -+ // We can remove that subview: - -- [self addSubview:_backedTextInputView]; -- } -+ id parentInputView = super.backedTextInputView; -+ if ([parentInputView isKindOfClass:[UIView class]]) { -+ UIView *parentSubview = (UIView *)parentInputView; -+ if (parentSubview.superview == self) { -+ [parentSubview removeFromSuperview]; -+ } -+ } - -+ // 2. Now create our custom PasteInputTextView -+ _customBackedTextView = [[PasteInputTextView alloc] initWithFrame:self.bounds]; -+ _customBackedTextView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; -+ _customBackedTextView.textInputDelegate = self; -+ -+ // Optional: disable inline predictions for iOS 17+ -+ if (@available(iOS 17.0, *)) { -+ _customBackedTextView.inlinePredictionType = UITextInlinePredictionTypeNo; -+ } -+ -+ // 3. Add your custom text view as the only subview -+ [self addSubview:_customBackedTextView]; -+ } - return self; - } - -+/** -+ * Override the parent's accessor so that anywhere in RN that calls -+ * `self.backedTextInputView` will get the custom PasteInputTextView. -+ */ - - (id)backedTextInputView - { -- return _backedTextInputView; -+ return _customBackedTextView; - } - --- (void)setDisableCopyPaste:(BOOL)disableCopyPaste { -- _backedTextInputView.disableCopyPaste = disableCopyPaste; -+#pragma mark - Setters for React Props -+ -+- (void)setDisableCopyPaste:(BOOL)disableCopyPaste -+{ -+ _customBackedTextView.disableCopyPaste = disableCopyPaste; - } - --- (void)setOnPaste:(RCTDirectEventBlock)onPaste { -- _backedTextInputView.onPaste = onPaste; -+- (void)setOnPaste:(RCTDirectEventBlock)onPaste -+{ -+ _customBackedTextView.onPaste = onPaste; - } - --- (void)setSmartPunctuation:(NSString *)smartPunctuation { -- if ([smartPunctuation isEqualToString:@"enable"]) { -- [_backedTextInputView setSmartDashesType:UITextSmartDashesTypeYes]; -- [_backedTextInputView setSmartQuotesType:UITextSmartQuotesTypeYes]; -- [_backedTextInputView setSmartInsertDeleteType:UITextSmartInsertDeleteTypeYes]; -- } else if ([smartPunctuation isEqualToString:@"disable"]) { -- [_backedTextInputView setSmartDashesType:UITextSmartDashesTypeNo]; -- [_backedTextInputView setSmartQuotesType:UITextSmartQuotesTypeNo]; -- [_backedTextInputView setSmartInsertDeleteType:UITextSmartInsertDeleteTypeNo]; -- } else { -- [_backedTextInputView setSmartDashesType:UITextSmartDashesTypeDefault]; -- [_backedTextInputView setSmartQuotesType:UITextSmartQuotesTypeDefault]; -- [_backedTextInputView setSmartInsertDeleteType:UITextSmartInsertDeleteTypeDefault]; -- } -+- (void)setSmartPunctuation:(NSString *)smartPunctuation -+{ -+ if ([smartPunctuation isEqualToString:@"enable"]) { -+ [_customBackedTextView setSmartDashesType:UITextSmartDashesTypeYes]; -+ [_customBackedTextView setSmartQuotesType:UITextSmartQuotesTypeYes]; -+ [_customBackedTextView setSmartInsertDeleteType:UITextSmartInsertDeleteTypeYes]; -+ } else if ([smartPunctuation isEqualToString:@"disable"]) { -+ [_customBackedTextView setSmartDashesType:UITextSmartDashesTypeNo]; -+ [_customBackedTextView setSmartQuotesType:UITextSmartQuotesTypeNo]; -+ [_customBackedTextView setSmartInsertDeleteType:UITextSmartInsertDeleteTypeNo]; -+ } else { -+ [_customBackedTextView setSmartDashesType:UITextSmartDashesTypeDefault]; -+ [_customBackedTextView setSmartQuotesType:UITextSmartQuotesTypeDefault]; -+ [_customBackedTextView setSmartInsertDeleteType:UITextSmartInsertDeleteTypeDefault]; -+ } - } - - #pragma mark - UIScrollViewDelegate -@@ -62,7 +92,6 @@ - (void)setSmartPunctuation:(NSString *)smartPunctuation { - - (void)scrollViewDidScroll:(UIScrollView *)scrollView - { - RCTDirectEventBlock onScroll = self.onScroll; -- - if (onScroll) { - CGPoint contentOffset = scrollView.contentOffset; - CGSize contentSize = scrollView.contentSize; -@@ -71,22 +100,22 @@ - (void)scrollViewDidScroll:(UIScrollView *)scrollView - - onScroll(@{ - @"contentOffset": @{ -- @"x": @(contentOffset.x), -- @"y": @(contentOffset.y) -+ @"x": @(contentOffset.x), -+ @"y": @(contentOffset.y) - }, - @"contentInset": @{ -- @"top": @(contentInset.top), -- @"left": @(contentInset.left), -- @"bottom": @(contentInset.bottom), -- @"right": @(contentInset.right) -+ @"top": @(contentInset.top), -+ @"left": @(contentInset.left), -+ @"bottom": @(contentInset.bottom), -+ @"right": @(contentInset.right) - }, - @"contentSize": @{ -- @"width": @(contentSize.width), -- @"height": @(contentSize.height) -+ @"width": @(contentSize.width), -+ @"height": @(contentSize.height) - }, - @"layoutMeasurement": @{ -- @"width": @(size.width), -- @"height": @(size.height) -+ @"width": @(size.width), -+ @"height": @(size.height) - }, - @"zoomScale": @(scrollView.zoomScale ?: 1), - }); -diff --git a/node_modules/@mattermost/react-native-paste-input/ios/PasteTextInput.mm b/node_modules/@mattermost/react-native-paste-input/ios/PasteTextInput.mm -index dd50053..2ed7017 100644 ---- a/node_modules/@mattermost/react-native-paste-input/ios/PasteTextInput.mm -+++ b/node_modules/@mattermost/react-native-paste-input/ios/PasteTextInput.mm -@@ -122,8 +122,8 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared & - const auto &newTextInputProps = static_cast(*props); - - // Traits: -- if (newTextInputProps.traits.multiline != oldTextInputProps.traits.multiline) { -- [self _setMultiline:newTextInputProps.traits.multiline]; -+ if (newTextInputProps.multiline != oldTextInputProps.multiline) { -+ [self _setMultiline:newTextInputProps.multiline]; - } - - if (newTextInputProps.traits.autocapitalizationType != oldTextInputProps.traits.autocapitalizationType) { -@@ -421,7 +421,7 @@ - (void)textInputDidChangeSelection - return; - } - const auto &props = static_cast(*_props); -- if (props.traits.multiline && ![_lastStringStateWasUpdatedWith isEqual:_backedTextInputView.attributedText]) { -+ if (props.multiline && ![_lastStringStateWasUpdatedWith isEqual:_backedTextInputView.attributedText]) { - [self textInputDidChange]; - _ignoreNextTextInputCall = YES; - } -@@ -708,11 +708,11 @@ - (BOOL)_textOf:(NSAttributedString *)newText equals:(NSAttributedString *)oldTe - - (SubmitBehavior)getSubmitBehavior - { - const auto &props = static_cast(*_props); -- const SubmitBehavior submitBehaviorDefaultable = props.traits.submitBehavior; -+ const SubmitBehavior submitBehaviorDefaultable = props.submitBehavior; - - // We should always have a non-default `submitBehavior`, but in case we don't, set it based on multiline. - if (submitBehaviorDefaultable == SubmitBehavior::Default) { -- return props.traits.multiline ? SubmitBehavior::Newline : SubmitBehavior::BlurAndSubmit; -+ return props.multiline ? SubmitBehavior::Newline : SubmitBehavior::BlurAndSubmit; - } - - return submitBehaviorDefaultable; -diff --git a/node_modules/@mattermost/react-native-paste-input/ios/PasteTextInputSpecs/Props.cpp b/node_modules/@mattermost/react-native-paste-input/ios/PasteTextInputSpecs/Props.cpp -index 29e094f..7ef519a 100644 ---- a/node_modules/@mattermost/react-native-paste-input/ios/PasteTextInputSpecs/Props.cpp -+++ b/node_modules/@mattermost/react-native-paste-input/ios/PasteTextInputSpecs/Props.cpp -@@ -22,8 +22,7 @@ PasteTextInputProps::PasteTextInputProps( - const PropsParserContext &context, - const PasteTextInputProps &sourceProps, - const RawProps& rawProps) -- : ViewProps(context, sourceProps, rawProps), -- BaseTextProps(context, sourceProps, rawProps), -+ : BaseTextInputProps(context, sourceProps, rawProps), - traits(convertRawProp(context, rawProps, sourceProps.traits, {})), - smartPunctuation(convertRawProp(context, rawProps, "smartPunctuation", sourceProps.smartPunctuation, {})), - disableCopyPaste(convertRawProp(context, rawProps, "disableCopyPaste", sourceProps.disableCopyPaste, {false})), -@@ -133,7 +132,7 @@ TextAttributes PasteTextInputProps::getEffectiveTextAttributes(Float fontSizeMul - ParagraphAttributes PasteTextInputProps::getEffectiveParagraphAttributes() const { - auto result = paragraphAttributes; - -- if (!traits.multiline) { -+ if (!multiline) { - result.maximumNumberOfLines = 1; - } - -diff --git a/node_modules/@mattermost/react-native-paste-input/ios/PasteTextInputSpecs/Props.h b/node_modules/@mattermost/react-native-paste-input/ios/PasteTextInputSpecs/Props.h -index 723d00c..31cfe66 100644 ---- a/node_modules/@mattermost/react-native-paste-input/ios/PasteTextInputSpecs/Props.h -+++ b/node_modules/@mattermost/react-native-paste-input/ios/PasteTextInputSpecs/Props.h -@@ -15,6 +15,7 @@ - #include - #include - #include -+#include - #include - #include - #include -@@ -25,7 +26,7 @@ - - namespace facebook::react { - --class PasteTextInputProps final : public ViewProps, public BaseTextProps { -+class PasteTextInputProps final : public BaseTextInputProps { - public: - PasteTextInputProps() = default; - PasteTextInputProps(const PropsParserContext& context, const PasteTextInputProps& sourceProps, const RawProps& rawProps); -diff --git a/node_modules/@mattermost/react-native-paste-input/ios/PasteTextInputSpecs/ShadowNodes.cpp b/node_modules/@mattermost/react-native-paste-input/ios/PasteTextInputSpecs/ShadowNodes.cpp -index 31e07e3..7f0ebfb 100644 ---- a/node_modules/@mattermost/react-native-paste-input/ios/PasteTextInputSpecs/ShadowNodes.cpp -+++ b/node_modules/@mattermost/react-native-paste-input/ios/PasteTextInputSpecs/ShadowNodes.cpp -@@ -91,20 +91,11 @@ void PasteTextInputShadowNode::updateStateIfNeeded( - const auto& state = getStateData(); - - react_native_assert(textLayoutManager_); -- react_native_assert( -- (!state.layoutManager || state.layoutManager == textLayoutManager_) && -- "`StateData` refers to a different `TextLayoutManager`"); -- -- if (state.reactTreeAttributedString == reactTreeAttributedString && -- state.layoutManager == textLayoutManager_) { -- return; -- } - - auto newState = TextInputState{}; - newState.attributedStringBox = AttributedStringBox{reactTreeAttributedString}; - newState.paragraphAttributes = getConcreteProps().paragraphAttributes; - newState.reactTreeAttributedString = reactTreeAttributedString; -- newState.layoutManager = textLayoutManager_; - newState.mostRecentEventCount = getConcreteProps().mostRecentEventCount; - setStateData(std::move(newState)); - } diff --git a/src/view/com/composer/text-input/TextInput.tsx b/src/view/com/composer/text-input/TextInput.tsx index 0ba2e958a6..6806f037bf 100644 --- a/src/view/com/composer/text-input/TextInput.tsx +++ b/src/view/com/composer/text-input/TextInput.tsx @@ -8,21 +8,17 @@ import { import { type NativeSyntheticEvent, Text as RNText, + TextInput as RNTextInput, type TextInputSelectionChangeEventData, View, } from 'react-native' +import {type PasteEventPayload, TextInputWrapper} from 'expo-paste-input' import {AppBskyRichtextFacet, RichText} from '@atproto/api' -import PasteInput, { - type PastedFile, - type PasteInputRef, - // @ts-expect-error no types when installing from github - // eslint-disable-next-line import-x/no-unresolved -} from '@mattermost/react-native-paste-input' +import {useLingui} from '@lingui/react/macro' import {POST_IMG_MAX} from '#/lib/constants' import {downloadAndResize} from '#/lib/media/manip' import {isUriImage} from '#/lib/media/util' -import {cleanError} from '#/lib/strings/errors' import {getMentionAt, insertMentionAt} from '#/lib/strings/mention-manip' import {useTheme} from '#/lib/ThemeContext' import { @@ -51,8 +47,9 @@ export function TextInput({ onError, ...props }: TextInputProps) { + const {t: l} = useLingui() const {theme: t, fonts} = useAlf() - const textInput = useRef(null) + const textInput = useRef(null) const textInputSelection = useRef({start: 0, end: 0}) const theme = useTheme() const [autocompletePrefix, setAutocompletePrefix] = useState('') @@ -129,19 +126,21 @@ export function TextInput({ ) const onPaste = useCallback( - async (err: string | undefined, files: PastedFile[]) => { - if (err) { - return onError(cleanError(err)) + (payload: PasteEventPayload) => { + if (payload.type === 'unsupported') { + onError(l`Unsupported clipboard content`) + return } - const uris = files.map(f => f.uri) - const uri = uris.find(isUriImage) - - if (uri) { - onPhotoPasted(uri) + if (payload.type === 'images') { + for (const uri of payload.uris) { + if (isUriImage(uri)) { + onPhotoPasted(uri) + } + } } }, - [onError, onPhotoPasted], + [l, onError, onPhotoPasted], ) const onSelectionChange = useCallback( @@ -217,41 +216,42 @@ export function TextInput({ return ( - - {textDecorated} - + + + {textDecorated} + +