* Use @mattermost/react-native-paste-input to allow pasting of images & files
* upgrade @mattermost/react-native-paste-input
(cherry picked from commit 523777a207)
Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
This commit is contained in:
parent
38bddbf9fd
commit
c94716353c
26 changed files with 1697 additions and 2155 deletions
|
|
@ -57,7 +57,6 @@ private final ReactNativeHost mReactNativeHost =
|
|||
// Packages that cannot be auto linked yet can be added manually here, for example:
|
||||
// packages.add(new MyReactNativePackage());
|
||||
packages.add(new RNNotificationsPackage(MainApplication.this));
|
||||
packages.add(new RNPasteableTextInputPackage());
|
||||
packages.add(
|
||||
new TurboReactPackage() {
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
package com.mattermost.rnbeta;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
public interface RNEditTextOnPasteListener {
|
||||
void onPaste(Uri itemUri);
|
||||
}
|
||||
|
|
@ -1,98 +0,0 @@
|
|||
package com.mattermost.rnbeta;
|
||||
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.ActionMode;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
|
||||
public class RNPasteableActionCallback implements ActionMode.Callback {
|
||||
|
||||
private final RNPasteableEditText mEditText;
|
||||
|
||||
RNPasteableActionCallback(RNPasteableEditText editText) {
|
||||
mEditText = editText;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
|
||||
Bundle config = MainApplication.instance.getManagedConfig();
|
||||
if (config != null) {
|
||||
WritableMap result = Arguments.fromBundle(config);
|
||||
String copyPasteProtection = result.getString("copyAndPasteProtection");
|
||||
assert copyPasteProtection != null;
|
||||
if (copyPasteProtection.equals("true")) {
|
||||
disableMenus(menu);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
|
||||
Uri uri = this.getUriInClipboard();
|
||||
if (item.getItemId() == android.R.id.paste && uri != null) {
|
||||
mEditText.getOnPasteListener().onPaste(uri);
|
||||
mode.finish();
|
||||
} else {
|
||||
mEditText.onTextContextMenuItem(item.getItemId());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyActionMode(ActionMode mode) {
|
||||
|
||||
}
|
||||
|
||||
private void disableMenus(Menu menu) {
|
||||
for (int i = 0; i < menu.size(); i++) {
|
||||
MenuItem item = menu.getItem(i);
|
||||
int id = item.getItemId();
|
||||
boolean shouldDisableMenu = (
|
||||
id == android.R.id.paste
|
||||
|| id == android.R.id.copy
|
||||
|| id == android.R.id.cut
|
||||
);
|
||||
item.setEnabled(!shouldDisableMenu);
|
||||
}
|
||||
}
|
||||
|
||||
private Uri getUriInClipboard() {
|
||||
ClipboardManager clipboardManager = (ClipboardManager) mEditText.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
ClipData clipData = clipboardManager.getPrimaryClip();
|
||||
if (clipData == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ClipData.Item item = clipData.getItemAt(0);
|
||||
if (item == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
CharSequence chars = item.getText();
|
||||
if (chars == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String text = chars.toString();
|
||||
if (text.length() > 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return item.getUri();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
package com.mattermost.rnbeta;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.facebook.react.views.textinput.ReactEditText;
|
||||
|
||||
public class RNPasteableEditText extends ReactEditText {
|
||||
|
||||
private RNEditTextOnPasteListener mOnPasteListener;
|
||||
|
||||
public RNPasteableEditText(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public void setOnPasteListener(RNEditTextOnPasteListener listener) {
|
||||
mOnPasteListener = listener;
|
||||
}
|
||||
|
||||
public RNEditTextOnPasteListener getOnPasteListener() {
|
||||
return mOnPasteListener;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,163 +0,0 @@
|
|||
package com.mattermost.rnbeta;
|
||||
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.res.AssetFileDescriptor;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.util.Patterns;
|
||||
import android.webkit.MimeTypeMap;
|
||||
import android.webkit.URLUtil;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||
import com.mattermost.share.RealPathUtil;
|
||||
import com.mattermost.share.ShareModule;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.File;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
public class RNPasteableEditTextOnPasteListener implements RNEditTextOnPasteListener {
|
||||
|
||||
private final RNPasteableEditText mEditText;
|
||||
|
||||
RNPasteableEditTextOnPasteListener(RNPasteableEditText editText) {
|
||||
mEditText = editText;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPaste(Uri itemUri) {
|
||||
ReactContext reactContext = (ReactContext)mEditText.getContext();
|
||||
String uri = itemUri.toString();
|
||||
|
||||
WritableArray images = null;
|
||||
WritableMap error = null;
|
||||
|
||||
String uriMimeType = reactContext.getContentResolver().getType(itemUri);
|
||||
if (uriMimeType == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Special handle for Google docs
|
||||
if (uri.equals("content://com.google.android.apps.docs.editors.kix.editors.clipboard")) {
|
||||
ClipboardManager clipboardManager = (ClipboardManager) reactContext.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
ClipData clipData = clipboardManager.getPrimaryClip();
|
||||
if (clipData == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ClipData.Item item = clipData.getItemAt(0);
|
||||
String htmlText = item.getHtmlText();
|
||||
// Find uri from html
|
||||
Matcher matcher = Patterns.WEB_URL.matcher(htmlText);
|
||||
if (matcher.find()) {
|
||||
uri = htmlText.substring(matcher.start(1), matcher.end());
|
||||
}
|
||||
}
|
||||
|
||||
if (uri.startsWith("http")) {
|
||||
Thread pastImageFromUrlThread = new Thread(new RNPasteableImageFromUrl(reactContext, mEditText, uri));
|
||||
pastImageFromUrlThread.start();
|
||||
return;
|
||||
}
|
||||
|
||||
uri = RealPathUtil.getRealPathFromURI(reactContext, itemUri);
|
||||
if (uri == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get type
|
||||
String extension = MimeTypeMap.getFileExtensionFromUrl(uri);
|
||||
if (extension == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
|
||||
if (mimeType == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get fileName
|
||||
String fileName = URLUtil.guessFileName(uri, null, mimeType);
|
||||
|
||||
if (uri.contains(ShareModule.CACHE_DIR_NAME) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
uri = moveToImagesCache(uri, fileName);
|
||||
}
|
||||
|
||||
if (uri == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get fileSize
|
||||
long fileSize;
|
||||
try {
|
||||
ContentResolver contentResolver = reactContext.getContentResolver();
|
||||
AssetFileDescriptor assetFileDescriptor = contentResolver.openAssetFileDescriptor(itemUri, "r");
|
||||
if (assetFileDescriptor == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
fileSize = assetFileDescriptor.getLength();
|
||||
|
||||
WritableMap image = Arguments.createMap();
|
||||
image.putString("type", mimeType);
|
||||
image.putDouble("fileSize", fileSize);
|
||||
image.putString("fileName", fileName);
|
||||
image.putString("uri", "file://" + uri);
|
||||
|
||||
images = Arguments.createArray();
|
||||
images.pushMap(image);
|
||||
} catch (FileNotFoundException e) {
|
||||
error = Arguments.createMap();
|
||||
error.putString("message", e.getMessage());
|
||||
}
|
||||
|
||||
WritableMap event = Arguments.createMap();
|
||||
event.putArray("data", images);
|
||||
event.putMap("error", error);
|
||||
|
||||
reactContext
|
||||
.getJSModule(RCTEventEmitter.class)
|
||||
.receiveEvent(
|
||||
mEditText.getId(),
|
||||
"onPaste",
|
||||
event
|
||||
);
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
private String moveToImagesCache(String src, String fileName) {
|
||||
ReactContext ctx = (ReactContext)mEditText.getContext();
|
||||
String cacheFolder = ctx.getCacheDir().getAbsolutePath() + "/Images/";
|
||||
String dest = cacheFolder + fileName;
|
||||
File folder = new File(cacheFolder);
|
||||
|
||||
try {
|
||||
if (!folder.exists()) {
|
||||
boolean created = folder.mkdirs();
|
||||
if (!created) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Files.move(Paths.get(src), Paths.get(dest));
|
||||
} catch (FileAlreadyExistsException fileError) {
|
||||
// Do nothing and return dest path
|
||||
} catch (Exception err) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return dest;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
package com.mattermost.rnbeta;
|
||||
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||
import com.facebook.react.views.textinput.ReactEditText;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
|
||||
public class RNPasteableImageFromUrl implements Runnable {
|
||||
|
||||
private final ReactContext mContext;
|
||||
private final String mUri;
|
||||
private final ReactEditText mTarget;
|
||||
|
||||
RNPasteableImageFromUrl(ReactContext context, ReactEditText target, String uri) {
|
||||
mContext = context;
|
||||
mUri = uri;
|
||||
mTarget = target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
WritableArray images = null;
|
||||
WritableMap error = null;
|
||||
|
||||
try {
|
||||
URL url = new URL(mUri);
|
||||
URLConnection u = url.openConnection();
|
||||
|
||||
// Get type
|
||||
String mimeType = u.getHeaderField("Content-Type");
|
||||
if (!mimeType.startsWith("image")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get fileSize
|
||||
long fileSize = Long.parseLong(u.getHeaderField("Content-Length"));
|
||||
|
||||
// Get fileName
|
||||
String contentDisposition = u.getHeaderField("Content-Disposition");
|
||||
int startIndex = contentDisposition.indexOf("filename=\"") + 10;
|
||||
int endIndex = contentDisposition.length() - 1;
|
||||
String fileName = contentDisposition.substring(startIndex, endIndex);
|
||||
|
||||
WritableMap image = Arguments.createMap();
|
||||
image.putString("type", mimeType);
|
||||
image.putDouble("fileSize", fileSize);
|
||||
image.putString("fileName", fileName);
|
||||
image.putString("uri", mUri);
|
||||
|
||||
images = Arguments.createArray();
|
||||
images.pushMap(image);
|
||||
|
||||
} catch (IOException e) {
|
||||
error = Arguments.createMap();
|
||||
error.putString("message", e.getMessage());
|
||||
}
|
||||
|
||||
WritableMap event = Arguments.createMap();
|
||||
event.putArray("data", images);
|
||||
event.putMap("error", error);
|
||||
|
||||
mContext
|
||||
.getJSModule(RCTEventEmitter.class)
|
||||
.receiveEvent(
|
||||
mTarget.getId(),
|
||||
"onPaste",
|
||||
event
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
package com.mattermost.rnbeta;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.view.inputmethod.EditorInfoCompat;
|
||||
import androidx.core.view.inputmethod.InputConnectionCompat;
|
||||
import android.text.InputType;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputConnection;
|
||||
|
||||
import com.facebook.react.common.MapBuilder;
|
||||
import com.facebook.react.uimanager.ThemedReactContext;
|
||||
import com.facebook.react.views.textinput.ReactEditText;
|
||||
import com.facebook.react.views.textinput.ReactTextInputManager;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class RNPasteableTextInputManager extends ReactTextInputManager {
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public String getName() {
|
||||
return "PasteableTextInputAndroid";
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public ReactEditText createViewInstance(ThemedReactContext context) {
|
||||
RNPasteableEditText editText = new RNPasteableEditText(context) {
|
||||
@Override
|
||||
public InputConnection onCreateInputConnection(EditorInfo editorInfo) {
|
||||
final InputConnection ic = super.onCreateInputConnection(editorInfo);
|
||||
EditorInfoCompat.setContentMimeTypes(editorInfo,
|
||||
new String [] {"image/*"});
|
||||
|
||||
|
||||
final InputConnectionCompat.OnCommitContentListener callback =
|
||||
(inputContentInfo, flags, opts) -> {
|
||||
// read and display inputContentInfo asynchronously
|
||||
if ((flags &
|
||||
InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) {
|
||||
try {
|
||||
inputContentInfo.requestPermission();
|
||||
}
|
||||
catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
this.getOnPasteListener().onPaste(inputContentInfo.getContentUri());
|
||||
return true;
|
||||
};
|
||||
return InputConnectionCompat.createWrapper(ic, editorInfo, callback);
|
||||
}
|
||||
};
|
||||
int inputType = editText.getInputType();
|
||||
editText.setInputType(inputType & (~InputType.TYPE_TEXT_FLAG_MULTI_LINE));
|
||||
editText.setReturnKeyType("done");
|
||||
editText.setCustomInsertionActionModeCallback(new RNPasteableActionCallback(editText));
|
||||
editText.setCustomSelectionActionModeCallback(new RNPasteableActionCallback(editText));
|
||||
return editText;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addEventEmitters(ThemedReactContext reactContext, ReactEditText editText) {
|
||||
super.addEventEmitters(reactContext, editText);
|
||||
|
||||
RNPasteableEditText pasteableEditText = (RNPasteableEditText)editText;
|
||||
pasteableEditText.setOnPasteListener(new RNPasteableEditTextOnPasteListener(pasteableEditText));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Map<String, Object> getExportedCustomBubblingEventTypeConstants() {
|
||||
Map<String, Object> map = super.getExportedCustomBubblingEventTypeConstants();
|
||||
assert map != null;
|
||||
map.put(
|
||||
"onPaste",
|
||||
MapBuilder.of(
|
||||
"phasedRegistrationNames",
|
||||
MapBuilder.of("bubbled", "onPaste")));
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
package com.mattermost.rnbeta;
|
||||
|
||||
import com.facebook.react.ReactPackage;
|
||||
import com.facebook.react.bridge.NativeModule;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.uimanager.ViewManager;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
public class RNPasteableTextInputPackage implements ReactPackage {
|
||||
@Nonnull
|
||||
@Override
|
||||
public List<NativeModule> createNativeModules(@Nonnull ReactApplicationContext reactContext) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public List<ViewManager> createViewManagers(@Nonnull ReactApplicationContext reactContext) {
|
||||
return Arrays.asList(
|
||||
new RNPasteableTextInputManager()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`PasteableTextInput should render pasteable text input 1`] = `
|
||||
<TextInput
|
||||
onPaste={[Function]}
|
||||
screenId="Channel"
|
||||
>
|
||||
My Text
|
||||
</TextInput>
|
||||
`;
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import {NativeEventEmitter, NativeModules, Platform, TextInput} from 'react-native';
|
||||
|
||||
import {PASTE_FILES} from '@constants/post_draft';
|
||||
import EventEmitter from '@mm-redux/utils/event_emitter';
|
||||
|
||||
const {OnPasteEventManager} = NativeModules;
|
||||
const OnPasteEventEmitter = new NativeEventEmitter(OnPasteEventManager);
|
||||
|
||||
export class PasteableTextInput extends React.PureComponent {
|
||||
static propTypes = {
|
||||
...TextInput.PropTypes,
|
||||
forwardRef: PropTypes.any,
|
||||
screenId: PropTypes.string.isRequired,
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.subscription = OnPasteEventEmitter.addListener('onPaste', this.onPaste);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.subscription) {
|
||||
this.subscription.remove();
|
||||
}
|
||||
}
|
||||
|
||||
getLastSubscriptionKey = () => {
|
||||
const subscriptions = OnPasteEventEmitter._subscriber._subscriptionsForType.onPaste?.filter((sub) => sub); // eslint-disable-line no-underscore-dangle
|
||||
return subscriptions?.length && subscriptions[subscriptions.length - 1].key;
|
||||
}
|
||||
|
||||
onPaste = (event) => {
|
||||
const lastSubscriptionKey = this.getLastSubscriptionKey();
|
||||
if (this.subscription.key !== lastSubscriptionKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
let data = null;
|
||||
let error = null;
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
const {nativeEvent} = event;
|
||||
data = nativeEvent.data;
|
||||
error = nativeEvent.error;
|
||||
} else {
|
||||
data = event;
|
||||
}
|
||||
|
||||
EventEmitter.emit(PASTE_FILES, error, data, this.props.screenId);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {testID, forwardRef, ...props} = this.props;
|
||||
|
||||
return (
|
||||
<TextInput
|
||||
testID={testID}
|
||||
{...props}
|
||||
onPaste={this.onPaste}
|
||||
ref={forwardRef}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const WrappedPasteableTextInput = (props, ref) => (
|
||||
<PasteableTextInput
|
||||
{...props}
|
||||
forwardRef={ref}
|
||||
/>
|
||||
);
|
||||
|
||||
export default React.forwardRef(WrappedPasteableTextInput);
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import {PasteableTextInput} from './index';
|
||||
|
||||
describe('PasteableTextInput', () => {
|
||||
test('should render pasteable text input', () => {
|
||||
const onPaste = jest.fn();
|
||||
const text = 'My Text';
|
||||
const component = shallow(
|
||||
<PasteableTextInput
|
||||
onPaste={onPaste}
|
||||
screenId='Channel'
|
||||
>{text}</PasteableTextInput>,
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should remove onPaste listener when unmount', () => {
|
||||
const mockRemove = jest.fn();
|
||||
const text = 'My Text';
|
||||
const component = shallow(
|
||||
<PasteableTextInput screenId='Channel'>{text}</PasteableTextInput>,
|
||||
);
|
||||
|
||||
component.instance().subscription.remove = mockRemove;
|
||||
component.instance().componentWillUnmount();
|
||||
expect(mockRemove).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
@ -284,31 +284,49 @@ exports[`PostDraft Should render the DraftInput 1`] = `
|
|||
}
|
||||
>
|
||||
<View>
|
||||
<TextInput
|
||||
<PasteInput
|
||||
accessible={true}
|
||||
autoCapitalize="sentences"
|
||||
autoCompleteType="off"
|
||||
blurOnSubmit={false}
|
||||
disableCopyPaste={false}
|
||||
disableFullscreenUI={true}
|
||||
focusable={true}
|
||||
keyboardAppearance="light"
|
||||
keyboardType="default"
|
||||
mostRecentEventCount={0}
|
||||
multiline={true}
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onChangeText={[Function]}
|
||||
onClick={[Function]}
|
||||
onEndEditing={[Function]}
|
||||
onFocus={[Function]}
|
||||
onPaste={[Function]}
|
||||
onResponderGrant={[Function]}
|
||||
onResponderMove={[Function]}
|
||||
onResponderRelease={[Function]}
|
||||
onResponderTerminate={[Function]}
|
||||
onResponderTerminationRequest={[Function]}
|
||||
onScroll={[Function]}
|
||||
onSelectionChange={[Function]}
|
||||
onStartShouldSetResponder={[Function]}
|
||||
placeholder="Write to "
|
||||
placeholderTextColor="rgba(63,67,80,0.5)"
|
||||
screenId="NavigationScreen1"
|
||||
style={
|
||||
Object {
|
||||
"color": "#3f4350",
|
||||
"fontSize": 15,
|
||||
"lineHeight": 20,
|
||||
"maxHeight": 150,
|
||||
"minHeight": 30,
|
||||
"paddingBottom": 6,
|
||||
"paddingHorizontal": 12,
|
||||
"paddingTop": 6,
|
||||
}
|
||||
Array [
|
||||
Object {
|
||||
"color": "#3f4350",
|
||||
"fontSize": 15,
|
||||
"lineHeight": 20,
|
||||
"maxHeight": 150,
|
||||
"minHeight": 30,
|
||||
"paddingBottom": 6,
|
||||
"paddingHorizontal": 12,
|
||||
"paddingTop": 6,
|
||||
},
|
||||
]
|
||||
}
|
||||
testID="post_draft.post.input"
|
||||
textContentType="none"
|
||||
|
|
|
|||
|
|
@ -1,15 +1,17 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`PostInput should match, full snapshot 1`] = `
|
||||
<ForwardRef(WrappedPasteableTextInput)
|
||||
<ForwardRef
|
||||
autoCompleteType="off"
|
||||
blurOnSubmit={false}
|
||||
disableCopyPaste={false}
|
||||
disableFullscreenUI={true}
|
||||
keyboardAppearance="light"
|
||||
keyboardType="default"
|
||||
multiline={true}
|
||||
onChangeText={[Function]}
|
||||
onEndEditing={[Function]}
|
||||
onPaste={[Function]}
|
||||
onSelectionChange={[Function]}
|
||||
placeholder="Write to Test Channel"
|
||||
placeholderTextColor="rgba(63,67,80,0.5)"
|
||||
|
|
|
|||
|
|
@ -6,10 +6,11 @@ import React, {PureComponent} from 'react';
|
|||
import {intlShape} from 'react-intl';
|
||||
import {Alert, AppState, findNodeHandle, Keyboard, NativeModules, Platform} from 'react-native';
|
||||
|
||||
import PasteableTextInput from '@components/pasteable_text_input';
|
||||
import {NavigationTypes} from '@constants';
|
||||
import DEVICE from '@constants/device';
|
||||
import {INSERT_TO_COMMENT, INSERT_TO_DRAFT} from '@constants/post_draft';
|
||||
import {INSERT_TO_COMMENT, INSERT_TO_DRAFT, PASTE_FILES} from '@constants/post_draft';
|
||||
import mattermostManaged from '@mattermost-managed';
|
||||
import PasteableTextInput from '@mattermost/react-native-paste-input';
|
||||
import {debounce} from '@mm-redux/actions/helpers';
|
||||
import EventEmitter from '@mm-redux/utils/event_emitter';
|
||||
import {t} from '@utils/i18n';
|
||||
|
|
@ -55,6 +56,7 @@ export default class PostInput extends PureComponent {
|
|||
this.state = {
|
||||
keyboardType: 'default',
|
||||
longMessageAlertShown: false,
|
||||
disableCopyAndPaste: mattermostManaged.getCachedConfig()?.copyAndPasteProtection === 'true',
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -63,6 +65,7 @@ export default class PostInput extends PureComponent {
|
|||
EventEmitter.on(event, this.handleInsertTextToDraft);
|
||||
EventEmitter.on(NavigationTypes.BLUR_POST_DRAFT, this.blur);
|
||||
this.appStateListener = AppState.addEventListener('change', this.handleAppStateChange);
|
||||
this.managedListener = mattermostManaged.addEventListener('managedConfigDidChange', this.onManagedConfigurationChange);
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
this.keyboardListener = Keyboard.addListener('keyboardDidHide', this.handleAndroidKeyboard);
|
||||
|
|
@ -73,6 +76,7 @@ export default class PostInput extends PureComponent {
|
|||
const event = this.props.rootId ? INSERT_TO_COMMENT : INSERT_TO_DRAFT;
|
||||
EventEmitter.off(NavigationTypes.BLUR_POST_DRAFT, this.blur);
|
||||
EventEmitter.off(event, this.handleInsertTextToDraft);
|
||||
mattermostManaged.removeEventListener(this.managedListener);
|
||||
this.appStateListener.remove();
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
|
|
@ -249,11 +253,20 @@ export default class PostInput extends PureComponent {
|
|||
}
|
||||
|
||||
this.value = completed;
|
||||
|
||||
this.input.current.setNativeProps({
|
||||
text: completed,
|
||||
});
|
||||
};
|
||||
|
||||
onManagedConfigurationChange = (config) => {
|
||||
this.setState({disableCopyAndPaste: config.copyAndPasteProtection === 'true'});
|
||||
}
|
||||
|
||||
onPaste = (error, files) => {
|
||||
EventEmitter.emit(PASTE_FILES, error, files, this.props.screenId);
|
||||
}
|
||||
|
||||
resetTextInput = () => {
|
||||
if (this.input.current) {
|
||||
this.input.current.setNativeProps({
|
||||
|
|
@ -287,6 +300,7 @@ export default class PostInput extends PureComponent {
|
|||
<PasteableTextInput
|
||||
testID={testID}
|
||||
ref={this.input}
|
||||
disableCopyPaste={this.state.disableCopyAndPaste}
|
||||
style={{...style.input, maxHeight}}
|
||||
onChangeText={this.handleTextChange}
|
||||
onSelectionChange={this.handlePostDraftSelectionChanged}
|
||||
|
|
@ -297,6 +311,7 @@ export default class PostInput extends PureComponent {
|
|||
underlineColorAndroid='transparent'
|
||||
keyboardType={this.state.keyboardType}
|
||||
onEndEditing={this.handleEndEditing}
|
||||
onPaste={this.onPaste}
|
||||
disableFullscreenUI={true}
|
||||
textContentType='none'
|
||||
autoCompleteType='off'
|
||||
|
|
|
|||
|
|
@ -17,9 +17,7 @@
|
|||
3D7B4E6EE6B948AAA6A1E4E6 /* Roboto-BoldItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = C46342B0E5FD4D878AF3A129 /* Roboto-BoldItalic.ttf */; };
|
||||
460A48EA5C6C4D8B8D9A2C75 /* Roboto-Italic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 031EF04FB2D14EEFAACBAA1A /* Roboto-Italic.ttf */; };
|
||||
4953BF602368AE8600593328 /* SwimeProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4953BF5F2368AE8600593328 /* SwimeProxy.swift */; };
|
||||
499F7A5F2354F49F00E7AF6E /* UIPasteboard+GetImageInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 499F7A5E2354F49F00E7AF6E /* UIPasteboard+GetImageInfo.m */; };
|
||||
499F7A622354F4CC00E7AF6E /* NSData+MimeType.m in Sources */ = {isa = PBXBuildFile; fileRef = 499F7A602354F4CC00E7AF6E /* NSData+MimeType.m */; };
|
||||
499F7A652354F51900E7AF6E /* OnPasteEventManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 499F7A642354F51900E7AF6E /* OnPasteEventManager.m */; };
|
||||
49B4C050230C981C006E919E /* libUploadAttachments.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FABE04522137F2A00D0F595 /* libUploadAttachments.a */; };
|
||||
531BEBC62513E93C00BC05B1 /* compass-icons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 531BEBC52513E93C00BC05B1 /* compass-icons.ttf */; };
|
||||
531BEBC72513E93C00BC05B1 /* compass-icons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 531BEBC52513E93C00BC05B1 /* compass-icons.ttf */; };
|
||||
|
|
@ -44,7 +42,6 @@
|
|||
7F581D35221ED5C60099E66B /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F581D34221ED5C60099E66B /* NotificationService.swift */; };
|
||||
7F581D39221ED5C60099E66B /* NotificationService.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 7F581D32221ED5C60099E66B /* NotificationService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
7F581F78221EEA7C0099E66B /* libUploadAttachments.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FABE04522137F2A00D0F595 /* libUploadAttachments.a */; };
|
||||
7F5BA34722B99B7B005B05D3 /* Mattermost+RCTUITextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F5BA34622B99B7B005B05D3 /* Mattermost+RCTUITextView.m */; };
|
||||
7F72F2EE2211220500F98FFF /* GenericPreview.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7F72F2ED2211220500F98FFF /* GenericPreview.xib */; };
|
||||
7F72F2F0221123E200F98FFF /* GenericPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F72F2EF221123E200F98FFF /* GenericPreview.swift */; };
|
||||
7F72F2F322112EC700F98FFF /* generic.png in Resources */ = {isa = PBXBuildFile; fileRef = 7F72F2F222112EC700F98FFF /* generic.png */; };
|
||||
|
|
@ -162,12 +159,8 @@
|
|||
4953BF5F2368AE8600593328 /* SwimeProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SwimeProxy.swift; path = Mattermost/SwimeProxy.swift; sourceTree = "<group>"; };
|
||||
495BC95F23565ABF00C40C83 /* libXCDYouTubeKit.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libXCDYouTubeKit.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
495BC96123565ADD00C40C83 /* libYoutubePlayer-in-WKWebView.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = "libYoutubePlayer-in-WKWebView.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
499F7A5D2354F49F00E7AF6E /* UIPasteboard+GetImageInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIPasteboard+GetImageInfo.h"; path = "Mattermost/UIPasteboard+GetImageInfo.h"; sourceTree = "<group>"; };
|
||||
499F7A5E2354F49F00E7AF6E /* UIPasteboard+GetImageInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIPasteboard+GetImageInfo.m"; path = "Mattermost/UIPasteboard+GetImageInfo.m"; sourceTree = "<group>"; };
|
||||
499F7A602354F4CC00E7AF6E /* NSData+MimeType.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSData+MimeType.m"; path = "Mattermost/NSData+MimeType.m"; sourceTree = "<group>"; };
|
||||
499F7A612354F4CC00E7AF6E /* NSData+MimeType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSData+MimeType.h"; path = "Mattermost/NSData+MimeType.h"; sourceTree = "<group>"; };
|
||||
499F7A632354F51900E7AF6E /* OnPasteEventManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OnPasteEventManager.h; path = Mattermost/OnPasteEventManager.h; sourceTree = "<group>"; };
|
||||
499F7A642354F51900E7AF6E /* OnPasteEventManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OnPasteEventManager.m; path = Mattermost/OnPasteEventManager.m; sourceTree = "<group>"; };
|
||||
499F7AF0235511FC00E7AF6E /* Mattermost-Regular.otf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "Mattermost-Regular.otf"; path = "../assets/fonts/Mattermost-Regular.otf"; sourceTree = "<group>"; };
|
||||
499F7B3F235513F600E7AF6E /* libXCDYouTubeKit.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libXCDYouTubeKit.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
499F7B412355141200E7AF6E /* libYoutubePlayer-in-WKWebView.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = "libYoutubePlayer-in-WKWebView.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
|
|
@ -204,8 +197,6 @@
|
|||
7F581D34221ED5C60099E66B /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = "<group>"; };
|
||||
7F581D36221ED5C60099E66B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
7F581F77221EEA5A0099E66B /* NotificationService.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NotificationService.entitlements; sourceTree = "<group>"; };
|
||||
7F5BA34522B99B7B005B05D3 /* Mattermost+RCTUITextView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "Mattermost+RCTUITextView.h"; path = "Mattermost/Mattermost+RCTUITextView.h"; sourceTree = "<group>"; };
|
||||
7F5BA34622B99B7B005B05D3 /* Mattermost+RCTUITextView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = "Mattermost+RCTUITextView.m"; path = "Mattermost/Mattermost+RCTUITextView.m"; sourceTree = "<group>"; };
|
||||
7F63D2C21E6DD98A001FAE12 /* Mattermost.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = Mattermost.entitlements; path = Mattermost/Mattermost.entitlements; sourceTree = "<group>"; };
|
||||
7F72F2ED2211220500F98FFF /* GenericPreview.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = GenericPreview.xib; sourceTree = "<group>"; };
|
||||
7F72F2EF221123E200F98FFF /* GenericPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericPreview.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -330,12 +321,8 @@
|
|||
children = (
|
||||
536CC6C223E79287002C478C /* RNNotificationEventHandler+HandleReplyAction.h */,
|
||||
536CC6C123E79287002C478C /* RNNotificationEventHandler+HandleReplyAction.m */,
|
||||
499F7A632354F51900E7AF6E /* OnPasteEventManager.h */,
|
||||
499F7A642354F51900E7AF6E /* OnPasteEventManager.m */,
|
||||
499F7A612354F4CC00E7AF6E /* NSData+MimeType.h */,
|
||||
499F7A602354F4CC00E7AF6E /* NSData+MimeType.m */,
|
||||
499F7A5D2354F49F00E7AF6E /* UIPasteboard+GetImageInfo.h */,
|
||||
499F7A5E2354F49F00E7AF6E /* UIPasteboard+GetImageInfo.m */,
|
||||
13B07FAF1A68108700A75B9A /* AppDelegate.h */,
|
||||
13B07FB01A68108700A75B9A /* AppDelegate.m */,
|
||||
7FEB10961F6101710039A015 /* BlurAppScreen.h */,
|
||||
|
|
@ -354,8 +341,6 @@
|
|||
7FEB109B1F61019C0039A015 /* UIImage+ImageEffects.h */,
|
||||
7FEB109C1F61019C0039A015 /* UIImage+ImageEffects.m */,
|
||||
7F151D40221B069200FAD8F3 /* 0155-keys.png */,
|
||||
7F5BA34522B99B7B005B05D3 /* Mattermost+RCTUITextView.h */,
|
||||
7F5BA34622B99B7B005B05D3 /* Mattermost+RCTUITextView.m */,
|
||||
7F0F4B0924BA173900E14C60 /* LaunchScreen.storyboard */,
|
||||
);
|
||||
name = Mattermost;
|
||||
|
|
@ -824,11 +809,8 @@
|
|||
7FEB109E1F61019C0039A015 /* UIImage+ImageEffects.m in Sources */,
|
||||
13B07FC11A68108700A75B9A /* main.m in Sources */,
|
||||
7FEB10981F6101710039A015 /* BlurAppScreen.m in Sources */,
|
||||
499F7A5F2354F49F00E7AF6E /* UIPasteboard+GetImageInfo.m in Sources */,
|
||||
7FEB109D1F61019C0039A015 /* MattermostManaged.m in Sources */,
|
||||
499F7A652354F51900E7AF6E /* OnPasteEventManager.m in Sources */,
|
||||
7F240ACD220D460300637665 /* MattermostBucketModule.m in Sources */,
|
||||
7F5BA34722B99B7B005B05D3 /* Mattermost+RCTUITextView.m in Sources */,
|
||||
536CC6C323E79287002C478C /* RNNotificationEventHandler+HandleReplyAction.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
|
|
|||
|
|
@ -1,17 +0,0 @@
|
|||
//
|
||||
// Mattermost+RCTUITextView.h
|
||||
// Mattermost
|
||||
//
|
||||
// Created by Elias Nahum on 6/18/19.
|
||||
// Copyright © 2019 Facebook. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface Mattermost_RCTUITextView : NSObject
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
//
|
||||
// Mattermost+RCTUITextView.m
|
||||
// Mattermost
|
||||
//
|
||||
// Created by Elias Nahum on 6/18/19.
|
||||
// Copyright © 2019 Facebook. All rights reserved.
|
||||
//
|
||||
|
||||
#import "Mattermost+RCTUITextView.h"
|
||||
#import <React/RCTUITextView.h>
|
||||
#import <React/RCTUtils.h>
|
||||
#import <React/RCTMultilineTextInputView.h>
|
||||
#import "OnPasteEventManager.h"
|
||||
#import "UIPasteboard+GetImageInfo.h"
|
||||
|
||||
@implementation Mattermost_RCTUITextView
|
||||
@end
|
||||
|
||||
@implementation RCTUITextView (DisableCopyPaste)
|
||||
|
||||
#pragma GCC diagnostic ignored "-Wundeclared-selector"
|
||||
|
||||
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
|
||||
{
|
||||
NSDictionary *response = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"com.apple.configuration.managed"];
|
||||
if(response) {
|
||||
NSString *copyPasteProtection = response[@"copyAndPasteProtection"];
|
||||
BOOL prevent = action == @selector(paste:) ||
|
||||
action == @selector(copy:) ||
|
||||
action == @selector(cut:) ||
|
||||
action == @selector(_share:);
|
||||
|
||||
if ([copyPasteProtection isEqual: @"true"] && prevent) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
if (action == @selector(paste:) && [UIPasteboard generalPasteboard].numberOfItems > 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return [super canPerformAction:action withSender:sender];
|
||||
}
|
||||
|
||||
-(void)paste:(id)sender {
|
||||
[super paste:sender];
|
||||
|
||||
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
|
||||
if (pasteboard.hasURLs || pasteboard.hasStrings || pasteboard.hasColors) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSArray<NSDictionary *> *files = [pasteboard getCopiedFiles];
|
||||
if (files != nil && files.count > 0) {
|
||||
[OnPasteEventManager pasteFiles:files];
|
||||
}
|
||||
|
||||
// Dismiss contextual menu
|
||||
[self resignFirstResponder];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -186,4 +186,12 @@ RCT_EXPORT_METHOD(supportsFaceId:(RCTPromiseResolveBlock)resolve
|
|||
});
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(addListener:(NSString *)eventName) {
|
||||
// Keep: Required for RN built in Event Emitter Calls.
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(removeListeners:(double)count) {
|
||||
// Keep: Required for RN built in Event Emitter Calls.
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
//
|
||||
// PasteEventManager.h
|
||||
// Mattermost
|
||||
//
|
||||
// Created by Tek Min Ewe on 05/08/2019.
|
||||
// Copyright © 2019 Facebook. All rights reserved.
|
||||
//
|
||||
|
||||
#import <React/RCTBridgeModule.h>
|
||||
#import <React/RCTEventEmitter.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface OnPasteEventManager : RCTEventEmitter<RCTBridgeModule>
|
||||
|
||||
+(void)pasteFiles:(NSArray<NSDictionary *> *)data;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
//
|
||||
// PasteEventManager.m
|
||||
// Mattermost
|
||||
//
|
||||
// Created by Tek Min Ewe on 05/08/2019.
|
||||
// Copyright © 2019 Facebook. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OnPasteEventManager.h"
|
||||
|
||||
@implementation OnPasteEventManager {
|
||||
bool hasListeners;
|
||||
}
|
||||
|
||||
RCT_EXPORT_MODULE();
|
||||
|
||||
-(void)startObserving {
|
||||
hasListeners = YES;
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onPaste:) name:@"onPaste" object:nil];
|
||||
}
|
||||
|
||||
-(void)stopObserving {
|
||||
hasListeners = NO;
|
||||
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
-(NSArray<NSString *>*)supportedEvents {
|
||||
return @[@"onPaste"];
|
||||
}
|
||||
|
||||
-(void)onPaste:(NSNotification *)data {
|
||||
if (!hasListeners) {
|
||||
return;
|
||||
}
|
||||
|
||||
[self sendEventWithName:@"onPaste" body:data.userInfo[@"data"]];
|
||||
}
|
||||
|
||||
+(void)pasteFiles:(NSArray<NSDictionary *> *)data {
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"onPaste" object:data userInfo:@{
|
||||
@"data": data
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
//
|
||||
// UIPasteboard+GetImageInfo.h
|
||||
// Mattermost
|
||||
//
|
||||
// Created by Tek Min Ewe on 05/08/2019.
|
||||
// Copyright © 2019 Facebook. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface UIPasteboard (GetImageInfo)
|
||||
|
||||
-(NSArray<NSDictionary *> *)getCopiedFiles;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
|
@ -1,104 +0,0 @@
|
|||
//
|
||||
// UIPasteboard+GetImageInfo.m
|
||||
// Mattermost
|
||||
//
|
||||
// Created by Tek Min Ewe on 05/08/2019.
|
||||
// Copyright © 2019 Facebook. All rights reserved.
|
||||
//
|
||||
|
||||
#import "UIPasteboard+GetImageInfo.h"
|
||||
#import "NSData+MimeType.h"
|
||||
#import "Mattermost-Swift.h"
|
||||
#import "UIImage+vImageScaling.h"
|
||||
|
||||
@implementation UIPasteboard (GetImageInfo)
|
||||
|
||||
-(NSArray<NSDictionary *> *)getCopiedFiles {
|
||||
NSMutableArray<NSDictionary *> *fileInfos = [[NSMutableArray alloc] init];
|
||||
NSArray<NSDictionary<NSString *,id> *> *items = self.items;
|
||||
for (int i = 0; i < items.count; i++) {
|
||||
NSDictionary *item = items[i];
|
||||
BOOL added = NO;
|
||||
for (int j = 0; j < item.allKeys.count; j++) {
|
||||
if (added) {
|
||||
continue;
|
||||
}
|
||||
|
||||
NSString *type = item.allKeys[j];
|
||||
|
||||
|
||||
@try {
|
||||
NSString *uri = self.string;
|
||||
NSData *fileData = item[type];
|
||||
|
||||
if ([type isEqual:@"public.jpeg"] || [type isEqual:@"public.heic"] || [type isEqual:@"public.png"]) {
|
||||
fileData = [self getDataForImageItem:item[type] type:type];
|
||||
} else if ([type isEqual:@"com.compuserve.gif"]) {
|
||||
fileData = [self dataForPasteboardType:type];
|
||||
}
|
||||
|
||||
SwimeProxy *swimeProxy = [SwimeProxy shared];
|
||||
MimeTypeProxy *mimeProxy = [swimeProxy getMimeAndExtensionWithData:fileData uti:type];
|
||||
NSString *extension;
|
||||
NSString *mimeType;
|
||||
if (mimeProxy != nil) {
|
||||
extension = mimeProxy.ext;
|
||||
mimeType = mimeProxy.mime;
|
||||
} else {
|
||||
extension = [fileData extension];
|
||||
mimeType = [fileData mimeType];
|
||||
}
|
||||
|
||||
if ([extension length] == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
NSString *tempFilename = [NSString stringWithFormat:@"%@.%@", [[NSProcessInfo processInfo] globallyUniqueString], extension];
|
||||
NSURL *tempFileURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:tempFilename]];
|
||||
BOOL success = [fileData writeToURL:tempFileURL atomically:YES];
|
||||
if (success) {
|
||||
added = YES;
|
||||
uri = tempFileURL.absoluteString;
|
||||
[fileInfos addObject:@{
|
||||
@"fileName": tempFilename,
|
||||
@"fileSize": @([fileData length]),
|
||||
@"type": mimeType,
|
||||
@"uri": uri,
|
||||
}];
|
||||
}
|
||||
} @catch (NSException *exception) {
|
||||
[fileInfos addObject:@{
|
||||
@"type": type,
|
||||
@"error": exception.reason,
|
||||
}];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fileInfos;
|
||||
}
|
||||
|
||||
-(NSData *) getDataForImageItem:(NSData *)imageData type:(NSString *)type {
|
||||
UIImage *image;
|
||||
if ([type isEqual:@"public.heic"]) {
|
||||
CFDataRef cfdata = CFDataCreate(NULL, [imageData bytes], [imageData length]);
|
||||
CGImageSourceRef source = CGImageSourceCreateWithData(cfdata, nil);
|
||||
CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, 0, nil);
|
||||
image = [[UIImage alloc] initWithCGImage:imageRef];
|
||||
} else {
|
||||
image = (UIImage *)imageData;
|
||||
}
|
||||
size_t width = CGImageGetWidth(image.CGImage);
|
||||
size_t height = CGImageGetHeight(image.CGImage);
|
||||
if (width > 6048 || height > 4032) {
|
||||
image = [image vImageScaledImageWithSize:CGSizeMake(2048, 2048) contentMode:UIViewContentModeScaleAspectFit];
|
||||
}
|
||||
|
||||
if ([type isEqual:@"public.png"]) {
|
||||
return UIImagePNGRepresentation(image);
|
||||
}
|
||||
|
||||
return UIImageJPEGRepresentation(image, 1.0);
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -258,6 +258,9 @@ PODS:
|
|||
- React-Core
|
||||
- react-native-passcode-status (1.1.2):
|
||||
- React
|
||||
- react-native-paste-input (0.2.0):
|
||||
- React-Core
|
||||
- Swime (= 3.0.6)
|
||||
- react-native-safe-area (0.5.1):
|
||||
- React
|
||||
- react-native-safe-area-context (3.3.2):
|
||||
|
|
@ -476,6 +479,7 @@ DEPENDENCIES:
|
|||
- "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)"
|
||||
- react-native-notifications (from `../node_modules/react-native-notifications`)
|
||||
- react-native-passcode-status (from `../node_modules/react-native-passcode-status`)
|
||||
- "react-native-paste-input (from `../node_modules/@mattermost/react-native-paste-input`)"
|
||||
- react-native-safe-area (from `../node_modules/react-native-safe-area`)
|
||||
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
|
||||
- react-native-startup-time (from `../node_modules/react-native-startup-time`)
|
||||
|
|
@ -604,6 +608,8 @@ EXTERNAL SOURCES:
|
|||
:path: "../node_modules/react-native-notifications"
|
||||
react-native-passcode-status:
|
||||
:path: "../node_modules/react-native-passcode-status"
|
||||
react-native-paste-input:
|
||||
:path: "../node_modules/@mattermost/react-native-paste-input"
|
||||
react-native-safe-area:
|
||||
:path: "../node_modules/react-native-safe-area"
|
||||
react-native-safe-area-context:
|
||||
|
|
@ -737,6 +743,7 @@ SPEC CHECKSUMS:
|
|||
react-native-netinfo: 92e6e4476eb8bf6fc2d7c0a6ca0a1406f663d73a
|
||||
react-native-notifications: 97c14bf84c64bd6a6eb7bdcdb916036d93d33428
|
||||
react-native-passcode-status: e78f76b3c8db613e6ced6bd40b54aa4f53374173
|
||||
react-native-paste-input: 5da631cf8210a9e61d577f987f7fb8b0ef966121
|
||||
react-native-safe-area: e8230b0017d76c00de6b01e2412dcf86b127c6a3
|
||||
react-native-safe-area-context: 584dc04881deb49474363f3be89e4ca0e854c057
|
||||
react-native-startup-time: 1a068b744ce5097a85ebe0fbff691b05961c324d
|
||||
|
|
|
|||
2873
package-lock.json
generated
2873
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -8,6 +8,7 @@
|
|||
"private": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.15.4",
|
||||
"@mattermost/react-native-paste-input": "0.2.0",
|
||||
"@react-native-community/async-storage": "1.12.1",
|
||||
"@react-native-community/cameraroll": "4.0.4",
|
||||
"@react-native-community/clipboard": "1.5.1",
|
||||
|
|
|
|||
|
|
@ -19,19 +19,6 @@ index 8eca425..f0df04f 100644
|
|||
<NativeDirectionalScrollView
|
||||
{...props}
|
||||
style={StyleSheet.compose(baseStyle, inner)}
|
||||
diff --git a/node_modules/react-native/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js b/node_modules/react-native/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js
|
||||
index b8d559a..cb84238 100644
|
||||
--- a/node_modules/react-native/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js
|
||||
+++ b/node_modules/react-native/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js
|
||||
@@ -547,7 +547,7 @@ export const Commands: NativeCommands = codegenNativeCommands<NativeCommands>({
|
||||
});
|
||||
|
||||
let AndroidTextInputNativeComponent = NativeComponentRegistry.get<NativeProps>(
|
||||
- 'AndroidTextInput',
|
||||
+ 'PasteableTextInputAndroid',
|
||||
() => AndroidTextInputViewConfig,
|
||||
);
|
||||
|
||||
diff --git a/node_modules/react-native/Libraries/Lists/VirtualizedList.js b/node_modules/react-native/Libraries/Lists/VirtualizedList.js
|
||||
index a7c1567..1531a45 100644
|
||||
--- a/node_modules/react-native/Libraries/Lists/VirtualizedList.js
|
||||
|
|
|
|||
Loading…
Reference in a new issue