Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f62ae6ded1 | |||
| 5668dd3258 | |||
| e2335da909 | |||
| 181c0c83d8 | |||
| 3d3d581a7e | |||
| 24038e6148 | |||
| 4fe62640f1 |
+1
-1
@@ -246,7 +246,7 @@ module.exports = function (_config) {
|
||||
compileSdkVersion: 35,
|
||||
targetSdkVersion: 35,
|
||||
buildToolsVersion: '35.0.0',
|
||||
buildReactNativeFromSource: IS_PRODUCTION,
|
||||
buildReactNativeFromSource: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -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 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<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(
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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])
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user