Compare commits

...

7 Commits

Author SHA1 Message Date
Samuel Newman f62ae6ded1 4 -> 5 2026-01-08 11:33:43 +02:00
Samuel Newman 5668dd3258 copy content uri to file in cache dir 2026-01-08 11:33:43 +02:00
Samuel Newman e2335da909 pass through mimetype rather than assuming data uri 2026-01-08 11:33:43 +02:00
Samuel Newman 181c0c83d8 fix patch, build rn from source 2026-01-08 11:33:43 +02:00
Samuel Newman 3d3d581a7e check mimetype instead of URI 2026-01-08 11:33:43 +02:00
Samuel Newman 24038e6148 rm mattermost pasteinput 2026-01-08 11:33:43 +02:00
Samuel Newman 4fe62640f1 Patch in RN onPaste PR 2026-01-08 11:33:43 +02:00
11 changed files with 756 additions and 316 deletions
+1 -1
View File
@@ -246,7 +246,7 @@ module.exports = function (_config) {
compileSdkVersion: 35,
targetSdkVersion: 35,
buildToolsVersion: '35.0.0',
buildReactNativeFromSource: IS_PRODUCTION,
buildReactNativeFromSource: true,
},
},
],
-1
View File
@@ -96,7 +96,6 @@
"@haileyok/bluesky-video": "0.3.2",
"@ipld/dag-cbor": "^9.2.0",
"@lingui/react": "^4.14.1",
"@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",
@@ -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<String, Any> {
- val map = super.getExportedCustomBubblingEventTypeConstants()!!
+ val map = super.getExportedCustomBubblingEventTypeConstants().toMutableMap()
map["onPaste"] = MapBuilder.of(
"phasedRegistrationNames",
MapBuilder.of("bubbled", "onPaste")
@@ -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 parents 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 supers 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<RCTBackedTextInputViewProtocol> 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<RCTBackedTextInputViewProtocol>)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<const PasteTextInputProps &>(*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<const PasteTextInputProps &>(*_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<const PasteTextInputProps &>(*_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 <react/renderer/components/iostextinput/conversions.h>
#include <react/renderer/components/iostextinput/primitives.h>
#include <react/renderer/components/text/BaseTextProps.h>
+#include <react/renderer/components/textinput/BaseTextInputProps.h>
#include <react/renderer/components/view/ViewProps.h>
#include <react/renderer/core/Props.h>
#include <react/renderer/core/PropsParserContext.h>
@@ -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));
}
@@ -0,0 +1,719 @@
diff --git a/node_modules/react-native/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js b/node_modules/react-native/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js
index 8cc1369..6b42a26 100644
--- a/node_modules/react-native/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js
+++ b/node_modules/react-native/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js
@@ -449,6 +449,21 @@ export type AndroidTextInputNativeProps = $ReadOnly<{
}>,
>,
+ /**
+ * Invoked when the user performs the paste action.
+ */
+ onPaste?: ?DirectEventHandler<
+ $ReadOnly<{
+ target: Int32,
+ items: $ReadOnlyArray<
+ $ReadOnly<{
+ type: string,
+ data: string,
+ }>,
+ >,
+ }>,
+ >,
+
/**
* The string that will be rendered before text input has been entered.
*/
@@ -640,6 +655,9 @@ export const __INTERNAL_VIEW_CONFIG: PartialViewConfig = {
topScroll: {
registrationName: 'onScroll',
},
+ topPaste: {
+ registrationName: 'onPaste',
+ },
},
validAttributes: {
acceptDragAndDropTypes: true,
@@ -694,6 +712,7 @@ export const __INTERNAL_VIEW_CONFIG: PartialViewConfig = {
secureTextEntry: true,
textBreakStrategy: true,
onScroll: true,
+ onPaste: true,
onContentSizeChange: true,
disableFullscreenUI: true,
includeFontPadding: true,
diff --git a/node_modules/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js b/node_modules/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js
index 05bddcb..69c11a2 100644
--- a/node_modules/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js
+++ b/node_modules/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js
@@ -82,6 +82,9 @@ const RCTTextInputViewConfig: PartialViewConfigWithoutName = {
topContentSizeChange: {
registrationName: 'onContentSizeChange',
},
+ topPaste: {
+ registrationName: 'onPaste',
+ },
topChangeSync: {
registrationName: 'onChangeSync',
},
@@ -160,6 +163,7 @@ const RCTTextInputViewConfig: PartialViewConfigWithoutName = {
onSelectionChange: true,
onContentSizeChange: true,
onScroll: true,
+ onPaste: true,
onChangeSync: true,
onKeyPressSync: true,
}),
diff --git a/node_modules/react-native/Libraries/Components/TextInput/TextInput.d.ts b/node_modules/react-native/Libraries/Components/TextInput/TextInput.d.ts
index b3ca156..2807af2 100644
--- a/node_modules/react-native/Libraries/Components/TextInput/TextInput.d.ts
+++ b/node_modules/react-native/Libraries/Components/TextInput/TextInput.d.ts
@@ -565,6 +565,16 @@ export interface TextInputSubmitEditingEventData {
export type TextInputSubmitEditingEvent =
NativeSyntheticEvent<TextInputSubmitEditingEventData>;
+/**
+ * @see TextInputProps.onPaste
+ */
+export interface TextInputPasteEventData extends TargetedEvent {
+items: Array<{
+ type: string;
+ data: string;
+ }>;
+}
+
/**
* @see https://reactnative.dev/docs/textinput#props
*/
@@ -896,6 +906,13 @@ export interface TextInputProps
*/
onKeyPress?: ((e: TextInputKeyPressEvent) => void) | undefined;
+ /**
+ * Invoked when the user performs the paste action.
+ */
+ onPaste?:
+ | ((e: NativeSyntheticEvent<TextInputPasteEventData>) => void)
+ | undefined;
+
/**
* The string that will be rendered before text input has been entered
*/
diff --git a/node_modules/react-native/Libraries/Components/TextInput/TextInput.flow.js b/node_modules/react-native/Libraries/Components/TextInput/TextInput.flow.js
index 5fa8811..ad2bf61 100644
--- a/node_modules/react-native/Libraries/Components/TextInput/TextInput.flow.js
+++ b/node_modules/react-native/Libraries/Components/TextInput/TextInput.flow.js
@@ -137,6 +137,18 @@ export type TextInputSubmitEditingEvent =
export type TextInputEditingEvent =
NativeSyntheticEvent<TextInputEndEditingEventData>;
+export type PasteEvent = SyntheticEvent<
+ $ReadOnly<{
+ target: number,
+ items: $ReadOnlyArray<
+ $ReadOnly<{
+ type: string,
+ data: string,
+ }>,
+ >,
+ }>,
+>;
+
type DataDetectorTypesType =
| 'phoneNumber'
| 'link'
@@ -884,6 +896,11 @@ type TextInputBaseProps = $ReadOnly<{
*/
onScroll?: ?(e: ScrollEvent) => mixed,
+ /**
+ * Invoked when the user performs the paste action.
+ */
+ onPaste?: ?(e: PasteEvent) => mixed,
+
/**
* The string that will be rendered before text input has been entered.
*/
diff --git a/node_modules/react-native/Libraries/Text/TextInput/Multiline/RCTUITextView.mm b/node_modules/react-native/Libraries/Text/TextInput/Multiline/RCTUITextView.mm
index 6e9c384..2c509eb 100644
--- a/node_modules/react-native/Libraries/Text/TextInput/Multiline/RCTUITextView.mm
+++ b/node_modules/react-native/Libraries/Text/TextInput/Multiline/RCTUITextView.mm
@@ -13,6 +13,10 @@
#import <React/RCTBackedTextInputDelegateAdapter.h>
#import <React/RCTTextAttributes.h>
+#import <MobileCoreServices/MobileCoreServices.h>
+#import <MobileCoreServices/UTType.h>
+#import <UIKit/UIKit.h>
+
@implementation RCTUITextView {
UILabel *_placeholderView;
UITextView *_detachedTextView;
@@ -209,7 +213,31 @@ - (void)scrollRangeToVisible:(NSRange)range
- (void)paste:(id)sender
{
_textWasPasted = YES;
- [super paste:sender];
+ UIPasteboard *clipboard = [UIPasteboard generalPasteboard];
+ if (clipboard.hasImages) {
+ for (NSItemProvider *itemProvider in clipboard.itemProviders) {
+ if ([itemProvider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeImage]) {
+ for (NSString *identifier in itemProvider.registeredTypeIdentifiers) {
+ if (UTTypeConformsTo((__bridge CFStringRef)identifier, kUTTypeImage)) {
+ NSString *MIMEType = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)identifier, kUTTagClassMIMEType);
+ NSString *fileExtension = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)identifier, kUTTagClassFilenameExtension);
+ NSString *filePath = RCTTempFilePath(fileExtension, nil);
+ NSURL *fileURL = [NSURL fileURLWithPath:filePath];
+ NSData *fileData = [clipboard dataForPasteboardType:identifier];
+ [fileData writeToFile:filePath atomically:YES];
+ [_textInputDelegateAdapter didPaste:MIMEType withData:[fileURL absoluteString]];
+ break;
+ }
+ }
+ break;
+ }
+ }
+ } else {
+ if (clipboard.hasStrings) {
+ [_textInputDelegateAdapter didPaste:@"text/plain" withData:clipboard.string];
+ }
+ [super paste:sender];
+ }
}
// Turn off scroll animation to fix flaky scrolling.
@@ -301,6 +329,10 @@ - (BOOL)canPerformAction:(SEL)action withSender:(id)sender
return NO;
}
+ if (action == @selector(paste:) && [UIPasteboard generalPasteboard].hasImages) {
+ return YES;
+ }
+
return [super canPerformAction:action withSender:sender];
}
diff --git a/node_modules/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegate.h b/node_modules/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegate.h
index 7187177..da00893 100644
--- a/node_modules/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegate.h
+++ b/node_modules/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegate.h
@@ -37,6 +37,8 @@ NS_ASSUME_NONNULL_BEGIN
- (void)textInputDidChangeSelection;
+- (void)textInputDidPaste:(NSString *)type withData:(NSString *)data;
+
@optional
- (void)scrollViewDidScroll:(UIScrollView *)scrollView;
diff --git a/node_modules/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.h b/node_modules/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.h
index f1c32e6..0ce9dfe 100644
--- a/node_modules/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.h
+++ b/node_modules/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.h
@@ -20,6 +20,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)skipNextTextInputDidChangeSelectionEventWithTextRange:(UITextRange *)textRange;
- (void)selectedTextRangeWasSet;
+- (void)didPaste:(NSString *)type withData:(NSString *)data;
@end
@@ -30,6 +31,7 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)initWithTextView:(UITextView<RCTBackedTextInputViewProtocol> *)backedTextInputView;
- (void)skipNextTextInputDidChangeSelectionEventWithTextRange:(UITextRange *)textRange;
+- (void)didPaste:(NSString *)type withData:(NSString *)data;
@end
diff --git a/node_modules/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.mm b/node_modules/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.mm
index 82d9a79..8cc48ec 100644
--- a/node_modules/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.mm
+++ b/node_modules/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.mm
@@ -148,6 +148,11 @@ - (void)selectedTextRangeWasSet
[self textFieldProbablyDidChangeSelection];
}
+- (void)didPaste:(NSString *)type withData:(NSString *)data
+{
+ [_backedTextInputView.textInputDelegate textInputDidPaste:type withData:data];
+}
+
#pragma mark - Generalization
- (void)textFieldProbablyDidChangeSelection
@@ -330,6 +335,11 @@ - (void)skipNextTextInputDidChangeSelectionEventWithTextRange:(UITextRange *)tex
_previousSelectedTextRange = textRange;
}
+- (void)didPaste:(NSString *)type withData:(NSString *)data
+{
+ [_backedTextInputView.textInputDelegate textInputDidPaste:type withData:data];
+}
+
#pragma mark - Generalization
- (void)textViewProbablyDidChangeSelection
diff --git a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.h b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.h
index 4804624..90b7081 100644
--- a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.h
+++ b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.h
@@ -37,6 +37,7 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, copy, nullable) RCTDirectEventBlock onChange;
@property (nonatomic, copy, nullable) RCTDirectEventBlock onChangeSync;
@property (nonatomic, copy, nullable) RCTDirectEventBlock onScroll;
+@property (nonatomic, copy, nullable) RCTDirectEventBlock onPaste;
@property (nonatomic, assign) NSInteger mostRecentEventCount;
@property (nonatomic, assign, readonly) NSInteger nativeEventCount;
diff --git a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm
index 6a2d4f8..b6e6060 100644
--- a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm
+++ b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm
@@ -599,6 +599,26 @@ - (void)textInputDidChangeSelection
});
}
+- (void)textInputDidPaste:(NSString *)type withData:(NSString *)data
+{
+ if (!_onPaste) {
+ return;
+ }
+
+ NSMutableArray *items = [NSMutableArray new];
+ [items addObject:@{
+ @"type" : type,
+ @"data" : data,
+ }];
+
+ NSDictionary *payload = @{
+ @"target" : self.reactTag,
+ @"items" : items,
+ };
+
+ _onPaste(payload);
+}
+
- (void)updateLocalData
{
[self enforceTextAttributesIfNeeded];
diff --git a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputViewManager.mm b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputViewManager.mm
index 47adc53..1865e0a 100644
--- a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputViewManager.mm
+++ b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputViewManager.mm
@@ -70,6 +70,7 @@ @implementation RCTBaseTextInputViewManager {
RCT_EXPORT_VIEW_PROPERTY(onChangeSync, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onSelectionChange, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onScroll, RCTDirectEventBlock)
+RCT_EXPORT_VIEW_PROPERTY(onPaste, RCTDirectEventBlock)
RCT_EXPORT_SHADOW_PROPERTY(text, NSString)
RCT_EXPORT_SHADOW_PROPERTY(placeholder, NSString)
diff --git a/node_modules/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.mm b/node_modules/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.mm
index 377f41e..b8f48e6 100644
--- a/node_modules/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.mm
+++ b/node_modules/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.mm
@@ -12,6 +12,10 @@
#import <React/RCTUtils.h>
#import <React/UIView+React.h>
+#import <MobileCoreServices/MobileCoreServices.h>
+#import <MobileCoreServices/UTType.h>
+#import <UIKit/UIKit.h>
+
@implementation RCTUITextField {
RCTBackedTextFieldDelegateAdapter *_textInputDelegateAdapter;
NSDictionary<NSAttributedStringKey, id> *_defaultTextAttributes;
@@ -180,6 +184,10 @@ - (BOOL)canPerformAction:(SEL)action withSender:(id)sender
return NO;
}
+ if (action == @selector(paste:) && [UIPasteboard generalPasteboard].hasImages) {
+ return YES;
+ }
+
return [super canPerformAction:action withSender:sender];
}
@@ -263,7 +271,31 @@ - (void)scrollRangeToVisible:(NSRange)range
- (void)paste:(id)sender
{
_textWasPasted = YES;
- [super paste:sender];
+ UIPasteboard *clipboard = [UIPasteboard generalPasteboard];
+ if (clipboard.hasImages) {
+ for (NSItemProvider *itemProvider in clipboard.itemProviders) {
+ if ([itemProvider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeImage]) {
+ for (NSString *identifier in itemProvider.registeredTypeIdentifiers) {
+ if (UTTypeConformsTo((__bridge CFStringRef)identifier, kUTTypeImage)) {
+ NSString *MIMEType = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)identifier, kUTTagClassMIMEType);
+ NSString *fileExtension = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)identifier, kUTTagClassFilenameExtension);
+ NSString *filePath = RCTTempFilePath(fileExtension, nil);
+ NSURL *fileURL = [NSURL fileURLWithPath:filePath];
+ NSData *fileData = [clipboard dataForPasteboardType:identifier];
+ [fileData writeToFile:filePath atomically:YES];
+ [_textInputDelegateAdapter didPaste:MIMEType withData:[fileURL absoluteString]];
+ break;
+ }
+ }
+ break;
+ }
+ }
+ } else {
+ if (clipboard.hasStrings) {
+ [_textInputDelegateAdapter didPaste:@"text/plain" withData:clipboard.string];
+ }
+ [super paste:sender];
+ }
}
#pragma mark - Layout
diff --git a/node_modules/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm b/node_modules/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm
index 577bebe..bbde7fd 100644
--- a/node_modules/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm
+++ b/node_modules/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm
@@ -516,6 +516,13 @@ - (void)textInputDidChangeSelection
}
}
+- (void)textInputDidPaste:(NSString *)type withData:(NSString *)data
+{
+ if (_eventEmitter) {
+ static_cast<const TextInputEventEmitter &>(*_eventEmitter).onPaste(std::string([type UTF8String]), std::string([data UTF8String]));
+ }
+}
+
#pragma mark - RCTBackedTextInputDelegate (UIScrollViewDelegate)
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/PasteWatcher.kt b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/PasteWatcher.kt
new file mode 100644
index 0000000..a684b87
--- /dev/null
+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/PasteWatcher.kt
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+package com.facebook.react.views.textinput
+
+/**
+ * Implement this interface to be informed of paste event in the
+ * ReactTextEdit This is used by the ReactTextInputManager to forward events
+ * from the EditText to JS
+ */
+public fun interface PasteWatcher {
+ public fun onPaste(type: String, data: String)
+}
diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.kt b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.kt
index 42f6e03..158e4e4 100644
--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.kt
+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.kt
@@ -8,6 +8,10 @@
package com.facebook.react.views.textinput
import android.annotation.SuppressLint
+import android.content.ClipboardManager
+import android.content.ClipData
+import android.content.ClipDescription
+import android.content.ContentResolver
import android.content.Context
import android.content.res.Configuration
import android.graphics.Canvas
@@ -15,7 +19,12 @@ import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
import android.graphics.drawable.Drawable
+import android.net.Uri
+import com.facebook.react.uimanager.UIManagerHelper.getReactContext
import android.os.Build
+import java.io.File
+import java.io.FileOutputStream
+import java.io.InputStream
import android.os.Bundle
import android.text.Editable
import android.text.InputType
@@ -128,6 +137,7 @@ public open class ReactEditText public constructor(context: Context) : AppCompat
private var selectionWatcher: SelectionWatcher? = null
private var contentSizeWatcher: ContentSizeWatcher? = null
private var scrollWatcher: ScrollWatcher?
+ private var pasteWatcher: PasteWatcher?
private var keyListener: InternalKeyListener? = null
private var detectScrollMovement = false
private var onKeyPress = false
@@ -212,6 +222,7 @@ public open class ReactEditText public constructor(context: Context) : AppCompat
keyListener = InternalKeyListener()
}
scrollWatcher = null
+ pasteWatcher = null
textAttributes = TextAttributes()
applyTextAttributes()
@@ -356,9 +367,57 @@ public open class ReactEditText public constructor(context: Context) : AppCompat
* Called when a context menu option for the text view is selected.
* React Native replaces copy (as rich text) with copy as plain text.
*/
- override fun onTextContextMenuItem(id: Int): Boolean =
- super.onTextContextMenuItem(
- if (id == android.R.id.paste) android.R.id.pasteAsPlainText else id)
+ override fun onTextContextMenuItem(id: Int): Boolean {
+ val modifiedId = if (id == android.R.id.paste || id == android.R.id.pasteAsPlainText) {
+ android.R.id.pasteAsPlainText
+ } else {
+ id
+ }
+
+ if (modifiedId == android.R.id.pasteAsPlainText) {
+ val clipboardManager = getContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
+ val clipData = clipboardManager.primaryClip
+ if (clipData != null) {
+ val item = clipData.getItemAt(0)
+ val itemUri = item.uri
+ var type: String? = null
+ var data: String? = null
+
+ if (itemUri != null) {
+ // First try to get MIME type from ClipData description (more reliable for FileProvider URIs)
+ type = if (clipData.description.mimeTypeCount > 0) {
+ clipData.description.getMimeType(0)
+ } else {
+ // Fall back to ContentResolver
+ val cr = getReactContext(this).contentResolver
+ cr.getType(itemUri)
+ }
+ if (type != null && type != ClipDescription.MIMETYPE_TEXT_PLAIN) {
+ // Copy content URI to cache and get file:// URI
+ data = copyContentUriToCache(itemUri, type)
+ if (data != null && pasteWatcher != null) {
+ pasteWatcher?.onPaste(type, data)
+ }
+ // Prevents default behavior to avoid inserting raw binary data into the text field
+ return true
+ }
+ }
+
+ if (clipData.description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) {
+ type = ClipDescription.MIMETYPE_TEXT_PLAIN
+ val text: CharSequence? = item.text
+ if (text != null) {
+ data = text.toString()
+ if (pasteWatcher != null) {
+ pasteWatcher?.onPaste(type, data)
+ }
+ // Don't return - let the system proceed with default text pasting behavior
+ }
+ }
+ }
+ }
+ return super.onTextContextMenuItem(id)
+ }
internal fun clearFocusAndMaybeRefocus() {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P || !isInTouchMode) {
@@ -421,6 +480,45 @@ public open class ReactEditText public constructor(context: Context) : AppCompat
this.scrollWatcher = scrollWatcher
}
+ public fun setPasteWatcher(pasteWatcher: PasteWatcher?) {
+ this.pasteWatcher = pasteWatcher
+ }
+
+ /**
+ * Copies a content URI to the cache directory and returns a file:// URI
+ */
+ private fun copyContentUriToCache(contentUri: Uri, mimeType: String?): String? {
+ try {
+ val cr = getReactContext(this).contentResolver
+ val inputStream: InputStream = cr.openInputStream(contentUri) ?: return null
+
+ // Generate filename with appropriate extension
+ val extension = when (mimeType) {
+ "image/jpeg", "image/jpg" -> "jpg"
+ "image/png" -> "png"
+ "image/gif" -> "gif"
+ "image/webp" -> "webp"
+ else -> "jpg"
+ }
+ val fileName = "paste_${System.currentTimeMillis()}.$extension"
+
+ val cacheDir = context.cacheDir
+ val outputFile = File(cacheDir, fileName)
+ val outputStream = FileOutputStream(outputFile)
+
+ inputStream.use { input ->
+ outputStream.use { output ->
+ input.copyTo(output)
+ }
+ }
+
+ return "file://${outputFile.absolutePath}"
+ } catch (e: Exception) {
+ e.printStackTrace()
+ return null
+ }
+ }
+
/**
* Attempt to set a selection or fail silently. Intentionally meant to handle bad inputs.
* EventCounter is the same one used as with text.
diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.kt b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.kt
index 42c13a1..c39e240 100644
--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.kt
+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.kt
@@ -133,6 +133,8 @@ public open class ReactTextInputManager public constructor() :
val eventTypeConstants = baseEventTypeConstants ?: mutableMapOf()
eventTypeConstants.putAll(
mapOf(getJSEventName(ScrollEventType.SCROLL) to mapOf("registrationName" to "onScroll")))
+ eventTypeConstants.putAll(
+ mapOf("topPaste" to mapOf("registrationName" to "onPaste")))
return eventTypeConstants
}
@@ -327,6 +329,15 @@ public open class ReactTextInputManager public constructor() :
}
}
+ @ReactProp(name = "onPaste", defaultBoolean = false)
+ public fun setOnPaste(view: ReactEditText, onPaste: Boolean) {
+ if (onPaste) {
+ view.setPasteWatcher(ReactPasteWatcher(view))
+ } else {
+ view.setPasteWatcher(null)
+ }
+ }
+
@ReactProp(name = "onKeyPress", defaultBoolean = false)
public fun setOnKeyPress(view: ReactEditText, onKeyPress: Boolean) {
view.setOnKeyPress(onKeyPress)
@@ -944,6 +955,24 @@ public open class ReactTextInputManager public constructor() :
}
}
+ private class ReactPasteWatcher(editText: ReactEditText) : PasteWatcher {
+ private val mReactEditText: ReactEditText = editText
+ private val mEventDispatcher: EventDispatcher?
+ private val mSurfaceId: Int
+
+ init {
+ val reactContext = UIManagerHelper.getReactContext(editText)
+ mEventDispatcher = getEventDispatcher(reactContext, editText)
+ mSurfaceId = UIManagerHelper.getSurfaceId(reactContext)
+ }
+
+ override fun onPaste(type: String, data: String) {
+ mEventDispatcher?.dispatchEvent(
+ ReactTextInputPasteEvent(mSurfaceId, mReactEditText.id, type, data)
+ )
+ }
+ }
+
override fun getExportedViewConstants(): Map<String, Any> =
mapOf(
"AutoCapitalizationType" to
diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputPasteEvent.kt b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputPasteEvent.kt
new file mode 100644
index 0000000..6f5b10b
--- /dev/null
+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputPasteEvent.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+package com.facebook.react.views.textinput
+
+import androidx.annotation.Nullable
+import com.facebook.react.bridge.Arguments
+import com.facebook.react.bridge.WritableMap
+import com.facebook.react.bridge.WritableArray
+import com.facebook.react.uimanager.common.ViewUtil
+import com.facebook.react.uimanager.events.Event
+
+/**
+ * Event emitted by EditText native view when clipboard content is pasted
+ */
+public class ReactTextInputPasteEvent : Event<ReactTextInputPasteEvent> {
+
+ public companion object {
+ private const val EVENT_NAME = "topPaste"
+ }
+
+ private val mType: String
+ private val mData: String
+
+ @Deprecated("Use constructor with surfaceId")
+ public constructor(viewId: Int, type: String, data: String) :
+ this(ViewUtil.NO_SURFACE_ID, viewId, type, data)
+
+ public constructor(surfaceId: Int, viewId: Int, type: String, data: String) :
+ super(surfaceId, viewId) {
+ mType = type
+ mData = data
+ }
+
+ override fun getEventName(): String {
+ return EVENT_NAME
+ }
+
+ override fun canCoalesce(): Boolean {
+ return false
+ }
+
+ @Nullable
+ override fun getEventData(): WritableMap? {
+ val eventData = Arguments.createMap()
+
+ val items: WritableArray = Arguments.createArray()
+ val item: WritableMap = Arguments.createMap()
+ item.putString("type", mType)
+ item.putString("data", mData)
+ items.pushMap(item)
+
+ eventData.putArray("items", items)
+
+ return eventData
+ }
+}
diff --git a/node_modules/react-native/ReactCommon/react/renderer/components/textinput/TextInputEventEmitter.cpp b/node_modules/react-native/ReactCommon/react/renderer/components/textinput/TextInputEventEmitter.cpp
index a9bc219..7ebec12 100644
--- a/node_modules/react-native/ReactCommon/react/renderer/components/textinput/TextInputEventEmitter.cpp
+++ b/node_modules/react-native/ReactCommon/react/renderer/components/textinput/TextInputEventEmitter.cpp
@@ -177,6 +177,19 @@ void TextInputEventEmitter::onScroll(const Metrics& textInputMetrics) const {
});
}
+void TextInputEventEmitter::onPaste(const std::string& type, const std::string& data) const {
+ dispatchEvent("onPaste", [type, data](jsi::Runtime& runtime) {
+ auto payload = jsi::Object(runtime);
+ auto items = jsi::Array(runtime, 1);
+ auto item = jsi::Object(runtime);
+ item.setProperty(runtime, "type", type);
+ item.setProperty(runtime, "data", data);
+ items.setValueAtIndex(runtime, 0, item);
+ payload.setProperty(runtime, "items", items);
+ return payload;
+ });
+}
+
void TextInputEventEmitter::dispatchTextInputEvent(
const std::string& name,
const Metrics& textInputMetrics,
diff --git a/node_modules/react-native/ReactCommon/react/renderer/components/textinput/TextInputEventEmitter.h b/node_modules/react-native/ReactCommon/react/renderer/components/textinput/TextInputEventEmitter.h
index dbce575..5b03581 100644
--- a/node_modules/react-native/ReactCommon/react/renderer/components/textinput/TextInputEventEmitter.h
+++ b/node_modules/react-native/ReactCommon/react/renderer/components/textinput/TextInputEventEmitter.h
@@ -44,6 +44,7 @@ class TextInputEventEmitter : public ViewEventEmitter {
void onSubmitEditing(const Metrics& textInputMetrics) const;
void onKeyPress(const KeyPressMetrics& keyPressMetrics) const;
void onScroll(const Metrics& textInputMetrics) const;
+ void onPaste(const std::string& type, const std::string& data) const;
private:
void dispatchTextInputEvent(
+4
View File
@@ -8,6 +8,10 @@ export function getDataUriSize(uri: string): number {
return Math.round((uri.length * 3) / 4)
}
export function isAcceptedImageMimeType(type: string): boolean {
return ['image/jpeg', 'image/png', 'image/webp'].includes(type)
}
export function isUriImage(uri: string): boolean {
return /\.(jpg|jpeg|png|webp).*$/.test(uri)
}
+8 -2
View File
@@ -103,9 +103,15 @@ export function createInitialImages(
export async function pasteImage(
uri: string,
mimeType?: string,
): Promise<ComposerImageWithoutTransformation> {
const {width, height} = await getImageDim(uri)
const match = /^data:(.+?);/.exec(uri)
let mime = mimeType
if (!mime) {
const match = /^data:(.+?);/.exec(uri)
mime = match ? match[1] : 'image/jpeg'
}
return {
alt: '',
@@ -114,7 +120,7 @@ export async function pasteImage(
path: uri,
width: width,
height: height,
mime: match ? match[1] : 'image/jpeg',
mime,
},
}
}
+3 -2
View File
@@ -886,7 +886,7 @@ let ComposerPost = React.memo(function ComposerPost({
)
const onPhotoPasted = useCallback(
async (uri: string) => {
async (uri: string, mimeType?: string) => {
if (
uri.startsWith('data:video/') ||
(isWeb && uri.startsWith('data:image/gif'))
@@ -905,7 +905,8 @@ let ComposerPost = React.memo(function ComposerPost({
.then(blob => new File([blob], name, {type: mimeType}))
onSelectVideo(post.id, await getVideoMetadata(file))
} else {
const res = await pasteImage(uri)
const res = await pasteImage(uri, mimeType)
onImageAdd([res])
}
},
+15 -21
View File
@@ -8,19 +8,16 @@ import {
import {
type NativeSyntheticEvent,
Text as RNText,
type TextInputSelectionChangeEventData,
TextInput as RNTextInput,
type TextInputPasteEventData,
type TextInputSelectionChangeEvent,
View,
} from 'react-native'
import {AppBskyRichtextFacet, RichText} from '@atproto/api'
import PasteInput, {
type PastedFile,
type PasteInputRef, // @ts-expect-error no types when installing from github
} from '@mattermost/react-native-paste-input'
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 {isAcceptedImageMimeType, isUriImage} from '#/lib/media/util'
import {getMentionAt, insertMentionAt} from '#/lib/strings/mention-manip'
import {useTheme} from '#/lib/ThemeContext'
import {isAndroid, isNative} from '#/platform/detection'
@@ -46,11 +43,11 @@ export function TextInput({
setRichText,
onPhotoPasted,
onNewLink,
onError,
onError: _onError,
...props
}: TextInputProps) {
const {theme: t, fonts} = useAlf()
const textInput = useRef<PasteInputRef>(null)
const textInput = useRef<RNTextInput>(null)
const textInputSelection = useRef<Selection>({start: 0, end: 0})
const theme = useTheme()
const [autocompletePrefix, setAutocompletePrefix] = useState('')
@@ -127,23 +124,20 @@ export function TextInput({
)
const onPaste = useCallback(
async (err: string | undefined, files: PastedFile[]) => {
if (err) {
return onError(cleanError(err))
}
async (evt: NativeSyntheticEvent<TextInputPasteEventData>) => {
const files = evt.nativeEvent.items
const uris = files.map(f => f.uri)
const uri = uris.find(isUriImage)
const file = files.find(f => isAcceptedImageMimeType(f.type))
if (uri) {
onPhotoPasted(uri)
if (file) {
onPhotoPasted(file.data, file.type)
}
},
[onError, onPhotoPasted],
[onPhotoPasted],
)
const onSelectionChange = useCallback(
(evt: NativeSyntheticEvent<TextInputSelectionChangeEventData>) => {
(evt: TextInputSelectionChangeEvent) => {
// NOTE we track the input selection using a ref to avoid excessive renders -prf
textInputSelection.current = evt.nativeEvent.selection
},
@@ -215,7 +209,7 @@ export function TextInput({
return (
<View style={[a.flex_1, a.pl_md, hasRightPadding && a.pr_4xl]}>
<PasteInput
<RNTextInput
testID="composerTextInput"
ref={textInput}
onChangeText={onChangeText}
@@ -249,7 +243,7 @@ export function TextInput({
props.style,
]}>
{textDecorated}
</PasteInput>
</RNTextInput>
<Autocomplete
prefix={autocompletePrefix}
onSelect={onSelectAutocompleteItem}
@@ -26,7 +26,7 @@ export type TextInputProps = {
hasRightPadding: boolean
isActive: boolean
setRichText: (v: RichText) => void
onPhotoPasted: (uri: string) => void
onPhotoPasted: (uri: string, mimeType?: string) => void
onPressPublish: (richtext: RichText) => void
onNewLink: (uri: string) => void
onError: (err: string) => void
+5 -11
View File
@@ -5299,12 +5299,6 @@
"@babel/runtime" "^7.20.13"
"@lingui/core" "4.14.1"
"@mattermost/react-native-paste-input@mattermost/react-native-paste-input":
version "0.8.1"
resolved "https://codeload.github.com/mattermost/react-native-paste-input/tar.gz/f260447edc645a817ab1ba7b46d8341d84dba8e9"
dependencies:
semver "7.6.3"
"@messageformat/parser@^5.0.0":
version "5.1.0"
resolved "https://registry.yarnpkg.com/@messageformat/parser/-/parser-5.1.0.tgz#05e4851c782d633ad735791dd0a68ee65d2a7201"
@@ -17582,16 +17576,16 @@ semver-compare@^1.0.0:
resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc"
integrity sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==
semver@7.6.3, semver@^7.1.3, semver@^7.6.3:
version "7.6.3"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143"
integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==
semver@^6.0.0, semver@^6.3.0, semver@^6.3.1:
version "6.3.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
semver@^7.1.3, semver@^7.6.3:
version "7.6.3"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143"
integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==
semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.3, semver@^7.5.4, semver@~7.5.4:
version "7.5.4"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e"