Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support image pasting on Native #45722

Merged
merged 12 commits into from
Aug 7, 2024
458 changes: 458 additions & 0 deletions patches/react-native+0.73.4+018+Add-onPaste-to-TextInput.patch
s77rt marked this conversation as resolved.
Show resolved Hide resolved

Large diffs are not rendered by default.

70 changes: 70 additions & 0 deletions patches/react-native+0.73.4+019+iOS-Image-Pasting.patch
s77rt marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
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 43c6c7d..5904762 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,8 @@
#import <React/RCTBackedTextInputDelegateAdapter.h>
#import <React/RCTTextAttributes.h>

+#import <UIKit/UIKit.h>
+
@implementation RCTUITextView {
UILabel *_placeholderView;
UITextView *_detachedTextView;
@@ -166,7 +168,9 @@ - (void)setSelectedTextRange:(UITextRange *)selectedTextRange notifyDelegate:(BO
- (void)paste:(id)sender
{
_textWasPasted = YES;
- [super paste:sender];
+ if (![UIPasteboard generalPasteboard].hasImages) {
+ [super paste:sender];
+ }
[_textInputDelegateAdapter didPaste];
}

@@ -259,6 +263,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/Singleline/RCTUITextField.mm b/node_modules/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.mm
index 10009ff..273bc0a 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,8 @@
#import <React/RCTUtils.h>
#import <React/UIView+React.h>

+#import <UIKit/UIKit.h>
+
@implementation RCTUITextField {
RCTBackedTextFieldDelegateAdapter *_textInputDelegateAdapter;
NSDictionary<NSAttributedStringKey, id> *_defaultTextAttributes;
@@ -139,6 +141,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];
}

@@ -204,7 +210,9 @@ - (void)setSelectedTextRange:(UITextRange *)selectedTextRange notifyDelegate:(BO
- (void)paste:(id)sender
{
_textWasPasted = YES;
- [super paste:sender];
+ if (![UIPasteboard generalPasteboard].hasImages) {
+ [super paste:sender];
+ }
s77rt marked this conversation as resolved.
Show resolved Hide resolved
[_textInputDelegateAdapter didPaste];
}

12 changes: 12 additions & 0 deletions src/components/Composer/index.native.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import useResetComposerFocus from '@hooks/useResetComposerFocus';
import useStyleUtils from '@hooks/useStyleUtils';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import Clipboard from '@libs/Clipboard';
import updateIsFullComposerAvailable from '@libs/ComposerUtils/updateIsFullComposerAvailable';
import * as EmojiUtils from '@libs/EmojiUtils';
import type {ComposerProps} from './types';
Expand All @@ -21,6 +22,7 @@ function Composer(
{
shouldClear = false,
onClear = () => {},
onPasteFile = () => {},
isDisabled = false,
maxLines,
isComposerFullSize = false,
Expand Down Expand Up @@ -64,6 +66,15 @@ function Composer(
// eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps
}, []);

const pasteImage = useCallback(() => {
Clipboard.getImage().then((image) => {
if (!image) {
return;
}
onPasteFile(image);
});
}, [onPasteFile]);

useEffect(() => {
if (!shouldClear) {
return;
Expand Down Expand Up @@ -92,6 +103,7 @@ function Composer(
/* eslint-disable-next-line react/jsx-props-no-spreading */
{...props}
readOnly={isDisabled}
onPaste={pasteImage}
onBlur={(e) => {
if (!isFocused) {
// eslint-disable-next-line react-compiler/react-compiler
Expand Down
3 changes: 2 additions & 1 deletion src/components/Composer/types.ts
s77rt marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type {NativeSyntheticEvent, StyleProp, TextInputProps, TextInputSelectionChangeEventData, TextStyle} from 'react-native';
import {FileObject} from '@components/AttachmentModal';

Check failure on line 2 in src/components/Composer/types.ts

View workflow job for this annotation

GitHub Actions / Run ESLint

All imports in the declaration are only used as types. Use `import type`

type TextSelection = {
start: number;
Expand Down Expand Up @@ -31,7 +32,7 @@
onChangeText?: (numberOfLines: string) => void;

/** Callback method to handle pasting a file */
onPasteFile?: (file: File) => void;
onPasteFile?: (file: FileObject) => void;

/** General styles to apply to the text input */
// eslint-disable-next-line react/forbid-prop-types
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Clipboard from '@react-native-clipboard/clipboard';
import type {CanSetHtml, SetHtml, SetString} from './types';
import type {CanSetHtml, GetImage, SetHtml, SetString} from './types';

/**
* Sets a string on the Clipboard object via @react-native-clipboard/clipboard
Expand All @@ -12,8 +12,18 @@
const canSetHtml: CanSetHtml = () => false;
const setHtml: SetHtml = () => {};

const getImage: GetImage = () => {

Check failure on line 15 in src/libs/Clipboard/index.android.ts

View workflow job for this annotation

GitHub Actions / Run ESLint

Unexpected block statement surrounding arrow body; move the returned value immediately after the `=>`
return Clipboard.getImage().then((imageb64) => {
if (!imageb64) {
return undefined;
}
return {uri: imageb64, name: 'image.png', type: 'image/png'};
});
};

export default {
setString,
canSetHtml,
setHtml,
getImage,
};
36 changes: 36 additions & 0 deletions src/libs/Clipboard/index.ios.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import Clipboard from '@react-native-clipboard/clipboard';
import type {CanSetHtml, GetImage, SetHtml, SetString} from './types';

/**
* Sets a string on the Clipboard object via @react-native-clipboard/clipboard
*/
const setString: SetString = (text) => {
Clipboard.setString(text);
};

// We don't want to set HTML on native platforms so noop them.
const canSetHtml: CanSetHtml = () => false;
const setHtml: SetHtml = () => {};

const getImage: GetImage = () => {

Check failure on line 15 in src/libs/Clipboard/index.ios.ts

View workflow job for this annotation

GitHub Actions / Run ESLint

Unexpected block statement surrounding arrow body; move the returned value immediately after the `=>`
return Clipboard.hasImage()
.then((hasImage) => {
if (!hasImage) {
return undefined;
}
return Clipboard.getImagePNG();
})
.then((imageb64) => {
if (!imageb64) {
return undefined;
}
return {uri: imageb64, name: 'image.png', type: 'image/png'};
});
};

export default {
setString,
canSetHtml,
setHtml,
getImage,
};
5 changes: 4 additions & 1 deletion src/libs/Clipboard/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Clipboard from '@react-native-clipboard/clipboard';
import * as Browser from '@libs/Browser';
import CONST from '@src/CONST';
import type {CanSetHtml, SetHtml, SetString} from './types';
import type {CanSetHtml, GetImage, SetHtml, SetString} from './types';

type ComposerSelection = {
start: number;
Expand Down Expand Up @@ -136,8 +136,11 @@
Clipboard.setString(text);
};

const getImage: GetImage = () => Promise.reject('getImage not supported on web');

Check failure on line 139 in src/libs/Clipboard/index.ts

View workflow job for this annotation

GitHub Actions / Run ESLint

Expected the Promise rejection reason to be an Error

export default {
setString,
canSetHtml,
setHtml,
getImage,
};
5 changes: 4 additions & 1 deletion src/libs/Clipboard/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import {FileObject} from '@components/AttachmentModal';

Check failure on line 1 in src/libs/Clipboard/types.ts

View workflow job for this annotation

GitHub Actions / Run ESLint

All imports in the declaration are only used as types. Use `import type`

type SetString = (text: string) => void;
type SetHtml = (html: string, text: string) => void;
type CanSetHtml = (() => (...args: ClipboardItems) => Promise<void>) | (() => boolean);
type GetImage = () => Promise<FileObject | undefined>;

export type {SetString, CanSetHtml, SetHtml};
export type {SetString, CanSetHtml, SetHtml, GetImage};
Loading