forked from Ivasoft/mattermost-mobile
Compare commits
16 Commits
release-1.
...
release-1.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d451cf6bde | ||
|
|
38fa757942 | ||
|
|
ad249e9fe4 | ||
|
|
44708f5bba | ||
|
|
ca308dee84 | ||
|
|
d640a5643d | ||
|
|
da575c7cb1 | ||
|
|
962a1759a5 | ||
|
|
50f616f5d7 | ||
|
|
1a20677eba | ||
|
|
5b5b7bc620 | ||
|
|
456ab53ecf | ||
|
|
9c87835802 | ||
|
|
11cc2e57f5 | ||
|
|
ac5914ef15 | ||
|
|
9c15ae1c5b |
@@ -132,8 +132,8 @@ android {
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
missingDimensionStrategy "RNNotifications.reactNativeVersion", "reactNative60"
|
||||
versionCode 360
|
||||
versionName "1.44.1"
|
||||
versionCode 364
|
||||
versionName "1.45.1"
|
||||
multiDexEnabled = true
|
||||
testBuildType System.getProperty('testBuildType', 'debug')
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package com.mattermost.rnbeta;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
@@ -12,6 +14,7 @@ import androidx.core.app.NotificationManagerCompat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -23,12 +26,15 @@ import static com.wix.reactnativenotifications.Defs.NOTIFICATION_RECEIVED_EVENT_
|
||||
|
||||
import com.mattermost.react_native_interface.ResolvePromise;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class CustomPushNotification extends PushNotification {
|
||||
private static final String PUSH_NOTIFICATIONS = "PUSH_NOTIFICATIONS";
|
||||
private static final String PUSH_TYPE_MESSAGE = "message";
|
||||
private static final String PUSH_TYPE_CLEAR = "clear";
|
||||
private static final String PUSH_TYPE_SESSION = "session";
|
||||
|
||||
private final static Map<String, List<Integer>> notificationsInChannel = new HashMap<>();
|
||||
private static final String NOTIFICATIONS_IN_CHANNEL = "notificationsInChannel";
|
||||
|
||||
public CustomPushNotification(Context context, Bundle bundle, AppLifecycleFacade appLifecycleFacade, AppLaunchHelper appLaunchHelper, JsIOHelper jsIoHelper) {
|
||||
super(context, bundle, appLifecycleFacade, appLaunchHelper, jsIoHelper);
|
||||
@@ -37,12 +43,14 @@ public class CustomPushNotification extends PushNotification {
|
||||
|
||||
public static void cancelNotification(Context context, String channelId, Integer notificationId) {
|
||||
if (!android.text.TextUtils.isEmpty(channelId)) {
|
||||
Map<String, List<Integer>> notificationsInChannel = loadNotificationsMap(context);
|
||||
List<Integer> notifications = notificationsInChannel.get(channelId);
|
||||
if (notifications == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
notifications.remove(notificationId);
|
||||
saveNotificationsMap(context, notificationsInChannel);
|
||||
final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
|
||||
notificationManager.cancel(notificationId);
|
||||
}
|
||||
@@ -50,25 +58,27 @@ public class CustomPushNotification extends PushNotification {
|
||||
|
||||
public static void clearChannelNotifications(Context context, String channelId) {
|
||||
if (!android.text.TextUtils.isEmpty(channelId)) {
|
||||
Map<String, List<Integer>> notificationsInChannel = loadNotificationsMap(context);
|
||||
List<Integer> notifications = notificationsInChannel.get(channelId);
|
||||
if (notifications == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
notificationsInChannel.remove(channelId);
|
||||
saveNotificationsMap(context, notificationsInChannel);
|
||||
|
||||
if (context != null) {
|
||||
for (final Integer notificationId : notifications) {
|
||||
final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
|
||||
notificationManager.cancel(notificationId);
|
||||
}
|
||||
for (final Integer notificationId : notifications) {
|
||||
final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
|
||||
notificationManager.cancel(notificationId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void clearAllNotifications(Context context) {
|
||||
notificationsInChannel.clear();
|
||||
if (context != null) {
|
||||
Map<String, List<Integer>> notificationsInChannel = loadNotificationsMap(context);
|
||||
notificationsInChannel.clear();
|
||||
saveNotificationsMap(context, notificationsInChannel);
|
||||
final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
|
||||
notificationManager.cancelAll();
|
||||
}
|
||||
@@ -106,22 +116,38 @@ public class CustomPushNotification extends PushNotification {
|
||||
});
|
||||
}
|
||||
|
||||
if (channelId != null) {
|
||||
synchronized (notificationsInChannel) {
|
||||
List<Integer> list = notificationsInChannel.get(channelId);
|
||||
if (list == null) {
|
||||
list = Collections.synchronizedList(new ArrayList(0));
|
||||
}
|
||||
|
||||
list.add(0, notificationId);
|
||||
notificationsInChannel.put(channelId, list);
|
||||
}
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case PUSH_TYPE_MESSAGE:
|
||||
case PUSH_TYPE_SESSION:
|
||||
super.postNotification(notificationId);
|
||||
boolean createSummary = type.equals(PUSH_TYPE_MESSAGE);
|
||||
if (!mAppLifecycleFacade.isAppVisible()) {
|
||||
if (type.equals(PUSH_TYPE_MESSAGE)) {
|
||||
if (channelId != null) {
|
||||
Map<String, List<Integer>> notificationsInChannel = loadNotificationsMap(mContext);
|
||||
List<Integer> list = notificationsInChannel.get(channelId);
|
||||
if (list == null) {
|
||||
list = Collections.synchronizedList(new ArrayList(0));
|
||||
}
|
||||
|
||||
list.add(0, notificationId);
|
||||
if (list.size() > 1) {
|
||||
createSummary = false;
|
||||
}
|
||||
|
||||
if (createSummary) {
|
||||
// Add the summary notification id as well
|
||||
list.add(0, notificationId + 1);
|
||||
}
|
||||
|
||||
notificationsInChannel.put(channelId, list);
|
||||
saveNotificationsMap(mContext, notificationsInChannel);
|
||||
}
|
||||
}
|
||||
|
||||
buildNotification(notificationId, createSummary);
|
||||
} else {
|
||||
notifyReceivedToJS();
|
||||
}
|
||||
break;
|
||||
case PUSH_TYPE_CLEAR:
|
||||
clearChannelNotifications(mContext, channelId);
|
||||
@@ -139,18 +165,41 @@ public class CustomPushNotification extends PushNotification {
|
||||
|
||||
Bundle data = mNotificationProps.asBundle();
|
||||
final String channelId = data.getString("channel_id");
|
||||
final Integer notificationId = data.getString("post_id").hashCode();
|
||||
final String postId = data.getString("post_id");
|
||||
Integer notificationId = CustomPushNotificationHelper.MESSAGE_NOTIFICATION_ID;
|
||||
|
||||
if (postId != null) {
|
||||
notificationId = postId.hashCode();
|
||||
}
|
||||
|
||||
if (channelId != null) {
|
||||
Map<String, List<Integer>> notificationsInChannel = loadNotificationsMap(mContext);
|
||||
List<Integer> notifications = notificationsInChannel.get(channelId);
|
||||
notifications.remove(notificationId);
|
||||
saveNotificationsMap(mContext, notificationsInChannel);
|
||||
clearChannelNotifications(mContext, channelId);
|
||||
}
|
||||
}
|
||||
|
||||
private void buildNotification(Integer notificationId, boolean createSummary) {
|
||||
final PendingIntent pendingIntent = super.getCTAPendingIntent();
|
||||
final Notification notification = buildNotification(pendingIntent);
|
||||
if (createSummary) {
|
||||
final Notification summary = getNotificationSummaryBuilder(pendingIntent).build();
|
||||
super.postNotification(summary, notificationId + 1);
|
||||
}
|
||||
super.postNotification(notification, notificationId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected NotificationCompat.Builder getNotificationBuilder(PendingIntent intent) {
|
||||
Bundle bundle = mNotificationProps.asBundle();
|
||||
return CustomPushNotificationHelper.createNotificationBuilder(mContext, intent, bundle);
|
||||
return CustomPushNotificationHelper.createNotificationBuilder(mContext, intent, bundle, false);
|
||||
}
|
||||
|
||||
protected NotificationCompat.Builder getNotificationSummaryBuilder(PendingIntent intent) {
|
||||
Bundle bundle = mNotificationProps.asBundle();
|
||||
return CustomPushNotificationHelper.createNotificationBuilder(mContext, intent, bundle, true);
|
||||
}
|
||||
|
||||
private void notificationReceiptDelivery(String ackId, String postId, String type, boolean isIdLoaded, ResolvePromise promise) {
|
||||
@@ -160,4 +209,43 @@ public class CustomPushNotification extends PushNotification {
|
||||
private void notifyReceivedToJS() {
|
||||
mJsIOHelper.sendEventToJS(NOTIFICATION_RECEIVED_EVENT_NAME, mNotificationProps.asBundle(), mAppLifecycleFacade.getRunningReactContext());
|
||||
}
|
||||
|
||||
private static void saveNotificationsMap(Context context, Map<String, List<Integer>> inputMap) {
|
||||
SharedPreferences pSharedPref = context.getSharedPreferences(PUSH_NOTIFICATIONS, Context.MODE_PRIVATE);
|
||||
if (pSharedPref != null && context != null) {
|
||||
JSONObject json = new JSONObject(inputMap);
|
||||
String jsonString = json.toString();
|
||||
SharedPreferences.Editor editor = pSharedPref.edit();
|
||||
editor.remove(NOTIFICATIONS_IN_CHANNEL).commit();
|
||||
editor.putString(NOTIFICATIONS_IN_CHANNEL, jsonString);
|
||||
editor.commit();
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<String, List<Integer>> loadNotificationsMap(Context context) {
|
||||
Map<String, List<Integer>> outputMap = new HashMap<>();
|
||||
if (context != null) {
|
||||
SharedPreferences pSharedPref = context.getSharedPreferences(PUSH_NOTIFICATIONS, Context.MODE_PRIVATE);
|
||||
try {
|
||||
if (pSharedPref != null) {
|
||||
String jsonString = pSharedPref.getString(NOTIFICATIONS_IN_CHANNEL, (new JSONObject()).toString());
|
||||
JSONObject json = new JSONObject(jsonString);
|
||||
Iterator<String> keysItr = json.keys();
|
||||
while (keysItr.hasNext()) {
|
||||
String key = keysItr.next();
|
||||
JSONArray array = json.getJSONArray(key);
|
||||
List<Integer> values = new ArrayList<>();
|
||||
for (int i = 0; i < array.length(); ++i) {
|
||||
values.add(array.getInt(i));
|
||||
}
|
||||
outputMap.put(key, values);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
return outputMap;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ public class CustomPushNotificationHelper {
|
||||
.addAction(replyAction);
|
||||
}
|
||||
|
||||
public static NotificationCompat.Builder createNotificationBuilder(Context context, PendingIntent intent, Bundle bundle) {
|
||||
public static NotificationCompat.Builder createNotificationBuilder(Context context, PendingIntent intent, Bundle bundle, boolean createSummary) {
|
||||
final NotificationCompat.Builder notification = new NotificationCompat.Builder(context, CHANNEL_HIGH_IMPORTANCE_ID);
|
||||
|
||||
String channelId = bundle.getString("channel_id");
|
||||
@@ -148,7 +148,7 @@ public class CustomPushNotificationHelper {
|
||||
addNotificationExtras(notification, bundle);
|
||||
setNotificationIcons(context, notification, bundle);
|
||||
setNotificationMessagingStyle(context, notification, bundle);
|
||||
setNotificationGroup(notification, channelId);
|
||||
setNotificationGroup(notification, channelId, createSummary);
|
||||
setNotificationBadgeType(notification);
|
||||
setNotificationSound(notification, notificationPreferences);
|
||||
setNotificationVibrate(notification, notificationPreferences);
|
||||
@@ -373,10 +373,13 @@ public class CustomPushNotificationHelper {
|
||||
notification.setStyle(messagingStyle);
|
||||
}
|
||||
|
||||
private static void setNotificationGroup(NotificationCompat.Builder notification, String channelId) {
|
||||
notification
|
||||
.setGroup(channelId)
|
||||
.setGroupSummary(true);
|
||||
private static void setNotificationGroup(NotificationCompat.Builder notification, String channelId, boolean setAsSummary) {
|
||||
notification.setGroup(channelId);
|
||||
|
||||
if (setAsSummary) {
|
||||
// if this is the first notification for the channel then set as summary, otherwise skip
|
||||
notification.setGroupSummary(true);
|
||||
}
|
||||
}
|
||||
|
||||
private static void setNotificationIcons(Context context, NotificationCompat.Builder notification, Bundle bundle) {
|
||||
|
||||
@@ -4,7 +4,6 @@ import android.content.Context;
|
||||
import android.content.RestrictionsManager;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -101,7 +100,7 @@ private final ReactNativeHost mReactNativeHost =
|
||||
|
||||
@Override
|
||||
protected JSIModulePackage getJSIModulePackage() {
|
||||
return new CustomMMKVJSIModulePackage();
|
||||
return (JSIModulePackage) new CustomMMKVJSIModulePackage();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -18,7 +18,14 @@ public class NotificationDismissService extends IntentService {
|
||||
final Context context = getApplicationContext();
|
||||
final Bundle bundle = NotificationIntentAdapter.extractPendingNotificationDataFromIntent(intent);
|
||||
final String channelId = bundle.getString("channel_id");
|
||||
final Integer notificationId = bundle.getString("post_id").hashCode();
|
||||
final String postId = bundle.getString("post_id");
|
||||
int notificationId = CustomPushNotificationHelper.MESSAGE_NOTIFICATION_ID;
|
||||
if (postId != null) {
|
||||
notificationId = postId.hashCode();
|
||||
} else if (channelId != null) {
|
||||
notificationId = channelId.hashCode();
|
||||
}
|
||||
|
||||
CustomPushNotification.cancelNotification(context, channelId, notificationId);
|
||||
Log.i("ReactNative", "Dismiss notification");
|
||||
}
|
||||
|
||||
@@ -9,11 +9,13 @@ import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.app.NotificationManagerCompat;
|
||||
import androidx.core.app.Person;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
import okhttp3.Call;
|
||||
import okhttp3.MediaType;
|
||||
@@ -21,7 +23,6 @@ import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.internal.annotations.EverythingIsNonNull;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import org.json.JSONException;
|
||||
@@ -88,21 +89,19 @@ public class NotificationReplyBroadcastReceiver extends BroadcastReceiver {
|
||||
|
||||
client.newCall(request).enqueue(new okhttp3.Callback() {
|
||||
@Override
|
||||
@EverythingIsNonNull
|
||||
public void onFailure(Call call, IOException e) {
|
||||
public void onFailure(@NonNull Call call, @NonNull IOException e) {
|
||||
Log.i("ReactNative", String.format("Reply FAILED exception %s", e.getMessage()));
|
||||
onReplyFailed(notificationId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@EverythingIsNonNull
|
||||
public void onResponse(Call call, final Response response) throws IOException {
|
||||
public void onResponse(@NonNull Call call, @NonNull final Response response) throws IOException {
|
||||
if (response.isSuccessful()) {
|
||||
onReplySuccess(notificationId, message);
|
||||
Log.i("ReactNative", "Reply SUCCESS");
|
||||
} else {
|
||||
assert response.body() != null;
|
||||
Log.i("ReactNative", String.format("Reply FAILED status %s BODY %s", response.code(), response.body().string()));
|
||||
Log.i("ReactNative", String.format("Reply FAILED status %s BODY %s", response.code(), Objects.requireNonNull(response.body()).string()));
|
||||
onReplyFailed(notificationId);
|
||||
}
|
||||
}
|
||||
@@ -133,7 +132,7 @@ public class NotificationReplyBroadcastReceiver extends BroadcastReceiver {
|
||||
final Intent cta = new Intent(mContext, ProxyService.class);
|
||||
final PushNotificationProps notificationProps = new PushNotificationProps(bundle);
|
||||
final PendingIntent pendingIntent = NotificationIntentAdapter.createPendingNotificationIntent(mContext, cta, notificationProps);
|
||||
NotificationCompat.Builder builder = CustomPushNotificationHelper.createNotificationBuilder(mContext, pendingIntent, bundle);
|
||||
NotificationCompat.Builder builder = CustomPushNotificationHelper.createNotificationBuilder(mContext, pendingIntent, bundle, false);
|
||||
Notification notification = builder.build();
|
||||
NotificationCompat.MessagingStyle messagingStyle = NotificationCompat.MessagingStyle.extractMessagingStyleFromNotification(notification);
|
||||
assert messagingStyle != null;
|
||||
|
||||
@@ -293,7 +293,44 @@ export function showModal(name, title, passProps = {}, options = {}) {
|
||||
|
||||
export function showModalOverCurrentContext(name, passProps = {}, options = {}) {
|
||||
const title = '';
|
||||
const animationsEnabled = (Platform.OS === 'android').toString();
|
||||
|
||||
let animations;
|
||||
switch (Platform.OS) {
|
||||
case 'android':
|
||||
animations = {
|
||||
showModal: {
|
||||
waitForRender: true,
|
||||
alpha: {
|
||||
from: 0,
|
||||
to: 1,
|
||||
duration: 250,
|
||||
},
|
||||
},
|
||||
dismissModal: {
|
||||
alpha: {
|
||||
from: 1,
|
||||
to: 0,
|
||||
duration: 250,
|
||||
},
|
||||
},
|
||||
};
|
||||
break;
|
||||
default:
|
||||
animations = {
|
||||
showModal: {
|
||||
enabled: false,
|
||||
enter: {},
|
||||
exit: {},
|
||||
},
|
||||
dismissModal: {
|
||||
enabled: false,
|
||||
enter: {},
|
||||
exit: {},
|
||||
},
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
const defaultOptions = {
|
||||
modalPresentationStyle: 'overCurrentContext',
|
||||
layout: {
|
||||
@@ -304,25 +341,7 @@ export function showModalOverCurrentContext(name, passProps = {}, options = {})
|
||||
visible: false,
|
||||
height: 0,
|
||||
},
|
||||
animations: {
|
||||
showModal: {
|
||||
waitForRender: true,
|
||||
enabled: animationsEnabled,
|
||||
alpha: {
|
||||
from: 0,
|
||||
to: 1,
|
||||
duration: 250,
|
||||
},
|
||||
},
|
||||
dismissModal: {
|
||||
enabled: animationsEnabled,
|
||||
alpha: {
|
||||
from: 1,
|
||||
to: 0,
|
||||
duration: 250,
|
||||
},
|
||||
},
|
||||
},
|
||||
animations,
|
||||
};
|
||||
const mergeOptions = merge(defaultOptions, options);
|
||||
|
||||
|
||||
@@ -301,7 +301,6 @@ describe('@actions/navigation', () => {
|
||||
test('showModalOverCurrentContext should call Navigation.showModal', () => {
|
||||
const showModal = jest.spyOn(Navigation, 'showModal');
|
||||
|
||||
const animationsEnabled = (Platform.OS === 'android').toString();
|
||||
const showModalOverCurrentContextTitle = '';
|
||||
const showModalOverCurrentContextOptions = {
|
||||
modalPresentationStyle: 'overCurrentContext',
|
||||
@@ -315,21 +314,14 @@ describe('@actions/navigation', () => {
|
||||
},
|
||||
animations: {
|
||||
showModal: {
|
||||
waitForRender: true,
|
||||
enabled: animationsEnabled,
|
||||
alpha: {
|
||||
from: 0,
|
||||
to: 1,
|
||||
duration: 250,
|
||||
},
|
||||
enabled: false,
|
||||
enter: {},
|
||||
exit: {},
|
||||
},
|
||||
dismissModal: {
|
||||
enabled: animationsEnabled,
|
||||
alpha: {
|
||||
from: 1,
|
||||
to: 0,
|
||||
duration: 250,
|
||||
},
|
||||
enabled: false,
|
||||
enter: {},
|
||||
exit: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -503,16 +503,7 @@ export function closeGMChannel(channel) {
|
||||
|
||||
export function refreshChannelWithRetry(channelId) {
|
||||
return async (dispatch) => {
|
||||
dispatch(setChannelRefreshing(true));
|
||||
const posts = await dispatch(fetchPostActionWithRetry(getPosts(channelId)));
|
||||
const actions = [setChannelRefreshing(false)];
|
||||
|
||||
if (posts) {
|
||||
actions.push(setChannelRetryFailed(false));
|
||||
}
|
||||
|
||||
dispatch(batchActions(actions, 'BATCH_REEFRESH_CHANNEL'));
|
||||
return posts;
|
||||
return dispatch(fetchPostActionWithRetry(getPosts(channelId)));
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ export function setProfileImageUri(imageUri = '') {
|
||||
export function removeProfileImage(user) {
|
||||
return async (dispatch) => {
|
||||
const result = await dispatch(setDefaultProfileImage(user));
|
||||
dispatch(setProfileImageUri());
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import DeviceInfo from 'react-native-device-info';
|
||||
import AndroidOpenSettings from 'react-native-android-open-settings';
|
||||
|
||||
import DocumentPicker from 'react-native-document-picker';
|
||||
import ImagePicker from 'react-native-image-picker';
|
||||
import {launchImageLibrary, launchCamera} from 'react-native-image-picker';
|
||||
import Permissions from 'react-native-permissions';
|
||||
|
||||
import {showModalOverCurrentContext} from '@actions/navigation';
|
||||
@@ -153,58 +153,35 @@ export default class AttachmentButton extends PureComponent {
|
||||
};
|
||||
|
||||
attachFileFromCamera = async (source, mediaType) => {
|
||||
const {formatMessage} = this.context.intl;
|
||||
const {title, text} = this.getPermissionDeniedMessage('camera', mediaType);
|
||||
const options = {
|
||||
quality: 0.8,
|
||||
videoQuality: 'high',
|
||||
noData: true,
|
||||
mediaType,
|
||||
storageOptions: {
|
||||
cameraRoll: true,
|
||||
waitUntilSaved: true,
|
||||
},
|
||||
permissionDenied: {
|
||||
title,
|
||||
text,
|
||||
reTryTitle: formatMessage({
|
||||
id: 'mobile.permission_denied_retry',
|
||||
defaultMessage: 'Settings',
|
||||
}),
|
||||
okTitle: formatMessage({id: 'mobile.permission_denied_dismiss', defaultMessage: 'Don\'t Allow'}),
|
||||
},
|
||||
saveToPhotos: true,
|
||||
};
|
||||
|
||||
const hasCameraPermission = await this.hasPhotoPermission(source, mediaType);
|
||||
|
||||
if (hasCameraPermission) {
|
||||
ImagePicker.launchCamera(options, (response) => {
|
||||
launchCamera(options, async (response) => {
|
||||
StatusBar.setHidden(false);
|
||||
emmProvider.inBackgroundSince = null;
|
||||
if (response.error || response.didCancel) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.uploadFiles([response]);
|
||||
const files = await this.getFilesFromResponse(response);
|
||||
this.uploadFiles(files);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
attachFileFromLibrary = async () => {
|
||||
const {formatMessage} = this.context.intl;
|
||||
const {title, text} = this.getPermissionDeniedMessage('photo');
|
||||
const options = {
|
||||
quality: 0.8,
|
||||
noData: true,
|
||||
permissionDenied: {
|
||||
title,
|
||||
text,
|
||||
reTryTitle: formatMessage({
|
||||
id: 'mobile.permission_denied_retry',
|
||||
defaultMessage: 'Settings',
|
||||
}),
|
||||
okTitle: formatMessage({id: 'mobile.permission_denied_dismiss', defaultMessage: 'Don\'t Allow'}),
|
||||
},
|
||||
mediaType: 'mixed',
|
||||
includeBase64: false,
|
||||
};
|
||||
|
||||
if (Platform.OS === 'ios') {
|
||||
@@ -214,14 +191,15 @@ export default class AttachmentButton extends PureComponent {
|
||||
const hasPhotoPermission = await this.hasPhotoPermission('photo', 'photo');
|
||||
|
||||
if (hasPhotoPermission) {
|
||||
ImagePicker.launchImageLibrary(options, (response) => {
|
||||
launchImageLibrary(options, async (response) => {
|
||||
StatusBar.setHidden(false);
|
||||
emmProvider.inBackgroundSince = null;
|
||||
if (response.error || response.didCancel) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.uploadFiles([response]);
|
||||
const files = await this.getFilesFromResponse(response);
|
||||
this.uploadFiles(files);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -231,30 +209,20 @@ export default class AttachmentButton extends PureComponent {
|
||||
};
|
||||
|
||||
attachVideoFromLibraryAndroid = () => {
|
||||
const {formatMessage} = this.context.intl;
|
||||
const {title, text} = this.getPermissionDeniedMessage('video');
|
||||
const options = {
|
||||
videoQuality: 'high',
|
||||
mediaType: 'video',
|
||||
noData: true,
|
||||
permissionDenied: {
|
||||
title,
|
||||
text,
|
||||
reTryTitle: formatMessage({
|
||||
id: 'mobile.permission_denied_retry',
|
||||
defaultMessage: 'Settings',
|
||||
}),
|
||||
okTitle: formatMessage({id: 'mobile.permission_denied_dismiss', defaultMessage: 'Don\'t Allow'}),
|
||||
},
|
||||
};
|
||||
|
||||
ImagePicker.launchImageLibrary(options, (response) => {
|
||||
launchImageLibrary(options, async (response) => {
|
||||
emmProvider.inBackgroundSince = null;
|
||||
if (response.error || response.didCancel) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.uploadFiles([response]);
|
||||
const files = await this.getFilesFromResponse(response);
|
||||
this.uploadFiles(files);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -286,6 +254,28 @@ export default class AttachmentButton extends PureComponent {
|
||||
}
|
||||
};
|
||||
|
||||
getFilesFromResponse = async (response) => {
|
||||
const files = [];
|
||||
const file = response.assets[0];
|
||||
if (Platform.OS === 'android') {
|
||||
// For android we need to retrieve the realPath in case the file being imported is from the cloud
|
||||
const uri = (await ShareExtension.getFilePath(file.uri)).filePath;
|
||||
const type = file.type || lookupMimeType(uri);
|
||||
let fileName = file.fileName;
|
||||
if (type.includes('video/')) {
|
||||
fileName = uri.split('\\').pop().split('/').pop();
|
||||
}
|
||||
|
||||
if (uri) {
|
||||
files.push({...file, fileName, uri, type});
|
||||
}
|
||||
} else {
|
||||
files.push(file);
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
hasPhotoPermission = async (source, mediaType = '') => {
|
||||
if (Platform.OS === 'ios') {
|
||||
const {formatMessage} = this.context.intl;
|
||||
|
||||
@@ -1403,6 +1403,7 @@ exports[`components/autocomplete/emoji_suggestion should match snapshot 2`] = `
|
||||
"massage_man",
|
||||
"massage_woman",
|
||||
"mate_drink",
|
||||
"mattermost",
|
||||
"mauritania",
|
||||
"mauritius",
|
||||
"mayotte",
|
||||
@@ -3742,6 +3743,7 @@ exports[`components/autocomplete/emoji_suggestion should match snapshot 2`] = `
|
||||
"massage_man",
|
||||
"massage_woman",
|
||||
"mate_drink",
|
||||
"mattermost",
|
||||
"mauritania",
|
||||
"mauritius",
|
||||
"mayotte",
|
||||
|
||||
@@ -13,7 +13,6 @@ import Fuse from 'fuse.js';
|
||||
|
||||
import Emoji from '@components/emoji';
|
||||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
import {BuiltInEmojis} from '@utils/emojis';
|
||||
import {getEmojiByName, compareEmojis} from '@utils/emoji_utils';
|
||||
import {makeStyleSheetFromTheme, changeOpacity} from '@utils/theme';
|
||||
|
||||
@@ -110,8 +109,8 @@ export default class EmojiSuggestion extends PureComponent {
|
||||
}
|
||||
|
||||
const emojiData = getEmojiByName(emoji);
|
||||
if (emojiData?.filename && !BuiltInEmojis.includes(emojiData.filename)) {
|
||||
const codeArray = emojiData.filename.split('-');
|
||||
if (emojiData?.image && emojiData.category !== 'custom') {
|
||||
const codeArray = emojiData.image.split('-');
|
||||
const code = codeArray.reduce((acc, c) => {
|
||||
return acc + String.fromCodePoint(parseInt(c, 16));
|
||||
}, '');
|
||||
@@ -126,7 +125,7 @@ export default class EmojiSuggestion extends PureComponent {
|
||||
|
||||
onChangeText(completedDraft);
|
||||
|
||||
if (Platform.OS === 'ios' && (!emojiData?.filename || BuiltInEmojis.includes(emojiData?.filename))) {
|
||||
if (Platform.OS === 'ios' && (!emojiData?.filename || emojiData.category !== 'custom')) {
|
||||
// This is the second part of the hack were we replace the double : with just one
|
||||
// after the auto correct vanished
|
||||
setTimeout(() => {
|
||||
|
||||
@@ -3,10 +3,7 @@
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Text,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import {Platform, Text, View} from 'react-native';
|
||||
import {injectIntl, intlShape} from 'react-intl';
|
||||
|
||||
import {displayUsername} from '@mm-redux/utils/user_utils';
|
||||
@@ -386,6 +383,11 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
marginHorizontal: 12,
|
||||
marginBottom: 12,
|
||||
overflow: 'hidden',
|
||||
...Platform.select({
|
||||
android: {
|
||||
scaleY: -1,
|
||||
},
|
||||
}),
|
||||
},
|
||||
displayName: {
|
||||
color: theme.centerChannelColor,
|
||||
|
||||
@@ -11,6 +11,8 @@ import {
|
||||
} from 'react-native';
|
||||
import FastImage, {ImageStyle} from 'react-native-fast-image';
|
||||
|
||||
const assetImages = new Map([['mattermost.png', require('@assets/images/emojis/mattermost.png')]]);
|
||||
|
||||
type Props = {
|
||||
|
||||
/*
|
||||
@@ -23,6 +25,11 @@ type Props = {
|
||||
*/
|
||||
imageUrl: string;
|
||||
|
||||
/*
|
||||
* asset name in case it is bundled with the app
|
||||
*/
|
||||
assetImage: string;
|
||||
|
||||
/*
|
||||
* Set if this is a custom emoji.
|
||||
*/
|
||||
@@ -45,6 +52,7 @@ const Emoji: React.FC<Props> = (props: Props) => {
|
||||
customEmojiStyle,
|
||||
displayTextOnly,
|
||||
imageUrl,
|
||||
assetImage,
|
||||
literal,
|
||||
unicode,
|
||||
testID,
|
||||
@@ -88,6 +96,24 @@ const Emoji: React.FC<Props> = (props: Props) => {
|
||||
);
|
||||
}
|
||||
|
||||
if (assetImage) {
|
||||
const key = Platform.OS === 'android' ? (`${assetImage}-${height}-${width}`) : null;
|
||||
|
||||
const image = assetImages.get(assetImage);
|
||||
if (!image) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<FastImage
|
||||
key={key}
|
||||
source={image}
|
||||
style={[customEmojiStyle, {width, height}]}
|
||||
resizeMode={FastImage.resizeMode.contain}
|
||||
testID={testID}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (!imageUrl) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -26,11 +26,17 @@ function mapStateToProps(state: GlobalState, ownProps: OwnProps) {
|
||||
|
||||
let imageUrl = '';
|
||||
let unicode;
|
||||
let assetImage = '';
|
||||
let isCustomEmoji = false;
|
||||
let displayTextOnly = false;
|
||||
if (EmojiIndicesByAlias.has(emojiName)) {
|
||||
const emoji = Emojis[EmojiIndicesByAlias.get(emojiName)!];
|
||||
unicode = emoji.image;
|
||||
if (emoji.category === 'custom') {
|
||||
assetImage = emoji.fileName;
|
||||
isCustomEmoji = true;
|
||||
} else {
|
||||
unicode = emoji.image;
|
||||
}
|
||||
} else if (customEmojis.has(emojiName) && serverUrl) {
|
||||
const emoji = customEmojis.get(emojiName);
|
||||
imageUrl = Client4.getCustomEmojiImageUrl(emoji!.id);
|
||||
@@ -45,6 +51,7 @@ function mapStateToProps(state: GlobalState, ownProps: OwnProps) {
|
||||
|
||||
return {
|
||||
imageUrl,
|
||||
assetImage,
|
||||
isCustomEmoji,
|
||||
displayTextOnly,
|
||||
unicode,
|
||||
|
||||
@@ -12861,7 +12861,25 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
|
||||
"key": "flags",
|
||||
},
|
||||
Object {
|
||||
"data": Array [],
|
||||
"data": Array [
|
||||
Object {
|
||||
"items": Array [
|
||||
Object {
|
||||
"aliases": Array [
|
||||
"mattermost",
|
||||
],
|
||||
"name": "mattermost",
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
],
|
||||
"key": "custom-0",
|
||||
},
|
||||
],
|
||||
"defaultMessage": undefined,
|
||||
"icon": "emoticon-custom-outline",
|
||||
"id": "emoji_picker.custom",
|
||||
|
||||
@@ -31,3 +31,164 @@ exports[`MarkdownEmoji should match snapshot 1`] = `
|
||||
</Unknown>
|
||||
</Unknown>
|
||||
`;
|
||||
|
||||
exports[`MarkdownEmoji should render with hardbreaks 1`] = `
|
||||
<Unknown
|
||||
context={Array []}
|
||||
first={true}
|
||||
last={true}
|
||||
literal={null}
|
||||
nodeKey="22"
|
||||
>
|
||||
<Unknown
|
||||
context={
|
||||
Array [
|
||||
"paragraph",
|
||||
]
|
||||
}
|
||||
emojiName="fire"
|
||||
literal=":fire:"
|
||||
nodeKey="4"
|
||||
>
|
||||
<Unknown
|
||||
context={
|
||||
Array [
|
||||
"paragraph",
|
||||
"emoji",
|
||||
]
|
||||
}
|
||||
literal=":fire:"
|
||||
nodeKey="3"
|
||||
/>
|
||||
</Unknown>
|
||||
<Unknown
|
||||
context={
|
||||
Array [
|
||||
"paragraph",
|
||||
]
|
||||
}
|
||||
literal=" "
|
||||
nodeKey="5"
|
||||
/>
|
||||
<Unknown
|
||||
context={
|
||||
Array [
|
||||
"paragraph",
|
||||
]
|
||||
}
|
||||
emojiName="fire"
|
||||
literal=":fire:"
|
||||
nodeKey="8"
|
||||
>
|
||||
<Unknown
|
||||
context={
|
||||
Array [
|
||||
"paragraph",
|
||||
"emoji",
|
||||
]
|
||||
}
|
||||
literal=":fire:"
|
||||
nodeKey="7"
|
||||
/>
|
||||
</Unknown>
|
||||
<Unknown
|
||||
context={
|
||||
Array [
|
||||
"paragraph",
|
||||
]
|
||||
}
|
||||
literal=""
|
||||
nodeKey="9"
|
||||
/>
|
||||
<Unknown
|
||||
context={
|
||||
Array [
|
||||
"paragraph",
|
||||
]
|
||||
}
|
||||
literal={null}
|
||||
nodeKey="10"
|
||||
/>
|
||||
<Unknown
|
||||
context={
|
||||
Array [
|
||||
"paragraph",
|
||||
]
|
||||
}
|
||||
emojiName="fire"
|
||||
literal=":fire:"
|
||||
nodeKey="13"
|
||||
>
|
||||
<Unknown
|
||||
context={
|
||||
Array [
|
||||
"paragraph",
|
||||
"emoji",
|
||||
]
|
||||
}
|
||||
literal=":fire:"
|
||||
nodeKey="12"
|
||||
/>
|
||||
</Unknown>
|
||||
<Unknown
|
||||
context={
|
||||
Array [
|
||||
"paragraph",
|
||||
]
|
||||
}
|
||||
literal=" "
|
||||
nodeKey="14"
|
||||
/>
|
||||
<Unknown
|
||||
context={
|
||||
Array [
|
||||
"paragraph",
|
||||
]
|
||||
}
|
||||
emojiName="fire"
|
||||
literal=":fire:"
|
||||
nodeKey="17"
|
||||
>
|
||||
<Unknown
|
||||
context={
|
||||
Array [
|
||||
"paragraph",
|
||||
"emoji",
|
||||
]
|
||||
}
|
||||
literal=":fire:"
|
||||
nodeKey="16"
|
||||
/>
|
||||
</Unknown>
|
||||
<Unknown
|
||||
context={
|
||||
Array [
|
||||
"paragraph",
|
||||
]
|
||||
}
|
||||
literal=" "
|
||||
nodeKey="18"
|
||||
/>
|
||||
<Unknown
|
||||
context={
|
||||
Array [
|
||||
"paragraph",
|
||||
]
|
||||
}
|
||||
emojiName="fire"
|
||||
literal=":fire:"
|
||||
nodeKey="21"
|
||||
>
|
||||
<Unknown
|
||||
context={
|
||||
Array [
|
||||
"paragraph",
|
||||
"emoji",
|
||||
]
|
||||
}
|
||||
literal=":fire:"
|
||||
nodeKey="20"
|
||||
/>
|
||||
</Unknown>
|
||||
</Unknown>
|
||||
`;
|
||||
|
||||
@@ -39,6 +39,7 @@ export default class MarkdownEmoji extends PureComponent {
|
||||
paragraph: this.renderParagraph,
|
||||
document: this.renderParagraph,
|
||||
text: this.renderText,
|
||||
hardbreak: this.renderNewLine,
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -73,10 +74,14 @@ export default class MarkdownEmoji extends PureComponent {
|
||||
|
||||
renderText = ({context, literal}) => {
|
||||
const style = this.computeTextStyle(this.props.baseTextStyle, context);
|
||||
|
||||
return <Text style={style}>{literal}</Text>;
|
||||
};
|
||||
|
||||
renderNewLine = ({context}) => {
|
||||
const style = this.computeTextStyle(this.props.baseTextStyle, context);
|
||||
return <Text style={style}>{'\n'}</Text>;
|
||||
}
|
||||
|
||||
renderEditedIndicator = ({context}) => {
|
||||
let spacer = '';
|
||||
if (context[0] === 'paragraph') {
|
||||
@@ -101,7 +106,7 @@ export default class MarkdownEmoji extends PureComponent {
|
||||
};
|
||||
|
||||
render() {
|
||||
const ast = this.parser.parse(this.props.value);
|
||||
const ast = this.parser.parse(this.props.value.replace(/\n*$/, ''));
|
||||
|
||||
if (this.props.isEdited) {
|
||||
const editIndicatorNode = new Node('edited_indicator');
|
||||
|
||||
@@ -24,4 +24,17 @@ describe('MarkdownEmoji', () => {
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should render with hardbreaks', () => {
|
||||
const wrapper = shallow(
|
||||
<MarkdownEmoji
|
||||
{...baseProps}
|
||||
value={`:fire: :fire:
|
||||
:fire: :fire: :fire:
|
||||
`}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import React from 'react';
|
||||
import {injectIntl, intlShape} from 'react-intl';
|
||||
import {Keyboard, View} from 'react-native';
|
||||
import {Keyboard, StyleProp, View, ViewStyle} from 'react-native';
|
||||
|
||||
import {showModalOverCurrentContext} from '@actions/navigation';
|
||||
import Markdown from '@components/markdown';
|
||||
@@ -31,6 +31,7 @@ type Props = {
|
||||
testID?: string;
|
||||
theme: Theme;
|
||||
usernamesById: Record<string, string>;
|
||||
style?: StyleProp<ViewStyle>;
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
@@ -87,7 +88,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
|
||||
const CombinedUserActivity = ({
|
||||
canDelete, currentUserId, currentUsername, intl, post,
|
||||
showJoinLeave, testID, theme, usernamesById,
|
||||
showJoinLeave, testID, theme, usernamesById, style,
|
||||
}: Props) => {
|
||||
const itemTestID = `${testID}.${post.id}`;
|
||||
const textStyles = getMarkdownTextStyles(theme);
|
||||
@@ -215,6 +216,7 @@ const CombinedUserActivity = ({
|
||||
|
||||
return (
|
||||
<View
|
||||
style={style}
|
||||
testID={testID}
|
||||
>
|
||||
<TouchableWithFeedback
|
||||
|
||||
@@ -5,6 +5,7 @@ import {connect} from 'react-redux';
|
||||
|
||||
import {handleSelectChannelByName, refreshChannelWithRetry} from '@actions/views/channel';
|
||||
import {closePermalink, showPermalink} from '@actions/views/permalink';
|
||||
import {getPostThread} from '@actions/views/post';
|
||||
import {setDeepLinkURL} from '@actions/views/root';
|
||||
import {getConfig, getCurrentUrl} from '@mm-redux/selectors/entities/general';
|
||||
import {getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
@@ -48,6 +49,7 @@ function mapStateToProps() {
|
||||
|
||||
const mapDispatchToProps = {
|
||||
closePermalink,
|
||||
getPostThread,
|
||||
handleSelectChannelByName,
|
||||
refreshChannelWithRetry,
|
||||
setDeepLinkURL,
|
||||
|
||||
@@ -95,7 +95,7 @@ const Header = ({
|
||||
testID='post_header'
|
||||
/>
|
||||
)}
|
||||
{!isSystemPost &&
|
||||
{(!isSystemPost || isAutoResponse) &&
|
||||
<HeaderTag
|
||||
isAutoResponder={isAutoResponse}
|
||||
isAutomation={isWebHook || isBot}
|
||||
|
||||
@@ -15,6 +15,7 @@ import {isPostFlagged, isSystemMessage} from '@mm-redux/utils/post_utils';
|
||||
import {canDeletePost} from '@selectors/permissions';
|
||||
import {areConsecutivePosts, postUserDisplayName} from '@utils/post';
|
||||
|
||||
import type {StyleProp, ViewStyle} from 'react-native';
|
||||
import type {Post as PostType} from '@mm-redux/types/posts';
|
||||
import type {Theme} from '@mm-redux/types/preferences';
|
||||
import type {GlobalState} from '@mm-redux/types/store';
|
||||
@@ -27,6 +28,7 @@ type OwnProps = {
|
||||
post?: PostType;
|
||||
previousPostId?: string;
|
||||
nextPostId?: string;
|
||||
style?: StyleProp<ViewStyle>;
|
||||
testID: string;
|
||||
theme: Theme;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import {injectIntl, intlShape} from 'react-intl';
|
||||
import {showModalOverCurrentContext} from '@actions/navigation';
|
||||
import SystemHeader from '@components/post_list/system_header';
|
||||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
import SystemAvatar from '@components/post_list/system_avatar';
|
||||
import * as Screens from '@constants/screen';
|
||||
import {Posts} from '@mm-redux/constants';
|
||||
import EventEmitter from '@mm-redux/utils/event_emitter';
|
||||
@@ -43,6 +44,7 @@ type PostProps = {
|
||||
showPermalink: (intl: typeof intlShape, teamName: string, postId: string) => null;
|
||||
skipFlaggedHeader?: boolean;
|
||||
skipPinnedHeader?: boolean;
|
||||
style?: StyleProp<ViewStyle>;
|
||||
teammateNameDisplay: string;
|
||||
testID?: string;
|
||||
theme: Theme
|
||||
@@ -91,10 +93,10 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
const Post = ({
|
||||
canDelete, enablePostUsernameOverride, highlight, highlightPinnedOrFlagged = true, intl, isConsecutivePost, isFirstReply, isFlagged, isLastReply,
|
||||
location, post, removePost, rootPostAuthor, shouldRenderReplyButton, skipFlaggedHeader, skipPinnedHeader, showAddReaction = true, showPermalink,
|
||||
teammateNameDisplay, testID, theme,
|
||||
teammateNameDisplay, testID, theme, style,
|
||||
}: PostProps) => {
|
||||
const pressDetected = useRef(false);
|
||||
const style = getStyleSheet(theme);
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
const handlePress = preventDoubleTap(() => {
|
||||
pressDetected.current = true;
|
||||
@@ -158,24 +160,27 @@ const Post = ({
|
||||
const highlightFlagged = isFlagged && !skipFlaggedHeader;
|
||||
const hightlightPinned = post.is_pinned && !skipPinnedHeader;
|
||||
const itemTestID = `${testID}.${post.id}`;
|
||||
const rightColumnStyle = [style.rightColumn, (post.root_id && isLastReply && style.rightColumnPadding)];
|
||||
const pendingPostStyle: StyleProp<ViewStyle> | undefined = isPostPendingOrFailed(post) ? style.pendingPost : undefined;
|
||||
const rightColumnStyle = [styles.rightColumn, (post.root_id && isLastReply && styles.rightColumnPadding)];
|
||||
const pendingPostStyle: StyleProp<ViewStyle> | undefined = isPostPendingOrFailed(post) ? styles.pendingPost : undefined;
|
||||
const isAutoResponder = fromAutoResponder(post);
|
||||
|
||||
let highlightedStyle: StyleProp<ViewStyle>;
|
||||
if (highlight) {
|
||||
highlightedStyle = style.highlight;
|
||||
highlightedStyle = styles.highlight;
|
||||
} else if ((highlightFlagged || hightlightPinned) && highlightPinnedOrFlagged) {
|
||||
highlightedStyle = style.highlightPinnedOrFlagged;
|
||||
highlightedStyle = styles.highlightPinnedOrFlagged;
|
||||
}
|
||||
|
||||
let header: ReactNode;
|
||||
let postAvatar: ReactNode;
|
||||
let consecutiveStyle: StyleProp<ViewStyle>;
|
||||
if (isConsecutivePost) {
|
||||
consecutiveStyle = style.consective;
|
||||
postAvatar = <View style={style.consecutivePostContainer}/>;
|
||||
consecutiveStyle = styles.consective;
|
||||
postAvatar = <View style={styles.consecutivePostContainer}/>;
|
||||
} else {
|
||||
postAvatar = (
|
||||
postAvatar = isAutoResponder ? (
|
||||
<SystemAvatar theme={theme}/>
|
||||
) : (
|
||||
<Avatar
|
||||
pendingPostStyle={pendingPostStyle}
|
||||
post={post}
|
||||
@@ -183,7 +188,7 @@ const Post = ({
|
||||
/>
|
||||
);
|
||||
|
||||
if (isSystemMessage(post)) {
|
||||
if (isSystemMessage(post) && !isAutoResponder) {
|
||||
header = (
|
||||
<SystemHeader
|
||||
createAt={post.create_at}
|
||||
@@ -206,7 +211,7 @@ const Post = ({
|
||||
}
|
||||
|
||||
let body;
|
||||
if (isSystemMessage(post) && !isPostEphemeral(post)) {
|
||||
if (isSystemMessage(post) && !isPostEphemeral(post) && !isAutoResponder) {
|
||||
body = (
|
||||
<SystemMessage
|
||||
post={post}
|
||||
@@ -231,7 +236,7 @@ const Post = ({
|
||||
return (
|
||||
<View
|
||||
testID={testID}
|
||||
style={[style.postStyle, highlightedStyle]}
|
||||
style={[styles.postStyle, style, highlightedStyle]}
|
||||
>
|
||||
<TouchableWithFeedback
|
||||
testID={itemTestID}
|
||||
@@ -250,7 +255,7 @@ const Post = ({
|
||||
skipPinnedHeader={skipPinnedHeader}
|
||||
theme={theme}
|
||||
/>
|
||||
<View style={[style.container, consecutiveStyle]}>
|
||||
<View style={[styles.container, consecutiveStyle]}>
|
||||
{postAvatar}
|
||||
<View style={rightColumnStyle}>
|
||||
{header}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {ReactElement, useCallback, useEffect, useLayoutEffect, useRef} from 'react';
|
||||
import React, {ReactElement, useCallback, useEffect, useLayoutEffect, useRef, useState} from 'react';
|
||||
import {injectIntl, intlShape} from 'react-intl';
|
||||
import {DeviceEventEmitter, FlatList, Platform, RefreshControl, StyleSheet, ViewToken} from 'react-native';
|
||||
import {DeviceEventEmitter, FlatList, NativeScrollEvent, NativeSyntheticEvent, Platform, StyleSheet, ViewToken} from 'react-native';
|
||||
|
||||
import {DeepLinkTypes, NavigationTypes} from '@constants';
|
||||
import * as Screens from '@constants/screen';
|
||||
@@ -27,6 +27,7 @@ import DateSeparator from './date_separator';
|
||||
import MoreMessagesButton from './more_messages_button';
|
||||
import NewMessagesLine from './new_message_line';
|
||||
import Post from './post';
|
||||
import PostListRefreshControl from './post_list_refresh_control';
|
||||
|
||||
type PostListProps = {
|
||||
channelId?: string;
|
||||
@@ -34,6 +35,7 @@ type PostListProps = {
|
||||
currentTeamName: string;
|
||||
deepLinkURL?: string;
|
||||
extraData: never;
|
||||
getPostThread: (rootId: string) => Promise<ActionResult>;
|
||||
handleSelectChannelByName: (channelName: string, teamName: string, errorHandler: (intl: typeof intlShape) => void, intl: typeof intlShape) => Promise<ActionResult>;
|
||||
highlightPinnedOrFlagged?: boolean;
|
||||
highlightPostId?: string;
|
||||
@@ -43,7 +45,9 @@ type PostListProps = {
|
||||
location: string;
|
||||
onLoadMoreUp: () => void;
|
||||
postIds: string[];
|
||||
refreshChannelWithRetry: (channelId: string) => Promise<ActionResult>;
|
||||
renderFooter: () => ReactElement | null;
|
||||
rootId?: string;
|
||||
scrollViewNativeID?: string;
|
||||
serverURL: string;
|
||||
shouldRenderReplyButton?: boolean;
|
||||
@@ -76,13 +80,21 @@ const styles = StyleSheet.create({
|
||||
postListContent: {
|
||||
paddingTop: 5,
|
||||
},
|
||||
scale: {
|
||||
...Platform.select({
|
||||
android: {
|
||||
scaleY: -1,
|
||||
},
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
const buildExtraData = makeExtraData();
|
||||
|
||||
const PostList = ({
|
||||
channelId, currentTeamName = '', closePermalink, deepLinkURL, extraData, handleSelectChannelByName, highlightPostId, highlightPinnedOrFlagged, initialIndex, intl,
|
||||
loadMorePostsVisible, location, onLoadMoreUp = emptyFunction, postIds = [], renderFooter = (() => null),
|
||||
channelId, currentTeamName = '', closePermalink, deepLinkURL, extraData, getPostThread,
|
||||
handleSelectChannelByName, highlightPostId, highlightPinnedOrFlagged, initialIndex, intl, loadMorePostsVisible,
|
||||
location, onLoadMoreUp = emptyFunction, postIds = [], refreshChannelWithRetry, renderFooter = (() => null), rootId,
|
||||
serverURL = '', setDeepLinkURL, showMoreMessagesButton, showPermalink, siteURL = '', scrollViewNativeID, shouldRenderReplyButton, testID, theme,
|
||||
}: PostListProps) => {
|
||||
const prevChannelId = useRef(channelId);
|
||||
@@ -90,6 +102,8 @@ const PostList = ({
|
||||
const flatListRef = useRef<FlatList<never>>(null);
|
||||
const onScrollEndIndexListener = useRef<onScrollEndIndexListenerEvent>();
|
||||
const onViewableItemsChangedListener = useRef<ViewableItemsChangedListenerEvent>();
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [offsetY, setOffsetY] = useState(0);
|
||||
|
||||
const registerViewableItemsListener = useCallback((listener) => {
|
||||
onViewableItemsChangedListener.current = listener;
|
||||
@@ -118,6 +132,18 @@ const PostList = ({
|
||||
showPermalink(intl, teamName, postId);
|
||||
};
|
||||
|
||||
const onRefresh = async () => {
|
||||
if (location === Screens.CHANNEL && channelId) {
|
||||
setRefreshing(true);
|
||||
await refreshChannelWithRetry(channelId);
|
||||
setRefreshing(false);
|
||||
} else if (location === Screens.THREAD && rootId) {
|
||||
setRefreshing(true);
|
||||
await getPostThread(rootId);
|
||||
setRefreshing(false);
|
||||
}
|
||||
};
|
||||
|
||||
const onScrollToIndexFailed = useCallback((info: ScrollIndexFailed) => {
|
||||
const animationFrameIndexFailed = requestAnimationFrame(() => {
|
||||
const index = Math.min(info.highestMeasuredFrameIndex, info.index);
|
||||
@@ -164,6 +190,7 @@ const PostList = ({
|
||||
theme={theme}
|
||||
moreMessages={moreNewMessages && checkForPostId}
|
||||
testID={`${testID}.new_messages_line`}
|
||||
style={styles.scale}
|
||||
/>
|
||||
);
|
||||
} else if (isDateLine(item)) {
|
||||
@@ -171,6 +198,7 @@ const PostList = ({
|
||||
<DateSeparator
|
||||
date={getDateForDateLine(item)}
|
||||
theme={theme}
|
||||
style={styles.scale}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -178,6 +206,7 @@ const PostList = ({
|
||||
if (isCombinedUserActivityPost(item)) {
|
||||
const postProps = {
|
||||
postId: item,
|
||||
style: styles.scale,
|
||||
testID: `${testID}.combined_user_activity`,
|
||||
theme,
|
||||
};
|
||||
@@ -215,6 +244,7 @@ const PostList = ({
|
||||
<Post
|
||||
highlight={highlightPostId === item}
|
||||
postId={item}
|
||||
style={styles.scale}
|
||||
testID={`${testID}.post`}
|
||||
{...postProps}
|
||||
/>
|
||||
@@ -230,6 +260,17 @@ const PostList = ({
|
||||
});
|
||||
}, []);
|
||||
|
||||
const onScroll = useCallback((event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
||||
if (Platform.OS === 'android') {
|
||||
const {y} = event.nativeEvent.contentOffset;
|
||||
if (y === 0) {
|
||||
setOffsetY(y);
|
||||
} else if (offsetY === 0 && y !== 0) {
|
||||
setOffsetY(y);
|
||||
}
|
||||
}
|
||||
}, [offsetY]);
|
||||
|
||||
useResetNativeScrollView(scrollViewNativeID, postIds);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -283,49 +324,47 @@ const PostList = ({
|
||||
}
|
||||
}, [initialIndex, highlightPostId]);
|
||||
|
||||
let refreshControl;
|
||||
if (location === Screens.PERMALINK) {
|
||||
// Hack so that the scrolling the permalink does not dismisses the modal screens
|
||||
refreshControl = (
|
||||
<RefreshControl
|
||||
refreshing={false}
|
||||
enabled={Platform.OS === 'ios'}
|
||||
colors={[theme.centerChannelColor]}
|
||||
tintColor={theme.centerChannelColor}
|
||||
/>);
|
||||
}
|
||||
const list = (
|
||||
<FlatList
|
||||
contentContainerStyle={styles.postListContent}
|
||||
data={postIds}
|
||||
extraData={buildExtraData(channelId, highlightPostId, extraData, loadMorePostsVisible)}
|
||||
initialNumToRender={INITIAL_BATCH_TO_RENDER}
|
||||
key={`recyclerFor-${channelId}-${hasPostsKey}`}
|
||||
keyboardDismissMode={'interactive'}
|
||||
keyboardShouldPersistTaps={'handled'}
|
||||
keyExtractor={keyExtractor}
|
||||
ListFooterComponent={renderFooter}
|
||||
listKey={`recyclerFor-${channelId}`}
|
||||
maintainVisibleContentPosition={SCROLL_POSITION_CONFIG}
|
||||
maxToRenderPerBatch={Platform.select({android: 5})}
|
||||
nativeID={scrollViewNativeID}
|
||||
onEndReached={onLoadMoreUp}
|
||||
onEndReachedThreshold={2}
|
||||
onScroll={onScroll}
|
||||
onScrollToIndexFailed={onScrollToIndexFailed}
|
||||
onViewableItemsChanged={onViewableItemsChanged}
|
||||
ref={flatListRef}
|
||||
removeClippedSubviews={true}
|
||||
renderItem={renderItem}
|
||||
scrollEventThrottle={60}
|
||||
style={styles.flex}
|
||||
windowSize={Posts.POST_CHUNK_SIZE / 2}
|
||||
viewabilityConfig={VIEWABILITY_CONFIG}
|
||||
testID={testID}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<FlatList
|
||||
contentContainerStyle={styles.postListContent}
|
||||
data={postIds}
|
||||
extraData={buildExtraData(channelId, highlightPostId, extraData, loadMorePostsVisible)}
|
||||
initialNumToRender={INITIAL_BATCH_TO_RENDER}
|
||||
inverted={true}
|
||||
key={`recyclerFor-${channelId}-${hasPostsKey}`}
|
||||
keyboardDismissMode={'interactive'}
|
||||
keyboardShouldPersistTaps={'handled'}
|
||||
keyExtractor={keyExtractor}
|
||||
ListFooterComponent={renderFooter}
|
||||
listKey={`recyclerFor-${channelId}`}
|
||||
maintainVisibleContentPosition={SCROLL_POSITION_CONFIG}
|
||||
maxToRenderPerBatch={Platform.select({android: 5})}
|
||||
nativeID={scrollViewNativeID}
|
||||
onScrollToIndexFailed={onScrollToIndexFailed}
|
||||
ref={flatListRef}
|
||||
onEndReached={onLoadMoreUp}
|
||||
onEndReachedThreshold={2}
|
||||
removeClippedSubviews={true}
|
||||
renderItem={renderItem}
|
||||
scrollEventThrottle={60}
|
||||
style={styles.flex}
|
||||
windowSize={Posts.POST_CHUNK_SIZE / 2}
|
||||
viewabilityConfig={VIEWABILITY_CONFIG}
|
||||
onViewableItemsChanged={onViewableItemsChanged}
|
||||
refreshControl={refreshControl}
|
||||
testID={testID}
|
||||
/>
|
||||
<PostListRefreshControl
|
||||
enabled={offsetY === 0}
|
||||
refreshing={refreshing}
|
||||
onRefresh={onRefresh}
|
||||
theme={theme}
|
||||
>
|
||||
{list}
|
||||
</PostListRefreshControl>
|
||||
{showMoreMessagesButton &&
|
||||
<MoreMessagesButton
|
||||
channelId={channelId}
|
||||
|
||||
52
app/components/post_list/post_list_refresh_control.tsx
Normal file
52
app/components/post_list/post_list_refresh_control.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {ReactElement} from 'react';
|
||||
import {Platform, RefreshControl, StyleSheet} from 'react-native';
|
||||
|
||||
import type {Theme} from '@mm-redux/types/preferences';
|
||||
|
||||
type Props = {
|
||||
children: ReactElement;
|
||||
enabled: boolean;
|
||||
onRefresh: () => void;
|
||||
refreshing: boolean;
|
||||
theme: Theme;
|
||||
}
|
||||
|
||||
const style = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
scaleY: -1,
|
||||
},
|
||||
});
|
||||
|
||||
const PostListRefreshControl = ({children, enabled, onRefresh, refreshing, theme}: Props) => {
|
||||
const props = {
|
||||
colors: [theme.onlineIndicator, theme.awayIndicator, theme.dndIndicator],
|
||||
onRefresh,
|
||||
refreshing,
|
||||
tintColor: theme.centerChannelColor,
|
||||
};
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
return (
|
||||
<RefreshControl
|
||||
{...props}
|
||||
enabled={enabled}
|
||||
style={style.container}
|
||||
>
|
||||
{children}
|
||||
</RefreshControl>
|
||||
);
|
||||
}
|
||||
|
||||
const refreshControl = <RefreshControl {...props}/>;
|
||||
|
||||
return React.cloneElement(
|
||||
children,
|
||||
{refreshControl, inverted: true},
|
||||
);
|
||||
};
|
||||
|
||||
export default PostListRefreshControl;
|
||||
@@ -118,13 +118,13 @@ const ProfilePicture = (props: ProfilePictureProps) => {
|
||||
}, [props.profileImageUri]);
|
||||
|
||||
useDidUpdate(() => {
|
||||
const {user} = props;
|
||||
const {edit, user} = props;
|
||||
|
||||
const url = user ? Client4.getProfilePictureUrl(user.id, user.last_picture_update) : undefined;
|
||||
if (url !== pictureUrl) {
|
||||
if (url !== pictureUrl && !edit) {
|
||||
setPictureUrl(url);
|
||||
}
|
||||
}, [props.user]);
|
||||
}, [props.user, props.edit]);
|
||||
|
||||
let statusIcon;
|
||||
let statusStyle = props.statusStyle;
|
||||
|
||||
@@ -381,7 +381,7 @@ class FilteredList extends Component {
|
||||
<View style={styles.container}>
|
||||
<SectionList
|
||||
sections={dataSource}
|
||||
removeClippedSubviews={true}
|
||||
removeClippedSubviews={false}
|
||||
renderItem={this.renderItem}
|
||||
renderSectionHeader={this.renderSectionHeader}
|
||||
keyExtractor={this.keyExtractor}
|
||||
|
||||
@@ -18,6 +18,7 @@ import {resetMomentLocale} from '@i18n';
|
||||
import {setupPermanentSidebar} from '@init/device';
|
||||
import PushNotifications from '@init/push_notifications';
|
||||
import {setAppState, setServerVersion} from '@mm-redux/actions/general';
|
||||
import {getTeams} from '@mm-redux/actions/teams';
|
||||
import {autoUpdateTimezone} from '@mm-redux/actions/timezone';
|
||||
import {close as closeWebSocket} from '@actions/websocket';
|
||||
import {Client4} from '@client/rest';
|
||||
@@ -213,6 +214,7 @@ class GlobalEventHandler {
|
||||
|
||||
await dispatch(loadConfigAndLicense());
|
||||
await dispatch(loadMe(user));
|
||||
dispatch(getTeams());
|
||||
|
||||
const window = Dimensions.get('window');
|
||||
this.onOrientationChange({window});
|
||||
|
||||
@@ -75,8 +75,11 @@ describe('PushNotification', () => {
|
||||
const deliveredNotifications = [{identifier: 1}, {identifier: 2}];
|
||||
Notifications.setDeliveredNotifications(deliveredNotifications);
|
||||
|
||||
await PushNotification.clearChannelNotifications();
|
||||
await PushNotification.clearChannelNotifications('some other channel');
|
||||
expect(setApplicationIconBadgeNumber).toHaveBeenCalledWith(deliveredNotifications.length);
|
||||
|
||||
await PushNotification.clearChannelNotifications();
|
||||
expect(setApplicationIconBadgeNumber).toHaveBeenCalledWith(0);
|
||||
});
|
||||
|
||||
it('clearChannelNotifications should set app badge number from redux store when set', async () => {
|
||||
@@ -90,7 +93,7 @@ describe('PushNotification', () => {
|
||||
const stateBadgeCount = 2 * deliveredNotifications.length;
|
||||
ViewSelectors.getBadgeCount = jest.fn().mockReturnValue(stateBadgeCount);
|
||||
|
||||
await PushNotification.clearChannelNotifications();
|
||||
await PushNotification.clearChannelNotifications('some other channel');
|
||||
expect(setApplicationIconBadgeNumber).toHaveBeenCalledWith(stateBadgeCount);
|
||||
});
|
||||
|
||||
@@ -103,7 +106,7 @@ describe('PushNotification', () => {
|
||||
|
||||
deliveredNotifications = [];
|
||||
Notifications.setDeliveredNotifications(deliveredNotifications);
|
||||
await PushNotification.clearChannelNotifications();
|
||||
await PushNotification.clearChannelNotifications('some other channel');
|
||||
expect(setBadgeCount).toHaveBeenCalledWith(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -88,6 +88,18 @@ class PushNotifications {
|
||||
//set the badge count to the total amount of notifications present in the not-center
|
||||
let badgeCount = notifications.length;
|
||||
|
||||
for (let i = 0; i < notifications.length; i++) {
|
||||
const notification = notifications[i] as NotificationWithChannel;
|
||||
if (notification.channel_id === channelId) {
|
||||
ids.push(notification.identifier);
|
||||
badgeCount--;
|
||||
}
|
||||
}
|
||||
|
||||
if (ids.length) {
|
||||
Notifications.ios.removeDeliveredNotifications(ids);
|
||||
}
|
||||
|
||||
if (Store.redux) {
|
||||
const totalMentions = getBadgeCount(Store.redux.getState());
|
||||
if (totalMentions > -1) {
|
||||
@@ -96,17 +108,6 @@ class PushNotifications {
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < notifications.length; i++) {
|
||||
const notification = notifications[i] as NotificationWithChannel;
|
||||
if (notification.channel_id === channelId) {
|
||||
ids.push(notification.identifier);
|
||||
}
|
||||
}
|
||||
|
||||
if (ids.length) {
|
||||
Notifications.ios.removeDeliveredNotifications(ids);
|
||||
}
|
||||
|
||||
if (Platform.OS === 'ios') {
|
||||
badgeCount = badgeCount <= 0 ? 0 : badgeCount;
|
||||
Notifications.ios.setBadgeCount(badgeCount);
|
||||
|
||||
@@ -22,6 +22,8 @@ const Preferences: Dictionary<any> = {
|
||||
INTERVAL_FIFTEEN_MINUTES: 15 * 60,
|
||||
INTERVAL_HOUR: 60 * 60,
|
||||
INTERVAL_IMMEDIATE: 30,
|
||||
CATEGORY_EMOJI: 'emoji',
|
||||
EMOJI_SKINTONE: 'emoji_skintone',
|
||||
|
||||
CATEGORY_CUSTOM_STATUS: 'custom_status',
|
||||
NAME_CUSTOM_STATUS_TUTORIAL_STATE: 'custom_status_tutorial_state',
|
||||
|
||||
@@ -23,7 +23,6 @@ export default class ChannelPostList extends PureComponent {
|
||||
getPostThread: PropTypes.func.isRequired,
|
||||
increasePostVisibility: PropTypes.func.isRequired,
|
||||
selectPost: PropTypes.func.isRequired,
|
||||
refreshChannelWithRetry: PropTypes.func.isRequired,
|
||||
setChannelRefreshing: PropTypes.func,
|
||||
}).isRequired,
|
||||
channelId: PropTypes.string.isRequired,
|
||||
|
||||
@@ -15,7 +15,6 @@ describe('ChannelPostList', () => {
|
||||
getPostThread: jest.fn(),
|
||||
increasePostVisibility: jest.fn(),
|
||||
selectPost: jest.fn(),
|
||||
refreshChannelWithRetry: jest.fn(),
|
||||
},
|
||||
channelId: 'channel-id',
|
||||
loadMorePostsVisible: false,
|
||||
|
||||
@@ -4,11 +4,7 @@
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {
|
||||
loadPostsIfNecessaryWithRetry,
|
||||
increasePostVisibility,
|
||||
refreshChannelWithRetry,
|
||||
} from '@actions/views/channel';
|
||||
import {loadPostsIfNecessaryWithRetry, increasePostVisibility} from '@actions/views/channel';
|
||||
import {getPostThread} from '@actions/views/post';
|
||||
import {Types} from '@constants';
|
||||
import {selectPost} from '@mm-redux/actions/posts';
|
||||
@@ -45,7 +41,6 @@ function mapDispatchToProps(dispatch) {
|
||||
getPostThread,
|
||||
increasePostVisibility,
|
||||
selectPost,
|
||||
refreshChannelWithRetry,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getConfig} from '@mm-redux/selectors/entities/general';
|
||||
import {getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
import {getTeammateNameDisplaySetting, getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
import {getCurrentUser} from '@mm-redux/selectors/entities/users';
|
||||
import {updateMe} from '@mm-redux/actions/users';
|
||||
|
||||
@@ -19,6 +19,7 @@ function mapStateToProps(state) {
|
||||
|
||||
return {
|
||||
config,
|
||||
teammateNameDisplay: getTeammateNameDisplaySetting(state),
|
||||
theme,
|
||||
updateMeRequest,
|
||||
currentUser,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
/* eslint-disable max-lines */
|
||||
|
||||
import React from 'react';
|
||||
import {injectIntl} from 'react-intl';
|
||||
import {
|
||||
@@ -14,13 +16,14 @@ import {
|
||||
} from 'react-native';
|
||||
|
||||
import deepEqual from 'deep-equal';
|
||||
import PropTypes from 'prop-types';
|
||||
import PropTypes, {string} from 'prop-types';
|
||||
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import RadioButtonGroup from '@components/radio_button';
|
||||
import StatusBar from '@components/status_bar';
|
||||
import PushNotifications from '@init/push_notifications';
|
||||
import {RequestStatus} from '@mm-redux/constants';
|
||||
import {displayUsername} from '@mm-redux/utils/user_utils';
|
||||
import SectionItem from '@screens/settings/section_item';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {getNotificationProps} from '@utils/notify_props';
|
||||
@@ -31,6 +34,7 @@ import NotificationSettingsMobileBase from './notification_settings_mobile_base'
|
||||
class NotificationSettingsMobileAndroid extends NotificationSettingsMobileBase {
|
||||
static propTypes = {
|
||||
...NotificationSettingsMobileBase.propTypes,
|
||||
teammateNameDisplay: string,
|
||||
updateMeRequest: PropTypes.object.isRequired,
|
||||
}
|
||||
|
||||
@@ -694,13 +698,15 @@ class NotificationSettingsMobileAndroid extends NotificationSettingsMobileBase {
|
||||
};
|
||||
|
||||
sendTestNotification = () => {
|
||||
const {intl} = this.props;
|
||||
const {currentUser, intl, teammateNameDisplay} = this.props;
|
||||
|
||||
PushNotifications.localNotification({
|
||||
body: intl.formatMessage({
|
||||
id: 'mobile.notification_settings_mobile.test_push',
|
||||
defaultMessage: 'This is a test push notification',
|
||||
}),
|
||||
sender_name: displayUsername(currentUser, teammateNameDisplay),
|
||||
sender_id: currentUser.id,
|
||||
userInfo: {
|
||||
local: true,
|
||||
test: true,
|
||||
|
||||
@@ -44,6 +44,7 @@ exports[`thread should match snapshot, has root post 1`] = `
|
||||
style={Object {}}
|
||||
/>
|
||||
}
|
||||
rootId="root_id"
|
||||
scrollViewNativeID="threadPostList"
|
||||
testID="thread.post_list"
|
||||
/>
|
||||
@@ -134,6 +135,7 @@ exports[`thread should match snapshot, render footer 1`] = `
|
||||
style={Object {}}
|
||||
/>
|
||||
}
|
||||
rootId="root_id"
|
||||
scrollViewNativeID="threadPostList"
|
||||
testID="thread.post_list"
|
||||
/>
|
||||
@@ -153,6 +155,7 @@ exports[`thread should match snapshot, render footer 2`] = `
|
||||
]
|
||||
}
|
||||
renderFooter={null}
|
||||
rootId="root_id"
|
||||
scrollViewNativeID="threadPostList"
|
||||
testID="thread.post_list"
|
||||
/>
|
||||
|
||||
@@ -42,6 +42,7 @@ export default class ThreadAndroid extends ThreadBase {
|
||||
currentUserId={myMember && myMember.user_id}
|
||||
lastViewedAt={this.state.lastViewedAt}
|
||||
location={THREAD}
|
||||
rootId={rootId}
|
||||
/>
|
||||
</Animated.View>
|
||||
<PostDraft
|
||||
|
||||
@@ -55,6 +55,7 @@ export default class ThreadIOS extends ThreadBase {
|
||||
currentUserId={myMember && myMember.user_id}
|
||||
lastViewedAt={this.state.lastViewedAt}
|
||||
location={THREAD}
|
||||
rootId={rootId}
|
||||
scrollViewNativeID={SCROLLVIEW_NATIVE_ID}
|
||||
/>
|
||||
</Animated.View>
|
||||
|
||||
@@ -6,6 +6,8 @@ import {createSelector} from 'reselect';
|
||||
import {getCustomEmojisByName as selectCustomEmojisByName} from '@mm-redux/selectors/entities/emojis';
|
||||
import {createIdsSelector} from '@mm-redux/utils/helpers';
|
||||
import {CategoryNames, CategoryTranslations, Emojis, EmojiIndicesByAlias, EmojiIndicesByCategory, CategoryMessage} from '@utils/emojis';
|
||||
import {get} from '@mm-redux/selectors/entities/preferences';
|
||||
import {Preferences} from '@mm-redux/constants';
|
||||
|
||||
const icons = {
|
||||
recent: 'clock-outline',
|
||||
@@ -38,24 +40,47 @@ function fillEmoji(indice) {
|
||||
};
|
||||
}
|
||||
|
||||
// if an emoji
|
||||
// - has `skin_variations` then it uses the default skin (yellow)
|
||||
// - has `skins` it's first value is considered the skin version (it can contain more values)
|
||||
// - any other case it doesn't have variations or is a custom emoji.
|
||||
function getSkin(emoji) {
|
||||
if ('skin_variations' in emoji) {
|
||||
return 'default';
|
||||
}
|
||||
if ('skins' in emoji) {
|
||||
return emoji.skins && emoji.skins[0];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export const selectEmojisByName = createIdsSelector(
|
||||
selectCustomEmojisByName,
|
||||
(customEmojis) => {
|
||||
getUserSkinTone,
|
||||
(customEmojis, skinTone) => {
|
||||
const emoticons = new Set();
|
||||
for (const [key] of [...EmojiIndicesByAlias.entries(), ...customEmojis.entries()]) {
|
||||
if (!key.includes('skin_tone')) {
|
||||
for (const [key, index] of EmojiIndicesByAlias.entries()) {
|
||||
const skin = getSkin(Emojis[index]);
|
||||
if (!skin || skin === skinTone) {
|
||||
emoticons.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
for (const [key] of customEmojis.entries()) {
|
||||
emoticons.add(key);
|
||||
}
|
||||
return Array.from(emoticons);
|
||||
},
|
||||
);
|
||||
|
||||
export function getUserSkinTone(state) {
|
||||
return get(state, Preferences.CATEGORY_EMOJI, Preferences.EMOJI_SKINTONE, 'default');
|
||||
}
|
||||
|
||||
export const selectEmojisBySection = createSelector(
|
||||
selectCustomEmojisByName,
|
||||
(state) => state.views.recentEmojis,
|
||||
(customEmojis, recentEmojis) => {
|
||||
getUserSkinTone,
|
||||
(customEmojis, recentEmojis, skinTone) => {
|
||||
const customEmojiItems = [];
|
||||
for (const [key] of customEmojis) {
|
||||
customEmojiItems.push({
|
||||
@@ -66,8 +91,7 @@ export const selectEmojisBySection = createSelector(
|
||||
const filteredCategories = CategoryNames.filter((category) => category !== 'recent' || recentItems.length > 0);
|
||||
|
||||
const emoticons = filteredCategories.map((category) => {
|
||||
// TODO: change default into user selected category once the user is able to choose it
|
||||
const items = EmojiIndicesByCategory.get('default').get(category).map(fillEmoji);
|
||||
const items = EmojiIndicesByCategory.get(skinTone).get(category).map(fillEmoji);
|
||||
const data = items;
|
||||
if (category === 'custom') {
|
||||
data.push(...customEmojiItems);
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -141,6 +141,24 @@
|
||||
"date_separator.yesterday": "Вчера",
|
||||
"edit_post.editPost": "Редактиране на публикация...",
|
||||
"edit_post.save": "Запиши",
|
||||
"emoji_picker.activities": "Дейности",
|
||||
"emoji_picker.animals-nature": "Животни и природа",
|
||||
"emoji_picker.custom": "Потребителски",
|
||||
"emoji_picker.flags": "Флагове",
|
||||
"emoji_picker.food-drink": "Храни и напитки",
|
||||
"emoji_picker.objects": "Обекти",
|
||||
"emoji_picker.people-body": "Хора и тяло",
|
||||
"emoji_picker.recent": "Последни",
|
||||
"emoji_picker.searchResults": "Резултати от търсенето",
|
||||
"emoji_picker.smileys-emotion": "Усмивки и емоции",
|
||||
"emoji_picker.symbols": "Символи",
|
||||
"emoji_picker.travel-places": "Пътуване и места",
|
||||
"emoji_skin.dark_skin_tone": "Тъмна кожа",
|
||||
"emoji_skin.default": "Цвят на кожата по подразбиране",
|
||||
"emoji_skin.light_skin_tone": "Светла кожа",
|
||||
"emoji_skin.medium_dark_skin_tone": "Средно тъмна кожа",
|
||||
"emoji_skin.medium_light_skin_tone": "Средно светла кожа",
|
||||
"emoji_skin.medium_skin_tone": "Среден цвят на кожата",
|
||||
"file_upload.fileAbove": "Файла трябва да бъде по-малък от {max}",
|
||||
"gallery.download_file": "Свали файла",
|
||||
"gallery.footer.channel_name": "Споделено в {channelName}",
|
||||
@@ -646,7 +664,7 @@
|
||||
"suggestion.mention.morechannels": "Други канали",
|
||||
"suggestion.mention.nonmembers": "Не е в канала",
|
||||
"suggestion.mention.special": "Специални споменавания",
|
||||
"suggestion.mention.you": "(Вие)",
|
||||
"suggestion.mention.you": " (Вие)",
|
||||
"suggestion.search.direct": "Директно съобщение",
|
||||
"suggestion.search.private": "Частни канали",
|
||||
"suggestion.search.public": "Публични канали",
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
"apps.error.responses.unexpected_error": "Unvorhergesehenen Fehler erhalten.",
|
||||
"apps.error.responses.unexpected_type": "Antworttyp App war nicht erwartet. Antworttyp: {type}.",
|
||||
"apps.error.responses.unknown_type": "Der Typ der Antwort der App wird nicht unterstützt. Typ der Antwort: {type}.",
|
||||
"apps.error.unknown": "Es gab einen unbekannten Fehler.",
|
||||
"apps.error.unknown": "Ein unbekannter Fehler ist aufgetreten.",
|
||||
"apps.suggestion.dynamic.error": "Dynamischer Auswahl Fehler",
|
||||
"apps.suggestion.errors.parser_error": "Fehler beim Parsen",
|
||||
"apps.suggestion.no_dynamic": "Keine Daten bei dynamischen Vorschlägen geliefert",
|
||||
@@ -268,6 +268,7 @@
|
||||
"mobile.channel_list.members": "MITGLIEDER",
|
||||
"mobile.channel_list.not_member": "KEIN MITGLIED",
|
||||
"mobile.channel_list.unreads": "UNGELESENE",
|
||||
"mobile.channel_loader.still_loading": "Versuche weiterhin Inhalte zu laden...",
|
||||
"mobile.channel_members.add_members_alert": "Sie müssen mindestens ein Mitglied auswählen, um es dem Kanal hinzuzufügen.",
|
||||
"mobile.commands.error_title": "Fehler beim Ausführen des Befehls",
|
||||
"mobile.components.error_list.dismiss_all": "Alle verwerfen",
|
||||
@@ -281,6 +282,8 @@
|
||||
"mobile.create_channel.public": "Neuer Öffentlicher Kanal",
|
||||
"mobile.create_post.read_only": "Dieser Kanal ist schreibgeschützt",
|
||||
"mobile.custom_list.no_results": "Keine Ergebnisse",
|
||||
"mobile.custom_status.choose_emoji": "Wähle einen Emoji",
|
||||
"mobile.custom_status.modal_confirm": "Erledigt",
|
||||
"mobile.display_settings.sidebar": "Seitenleiste",
|
||||
"mobile.display_settings.theme": "Motiv",
|
||||
"mobile.document_preview.failed_description": "Es ist ein Fehler beim Öffnen des Dokuments aufgetreten. Bitte stellen Sie sicher, dass Sie einen Betrachter für {fileType} installiert haben und versuchen es erneut.\n",
|
||||
@@ -314,6 +317,8 @@
|
||||
"mobile.file_upload.browse": "Dateien durchsuchen",
|
||||
"mobile.file_upload.camera_photo": "Foto aufnehmen",
|
||||
"mobile.file_upload.camera_video": "Video aufnehmen",
|
||||
"mobile.file_upload.disabled": "Hochladen von Dateien über die mobile App ist deaktiviert. Bitte den System Admin für Details kontaktieren.",
|
||||
"mobile.file_upload.disabled2": "Hochladen von Dateien über die mobile App ist deaktiviert.",
|
||||
"mobile.file_upload.library": "Foto-Bibliothek",
|
||||
"mobile.file_upload.max_warning": "Uploads sind auf maximal fünf Dateien beschränkt.",
|
||||
"mobile.file_upload.unsupportedMimeType": "Nur BMP-, JPG- oder PNG-Bilder sind als Profilbilder zugelassen.",
|
||||
@@ -321,8 +326,9 @@
|
||||
"mobile.files_paste.error_description": "Fehler beim Einfügen der Datei(en). Bitte erneut versuchen.",
|
||||
"mobile.files_paste.error_dismiss": "Verwerfen",
|
||||
"mobile.files_paste.error_title": "Einfügen fehlgeschlagen",
|
||||
"mobile.flagged_posts.empty_description": "Markierungen dienen als Möglichkeit, Nachrichten für eine Wiedervorlage zu markieren. Ihre Markierungen sind persönlich und können nicht von anderen Benutzern gesehen werden.",
|
||||
"mobile.flagged_posts.empty_title": "Keine markierte Nachrichten",
|
||||
"mobile.flagged_posts.empty_description": "Gespeicherte Nachrichten sind nur für Dich sichtbar. Markiere Nachrichten zur Nachverfolgung oder speichere sie für später, indem länger auf die Nachricht gedrückt wird und dann Speichern ausgewählt wird.",
|
||||
"mobile.flagged_posts.empty_title": "Keine gespeicherten Nachrichten",
|
||||
"mobile.gallery.title": "{index} von {total}",
|
||||
"mobile.general.error.title": "Fehler",
|
||||
"mobile.help.title": "Hilfe",
|
||||
"mobile.intro_messages.DM": "Dies ist der Start der Privatnachrichtenverlaufs mit {teammate}. Privatnachrichten und hier geteilte Dateien sind für Personen außerhalb dieses Bereichs nicht sichtbar.",
|
||||
@@ -331,12 +337,14 @@
|
||||
"mobile.ios.photos_permission_denied_description": "Laden Sie Fotos und Videos auf ihre Mattermost-Instanz hoch oder speichern Sie sie auf Ihrem Gerät. Öffnen Sie die Einstellungen, um Mattermost Lese- und Schreibzugriff auf ihre Foto- und Videobibliothek zu gewähren.",
|
||||
"mobile.ios.photos_permission_denied_title": "{applicationName} möchte auf Ihre Fotos zugreifen",
|
||||
"mobile.join_channel.error": "Dem Kanal {displayName} konnte nicht beigetreten werden. Bitte überprüfen Sie ihre Verbindung und versuchen es erneut.",
|
||||
"mobile.link.error.text": "Link kann nicht geöffnet werden.",
|
||||
"mobile.link.error.title": "Fehler",
|
||||
"mobile.loading_channels": "Lade Kanäle...",
|
||||
"mobile.loading_members": "Lade Mitglieder...",
|
||||
"mobile.loading_options": "Lade Optionen...",
|
||||
"mobile.loading_posts": "Lade Nachrichten...",
|
||||
"mobile.login_options.choose_title": "Wählen Sie ihre Anmeldemethode",
|
||||
"mobile.mailTo.error.text": "Email App kann nicht geöffnet werden.",
|
||||
"mobile.mailTo.error.title": "Fehler",
|
||||
"mobile.managed.blocked_by": "Blockiert durch {vendor}",
|
||||
"mobile.managed.exit": "Beenden",
|
||||
@@ -360,6 +368,7 @@
|
||||
"mobile.more_dms.start": "Start",
|
||||
"mobile.more_dms.title": "Neue Konversation",
|
||||
"mobile.more_dms.you": "@{username} - Sie",
|
||||
"mobile.more_messages_button.text": "{count} {isInitialMessage, select, true {new} other {more new}} {count, plural, one {message} other {messages}}",
|
||||
"mobile.notice_mobile_link": "mobile Apps",
|
||||
"mobile.notice_platform_link": "Server",
|
||||
"mobile.notice_text": "Mattermost wird durch die in {platform} und {mobile} verwendete Open-Source-Software ermöglicht.",
|
||||
@@ -382,7 +391,7 @@
|
||||
"mobile.notification_settings.modal_cancel": "ABBRECHEN",
|
||||
"mobile.notification_settings.modal_save": "SPEICHERN",
|
||||
"mobile.notification_settings.ooo_auto_responder": "Automatische Antworten auf Direktnachrichten",
|
||||
"mobile.notification_settings.save_failed_description": "Die Benachrichtigungseinstellungen konnten durch ein Verbindungsproblem nicht gespeichert werden, bitte erneut versuchen.",
|
||||
"mobile.notification_settings.save_failed_description": "Die Benachrichtigungseinstellungen konnten durch ein Verbindungsproblem nicht gespeichert werden. Bitte erneut versuchen.",
|
||||
"mobile.notification_settings.save_failed_title": "Verbindungsproblem",
|
||||
"mobile.notification_settings_mentions.keywords": "Stichwörter",
|
||||
"mobile.notification_settings_mentions.keywordsDescription": "Andere Wörter, die eine Erwähnung auslösen",
|
||||
@@ -400,8 +409,13 @@
|
||||
"mobile.notification_settings_mobile.test": "Schicke mir eine Testbenachrichtigung",
|
||||
"mobile.notification_settings_mobile.test_push": "Dies ist eine Test-Push-Benachrichtigung",
|
||||
"mobile.notification_settings_mobile.vibrate": "Vibrieren",
|
||||
"mobile.oauth.failed_to_login": "Ihre Anmeldeversuch ist fehlgeschlagen. Bitte nochmal versuchen.",
|
||||
"mobile.oauth.failed_to_open_link": "Der Link konnte nicht geöffnet werden. Bitte nochmal versuchen.",
|
||||
"mobile.oauth.failed_to_open_link_no_browser": "Der Link konnte nicht geöffnet werden. Bitte prüfen, ob ein Browser installiert ist.",
|
||||
"mobile.oauth.restart_login": "Anmeldung neu starten",
|
||||
"mobile.oauth.something_wrong": "Etwas ist schief gelaufen",
|
||||
"mobile.oauth.something_wrong.okButon": "OK",
|
||||
"mobile.oauth.switch_to_browser": "Bitte verwenden Sie ihren Browser um den Anmeldeprozess abzuschließen.",
|
||||
"mobile.oauth.try_again": "Erneut versuchen",
|
||||
"mobile.offlineIndicator.connected": "Verbunden",
|
||||
"mobile.offlineIndicator.connecting": "Verbinde...",
|
||||
@@ -411,7 +425,7 @@
|
||||
"mobile.open_unknown_channel.error": "Konnte Kanal nicht beitreten. Bitte setzen Sie den Cache zurück und versuchen es erneut.",
|
||||
"mobile.permission_denied_dismiss": "Nicht erlauben",
|
||||
"mobile.permission_denied_retry": "Einstellungen",
|
||||
"mobile.pinned_posts.empty_description": "Wichtige Elemente anheften durch gedrückt halten einer Nachricht und wählen von \"An Kanal anheften\".",
|
||||
"mobile.pinned_posts.empty_description": "Wichtige Nachrichten anheften, die für den ganzen Kanal sichtbar sind. Länger auf eine Nachricht drücken und \"An Kanal anheften\" auswählen.",
|
||||
"mobile.pinned_posts.empty_title": "Keine angehefteten Nachrichten",
|
||||
"mobile.post.cancel": "Abbrechen",
|
||||
"mobile.post.delete_question": "Möchten Sie diese Nachricht wirklich löschen?",
|
||||
@@ -422,31 +436,39 @@
|
||||
"mobile.post.retry": "Aktualisieren",
|
||||
"mobile.post_info.add_reaction": "Reaktion hinzufügen",
|
||||
"mobile.post_info.copy_text": "Text kopieren",
|
||||
"mobile.post_info.flag": "Markieren",
|
||||
"mobile.post_info.flag": "Speichern",
|
||||
"mobile.post_info.mark_unread": "Als ungelesen markieren",
|
||||
"mobile.post_info.pin": "An Kanal anheften",
|
||||
"mobile.post_info.reply": "Antworten",
|
||||
"mobile.post_info.unflag": "Markierung entfernen",
|
||||
"mobile.post_info.unflag": "Speicherung entfernen",
|
||||
"mobile.post_info.unpin": "Vom Kanal abheften",
|
||||
"mobile.post_pre_header.flagged": "Markiert",
|
||||
"mobile.post_pre_header.flagged": "Gespeichert",
|
||||
"mobile.post_pre_header.pinned": "Angeheftet",
|
||||
"mobile.post_pre_header.pinned_flagged": "Angeheftet und markiert",
|
||||
"mobile.post_pre_header.pinned_flagged": "Angeheftet und gespeichert",
|
||||
"mobile.post_textbox.entire_channel.cancel": "Abbrechen",
|
||||
"mobile.post_textbox.entire_channel.confirm": "Bestätigen",
|
||||
"mobile.post_textbox.entire_channel.message": "Durch die Verwendung von @all oder @channel benachrichtigen Sie {totalMembers, number} {totalMembers, plural, one {Person} other {Personen}}. Sind Sie sicher, dass sie dies tun möchten?",
|
||||
"mobile.post_textbox.entire_channel.message.with_timezones": "Durch die Verwendung von @all oder @channel benachrichtigen Sie {totalMembers, number} {totalMembers, plural, one {Person} other {Personen}} in {timezones, number} {timezones, plural, one {Zeitzone} other {Zeitzonen}}. Sind Sie sicher, dass sie dies tun möchten?",
|
||||
"mobile.post_textbox.entire_channel.title": "Bestätigen Sie das Senden von Benachrichtigungen an den gesamten Kanal",
|
||||
"mobile.post_textbox.groups.title": "Benachrichtigung an Gruppe senden bestätigen",
|
||||
"mobile.post_textbox.multi_group.message.with_timezones": "Wenn Sie {mentions} und {finalMention} verwenden, werden Sie Benachrichtigungen an mindestens {totalMembers} Menschen in {timezones, number} {timezones, plural, one {timezone} other {timezones}}. Sind Sie sich sicher, dass Sie dies tun wollen?",
|
||||
"mobile.post_textbox.multi_group.message.without_timezones": "Beim Verwenden von {mentions} und {finalMention} werden Benachrichtigungen an mindestens {totalMembers} Personen gesendet. Sind Sie sicher, dass Sie das möchten?",
|
||||
"mobile.post_textbox.one_group.message.with_timezones": "Durch Verwendung von {mention} werden Nachrichten an {totalMembers} Personen in {timezones, number} {timezones, plural, one {timezone} other {timezones}} gesendet. Sind Sie sicher?",
|
||||
"mobile.post_textbox.one_group.message.without_timezones": "Durch Verwendung von {mention} werden Benachrichtigungen an {totalMembers} Personen gesendet. Sind Sie sicher?",
|
||||
"mobile.post_textbox.uploadFailedDesc": "Einige Anhänge konnten nicht auf den Server hochgeladen werden. Sind Sie sicher, dass sie die Nachricht abschicken wollen?",
|
||||
"mobile.post_textbox.uploadFailedTitle": "Anhang Fehler",
|
||||
"mobile.posts_view.moreMsg": "Weitere neue Nachrichten oberhalb",
|
||||
"mobile.prepare_file.failed_description": "Beim Erstellen der Datei ist ein Fehler aufgetreten. Bitte nochmal versuchen.\n",
|
||||
"mobile.prepare_file.failed_title": "Erstellen fehlgeschlagen",
|
||||
"mobile.prepare_file.text": "Erstelle",
|
||||
"mobile.privacy_link": "Datenschutzbedingungen",
|
||||
"mobile.public_link.copied": "Öffentlicher Link kopiert",
|
||||
"mobile.push_notification_reply.button": "Senden",
|
||||
"mobile.push_notification_reply.placeholder": "Eine Antwort schreiben...",
|
||||
"mobile.push_notification_reply.title": "Antworten",
|
||||
"mobile.reaction_header.all_emojis": "Alle",
|
||||
"mobile.recent_mentions.empty_description": "Hier werden Nachrichten auftauchen, die ihren Benutzernamen oder andere Wörter enthalten, die Erwähnungen auslösen.",
|
||||
"mobile.recent_mentions.empty_title": "Keine letzten Erwähnungen",
|
||||
"mobile.recent_mentions.empty_description": "Nachrichten, in denen jemand Sie erwähnt oder Wörter verwendet, die Erwähnungen auslösen, werden hier gespeichert.",
|
||||
"mobile.recent_mentions.empty_title": "Bisher keine Erwähnungen",
|
||||
"mobile.rename_channel.display_name_maxLength": "Kanalname muss kürzer als {maxLength, number} Zeichen sein",
|
||||
"mobile.rename_channel.display_name_minLength": "Kanalname muss {minLength, number} oder mehr Zeichen enthalten",
|
||||
"mobile.rename_channel.display_name_required": "Kanalname ist erforderlich",
|
||||
@@ -458,7 +480,7 @@
|
||||
"mobile.reset_status.alert_cancel": "Abbrechen",
|
||||
"mobile.reset_status.alert_ok": "Ok",
|
||||
"mobile.reset_status.title_ooo": "\"Nicht im Büro\" deaktivieren?",
|
||||
"mobile.retry_message": "Aktualisierung der Nachrichten fehlgeschlagen. Nach oben ziehen, um erneut zu versuchen.",
|
||||
"mobile.retry_message": "Aktualisierung der Nachrichten fehlgeschlagen. Nach drücken, um erneut zu versuchen.",
|
||||
"mobile.routes.channelInfo": "Info",
|
||||
"mobile.routes.channelInfo.createdBy": "Erstellt durch {creator} am ",
|
||||
"mobile.routes.channelInfo.delete_channel": "Kanal archivieren",
|
||||
@@ -470,6 +492,7 @@
|
||||
"mobile.routes.channel_members.action_message_confirm": "Sind Sie sicher, dass Sie die ausgewählten Mitglieder vom Kanal löschen möchten?",
|
||||
"mobile.routes.code": "{language}-Code",
|
||||
"mobile.routes.code.noLanguage": "Code",
|
||||
"mobile.routes.custom_status": "Setzte einen Status",
|
||||
"mobile.routes.edit_profile": "Profil bearbeiten",
|
||||
"mobile.routes.login": "Anmelden",
|
||||
"mobile.routes.loginOptions": "Loginauswahl",
|
||||
@@ -484,6 +507,7 @@
|
||||
"mobile.routes.user_profile": "Profil",
|
||||
"mobile.routes.user_profile.edit": "Bearbeiten",
|
||||
"mobile.routes.user_profile.local_time": "LOKALE ZEIT",
|
||||
"mobile.routes.user_profile.organization": "ORGANISATION",
|
||||
"mobile.routes.user_profile.send_message": "Nachricht versenden",
|
||||
"mobile.search.after_modifier_description": "um Nachrichten nach einem spezifischen Datum zu finden",
|
||||
"mobile.search.before_modifier_description": "um Nachrichten vor einem spezifischen Datum zu finden",
|
||||
@@ -502,6 +526,7 @@
|
||||
"mobile.server_link.error.title": "Link-Fehler",
|
||||
"mobile.server_link.unreachable_channel.error": "Der Link gehört zu einem gelöschten Kanal oder einem Kanal auf den Sie keinen Zugriff haben.",
|
||||
"mobile.server_link.unreachable_team.error": "Der Link verweist auf ein gelöschtes Team oder ein Team auf das Sie keinen Zugriff haben.",
|
||||
"mobile.server_link.unreachable_user.error": "Dies Link gehört zu einem gelöschten Benutzer.",
|
||||
"mobile.server_ssl.error.text": "Das Sicherheitszertifikat von {host} ist ungültig.\n\nBitte kontaktieren Sie Ihren System Administrator um die Probleme mit dem Sicherheitszertifikat zu beheben und somit Verbindungen zum Server zu erlauben.",
|
||||
"mobile.server_ssl.error.title": "Ungültiges Sicherheitszertifikat",
|
||||
"mobile.server_upgrade.alert_description": "Diese Serverversion wird nicht mehr unterstützt und es können potentielle Kompatibilitätsprobleme für Nutzer auftreten, die zu Abstürzen oder schwerwiegenden Beeinträchtigungen der Hauptfunktionen der Applikation führen können. Eine Aktualisierung der Serverversion {serverVersion} oder aktueller ist nötig.",
|
||||
@@ -509,6 +534,7 @@
|
||||
"mobile.server_upgrade.dismiss": "Verwerfen",
|
||||
"mobile.server_upgrade.learn_more": "Mehr erfahren",
|
||||
"mobile.server_upgrade.title": "Serveraktualisierung erforderlich",
|
||||
"mobile.server_url.empty": "Bitte eine gültige Server URL eingeben",
|
||||
"mobile.server_url.invalid_format": "URL muss mit http:// oder https:// beginnen",
|
||||
"mobile.session_expired": "Die Sitzung ist abgelaufen: Bitte melden Sie sich an, um weiterhin Benachrichtigungen zu erhalten. Sitzungen für {siteName} sind so konfiguriert, dass sie nach {daysCount, number} {daysCount, plural, one {Tag} other {Tagen}} ablaufen.",
|
||||
"mobile.set_status.away": "Abwesend",
|
||||
@@ -544,9 +570,13 @@
|
||||
"mobile.timezone_settings.manual": "Zeitzone ändern",
|
||||
"mobile.timezone_settings.select": "Zeitzone auswählen",
|
||||
"mobile.tos_link": "Nutzungsbedingungen",
|
||||
"mobile.unsupported_server.message": "Anhänge, Linkvorschauen, Reaktionen und eingebettete Daten werden eventuell nicht korrekt angezeigt. Wenn dieser Zustand anhält, den System Administrator bitten, den Mattermost Server zu aktualisieren.",
|
||||
"mobile.unsupported_server.ok": "OK",
|
||||
"mobile.unsupported_server.title": "Nicht unterstützte Serverversion",
|
||||
"mobile.user.settings.notifications.email.fifteenMinutes": "Alle 15 Minuten",
|
||||
"mobile.user_list.deactivated": "Deaktiviert",
|
||||
"mobile.user_removed.message": "Sie wurden aus dem Kanal entfernt.",
|
||||
"mobile.user_removed.title": "Entfernt aus {channelName}",
|
||||
"mobile.video_playback.failed_description": "Es ist ein Fehler beim Abspielen des Videos aufgetreten.\n",
|
||||
"mobile.video_playback.failed_title": "Videowiedergabe fehlgeschlagen",
|
||||
"mobile.youtube_playback_error.description": "Es ist ein Fehler beim Versuch ein YouTube-Video abzuspielen aufgetreten.\nDetails: {details}",
|
||||
@@ -560,14 +590,20 @@
|
||||
"more_channels.dropdownTitle": "Anzeigen",
|
||||
"more_channels.noMore": "Keine weiteren Kanäle, denen beigetreten werden kann",
|
||||
"more_channels.publicChannels": "Öffentliche Kanäle",
|
||||
"more_channels.sharedChannels": "Geteilte Kanäle",
|
||||
"more_channels.showArchivedChannels": "Anzeigen: Archivierte Kanäle",
|
||||
"more_channels.showPublicChannels": "Anzeigen: Öffentliche Kanäle",
|
||||
"more_channels.showSharedChannels": "Anzeige: Geteilte Kanäle",
|
||||
"more_channels.title": "Weitere Kanäle",
|
||||
"msg_typing.areTyping": "{users} und {last} tippen gerade...",
|
||||
"msg_typing.isTyping": "{user} tippt...",
|
||||
"navbar.channel_drawer.button": "Kanäle und Teams",
|
||||
"navbar.channel_drawer.hint": "Öffnet die Kanal- und Team-Schublade",
|
||||
"navbar.leave": "Kanal verlassen",
|
||||
"navbar.more_options.button": "Mehr Optionen",
|
||||
"navbar.more_options.hint": "Öffnet die weiteren Optionen in der rechten Seitenleiste",
|
||||
"navbar.search.button": "Kanalsuche",
|
||||
"navbar.search.hint": "Öffnet die Kanalsuche",
|
||||
"password_form.title": "Passwort zurücksetzen",
|
||||
"password_send.checkInbox": "Bitte prüfen Sie den Posteingang.",
|
||||
"password_send.description": "Um ihr Passwort zurückzusetzen, geben Sie die E-Mail-Adresse an, die Sie zur Registrierung verwendet haben",
|
||||
@@ -577,6 +613,9 @@
|
||||
"permalink.error.access": "Der dauerhafte Link verweist auf eine gelöschte Nachricht oder einen Kanal auf den Sie keinen Zugriff haben.",
|
||||
"permalink.error.link_not_found": "Link nicht gefunden",
|
||||
"permalink.show_dialog_warn.cancel": "Abbrechen",
|
||||
"permalink.show_dialog_warn.description": "Sie sind dabei, \"{channel}\" beizutreten ohne explizit durch den Kanaladministrator hinzugefügt worden zu sein. Sind Sie sicher, dass Sie diesem privaten Kanal beitreten wollen?",
|
||||
"permalink.show_dialog_warn.join": "Beitreten",
|
||||
"permalink.show_dialog_warn.title": "Privatem Kanal beitreten",
|
||||
"post_body.check_for_out_of_channel_groups_mentions.message": "wurde durch diese Erwähnung nicht benachrichtigt, da sich der Benutzer nicht im Kanal befindet. Er kann dem Kanal nicht hinzugefügt werden, da er nicht Mitglied der verknüpften Gruppen ist. Um ihn zu diesem Kanal hinzuzufügen, muss er zu den verknüpften Gruppen hinzugefügt werden.",
|
||||
"post_body.check_for_out_of_channel_mentions.link.and": " und ",
|
||||
"post_body.check_for_out_of_channel_mentions.link.private": "sie zu diesem privaten Kanal hinzufügen",
|
||||
@@ -598,7 +637,7 @@
|
||||
"search_bar.search": "Suche",
|
||||
"search_header.results": "Suchergebnisse",
|
||||
"search_header.title2": "Letzte Erwähnungen",
|
||||
"search_header.title3": "Markierte Nachrichten",
|
||||
"search_header.title3": "Gespeicherte Nachrichten",
|
||||
"search_item.channelArchived": "Archiviert",
|
||||
"sidebar.channels": "ÖFFENTLICHE KANÄLE",
|
||||
"sidebar.direct": "DIREKTNACHRICHTEN",
|
||||
@@ -619,13 +658,13 @@
|
||||
"suggestion.mention.all": "Benachrichtigt jeden in diesem Kanal",
|
||||
"suggestion.mention.channel": "Benachrichtigt jeden in diesem Kanal",
|
||||
"suggestion.mention.channels": "Meine Kanäle",
|
||||
"suggestion.mention.groups": "Group Mentions",
|
||||
"suggestion.mention.groups": "Gruppenerwähnungen",
|
||||
"suggestion.mention.here": "Benachrichtigt jeden in diesem Kanal",
|
||||
"suggestion.mention.members": "Kanalmitglieder",
|
||||
"suggestion.mention.morechannels": "Andere Kanäle",
|
||||
"suggestion.mention.nonmembers": "Nicht im Kanal",
|
||||
"suggestion.mention.special": "Spezielle Erwähnungen",
|
||||
"suggestion.mention.you": "(Sie)",
|
||||
"suggestion.mention.you": " (Sie)",
|
||||
"suggestion.search.direct": "Direktnachricht",
|
||||
"suggestion.search.private": "Private Kanäle",
|
||||
"suggestion.search.public": "Öffentliche Kanäle",
|
||||
@@ -649,6 +688,7 @@
|
||||
"user.settings.general.lastName": "Nachname",
|
||||
"user.settings.general.nickname": "Spitzname",
|
||||
"user.settings.general.position": "Position",
|
||||
"user.settings.general.status": "Status",
|
||||
"user.settings.general.username": "Benutzername",
|
||||
"user.settings.modal.display": "Anzeige",
|
||||
"user.settings.modal.notifications": "Benachrichtigungen",
|
||||
|
||||
@@ -664,7 +664,7 @@
|
||||
"suggestion.mention.morechannels": "Other Channels",
|
||||
"suggestion.mention.nonmembers": "Not in Channel",
|
||||
"suggestion.mention.special": "Special Mentions",
|
||||
"suggestion.mention.you": "(you)",
|
||||
"suggestion.mention.you": " (you)",
|
||||
"suggestion.search.direct": "Direct Messages",
|
||||
"suggestion.search.private": "Private Channels",
|
||||
"suggestion.search.public": "Public Channels",
|
||||
|
||||
@@ -664,7 +664,7 @@
|
||||
"suggestion.mention.morechannels": "Otros Canales",
|
||||
"suggestion.mention.nonmembers": "No en el Canal",
|
||||
"suggestion.mention.special": "Menciones especiales",
|
||||
"suggestion.mention.you": "(tú)",
|
||||
"suggestion.mention.you": " (tu)",
|
||||
"suggestion.search.direct": "Mensajes Directos",
|
||||
"suggestion.search.private": "Canales Privados",
|
||||
"suggestion.search.public": "Canales Públicos",
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
{
|
||||
"about.date": "Kiadás dátuma:",
|
||||
"about.date": "Build dátuma:",
|
||||
"about.enterpriseEditionLearn": "Tudjon meg többet az Enterprise kiadásról: ",
|
||||
"about.enterpriseEditionSt": "Modern kommunikáció a tűzfalad mögött.",
|
||||
"about.enterpriseEditionSt": "Modern kommunikáció a tűzfalad mögül.",
|
||||
"about.enterpriseEditione1": "Enterprise Kiadás",
|
||||
"about.hash": "Build ujjlenyomata:",
|
||||
"about.hashee": "EE build ujjlenyomata:",
|
||||
"about.teamEditionLearn": "Csatlakozz a Mattermost közösséghez a ",
|
||||
"about.teamEditionLearn": "Csatlakozz a Mattermost közösséghez itt: ",
|
||||
"about.teamEditionSt": "Az összes csapaton belüli kommunikáció egy helyen, azonnal kereshetően és bárhonnan hozzáférhetően.",
|
||||
"about.teamEditiont0": "Team kiadás",
|
||||
"about.teamEditiont1": "Enterprise kiadás",
|
||||
"about.title": "Névjegy {appTitle}",
|
||||
"announcment_banner.dont_show_again": "Ne mutassa még egyszer",
|
||||
"about.title": "{appTitle} névjegye",
|
||||
"announcment_banner.dont_show_again": "Ne mutassa újra",
|
||||
"api.channel.add_member.added": "{addedUsername} hozzá lett adva a csatornához {username} által.",
|
||||
"apps.error": "Hiba: {error}",
|
||||
"apps.error.command.field_missing": "Hiányzó kötelező mezők: `{fieldName}`.",
|
||||
"apps.error.command.unknown_channel": "Ismeretlen csatorna a `{fieldName}` mezőhöz: `{option}`.",
|
||||
"apps.error.command.unknown_option": "Ismeretlen opció a `{fieldName}`mezőhöz: `{option}`.",
|
||||
"apps.error.command.unknown_user": "Ismeretlen felhasználó a `{fieldName}`mezőhöz: `{option}`.",
|
||||
"apps.error.form.no_call": "`call` nincs meghatározva.",
|
||||
"apps.error.form.no_form": "`form` nincs meghatározva.",
|
||||
"apps.error.form.no_lookup_call": "performLookupCall props.call nincs meghatározva",
|
||||
"apps.error.form.refresh": "Hiba történt a kiválasztás mezők letöltésekor. Lépjen kapcsolatba az alkalmazás fejlesztővel. Részletek: {details}",
|
||||
"apps.error.form.no_call": "`call` nincs megadva.",
|
||||
"apps.error.form.no_form": "`form` nincs megadva.",
|
||||
"apps.error.form.no_lookup_call": "performLookupCall props.call nincs megadva",
|
||||
"apps.error.form.refresh": "Hiba történt a kiválasztás mezők letöltésekor. Lépjen kapcsolatba az alkalmazás fejlesztőjével. Részletek: {details}",
|
||||
"apps.error.form.refresh_no_refresh": "Frissítést hívott egy frissítés nélküli mezőn.",
|
||||
"apps.error.form.submit.pretext": "Hiba történt a modál elküldése során. Lépjen kapcsolatba az alkalmazás fejlesztőjével. Részletek: {details}",
|
||||
"apps.error.lookup.error_preparing_request": "Hiba a keresési kérelem előkészítése során: {errorMessage}",
|
||||
@@ -331,7 +331,7 @@
|
||||
"mobile.gallery.title": "{index}/{total}",
|
||||
"mobile.general.error.title": "Hiba",
|
||||
"mobile.help.title": "Súgó",
|
||||
"mobile.intro_messages.DM": "Ezzel megkezdődik a (z) {teammate} közvetlen üzeneteinek előzménye. Az itt megosztott közvetlen üzenetek és fájlok nem jelennek meg az ezen a területen kívüli személyek.",
|
||||
"mobile.intro_messages.DM": "Ezzel megkezdődik a(z) {teammate} közvetlen üzeneteinek előzménye. Az itt megosztott közvetlen üzenetek és fájlok nem jelennek meg az ezen a területen kívüli személyek számára.",
|
||||
"mobile.intro_messages.default_message": "Ez az első csatorna, amelyet a csapattársak látnak, amikor feliratkoznak - használja úgy, mint egy információs csatornát.",
|
||||
"mobile.intro_messages.default_welcome": "Üdvözöllek a {name}!",
|
||||
"mobile.ios.photos_permission_denied_description": "Töltsön fel fényképeket és videókat a Mattermost szerverére, vagy mentse azokat a készülékére. Nyissa meg a Beállításokat az olvasási és írási engedély biztosításához a fotó- és videotárhoz.",
|
||||
@@ -357,7 +357,7 @@
|
||||
"mobile.markdown.code.copy_code": "Kód másolása",
|
||||
"mobile.markdown.code.plusMoreLines": "+{count, number} több {count, plural, one {line} other {lines}}",
|
||||
"mobile.markdown.image.too_large": "A kép meghaladja a maximális méretét (maximum szélesség: {maxWidth}, maximum magasság {maxHeight}):",
|
||||
"mobile.markdown.link.copy_url": "Link másolása",
|
||||
"mobile.markdown.link.copy_url": "URL másolása",
|
||||
"mobile.mention.copy_mention": "Említés másolása",
|
||||
"mobile.message_length.message": "A jelenlegi üzenete túl hosszú. Jelenlegi karakterek száma: {count}/{max}",
|
||||
"mobile.message_length.message_split_left": "Az üzenet meghaladja a karakterkorlátot",
|
||||
@@ -664,7 +664,7 @@
|
||||
"suggestion.mention.morechannels": "Egyéb csatornák",
|
||||
"suggestion.mention.nonmembers": "Nincs a csatornában",
|
||||
"suggestion.mention.special": "Külön említések",
|
||||
"suggestion.mention.you": "(Ön)",
|
||||
"suggestion.mention.you": " (te)",
|
||||
"suggestion.search.direct": "Közvetlen üzenetek",
|
||||
"suggestion.search.private": "Privát csatornák",
|
||||
"suggestion.search.public": "Nyilvános csatornák",
|
||||
|
||||
@@ -664,7 +664,7 @@
|
||||
"suggestion.mention.morechannels": "他のチャンネル",
|
||||
"suggestion.mention.nonmembers": "チャンネルにいません",
|
||||
"suggestion.mention.special": "特殊な誰かについての投稿",
|
||||
"suggestion.mention.you": "(あなた)",
|
||||
"suggestion.mention.you": " (あなた)",
|
||||
"suggestion.search.direct": "ダイレクトメッセージ",
|
||||
"suggestion.search.private": "非公開チャンネル",
|
||||
"suggestion.search.public": "公開チャンネル",
|
||||
|
||||
@@ -664,7 +664,7 @@
|
||||
"suggestion.mention.morechannels": "Andere Kanalen",
|
||||
"suggestion.mention.nonmembers": "Niet in kanaal",
|
||||
"suggestion.mention.special": "Speciale Vermeldingen",
|
||||
"suggestion.mention.you": "(jij)",
|
||||
"suggestion.mention.you": " (jij)",
|
||||
"suggestion.search.direct": "Privé bericht",
|
||||
"suggestion.search.private": "Privé-kanalen",
|
||||
"suggestion.search.public": "Publieke kanalen",
|
||||
|
||||
@@ -664,7 +664,7 @@
|
||||
"suggestion.mention.morechannels": "Diğer kanallar",
|
||||
"suggestion.mention.nonmembers": "Kanalda değil",
|
||||
"suggestion.mention.special": "Özel anmalar",
|
||||
"suggestion.mention.you": "(siz)",
|
||||
"suggestion.mention.you": " (siz)",
|
||||
"suggestion.search.direct": "Doğrudan iletiler",
|
||||
"suggestion.search.private": "Özel kanallar",
|
||||
"suggestion.search.public": "Herkese Açık Kanallar",
|
||||
|
||||
@@ -664,7 +664,7 @@
|
||||
"suggestion.mention.morechannels": "其他频道",
|
||||
"suggestion.mention.nonmembers": "不在频道中",
|
||||
"suggestion.mention.special": "特别提及",
|
||||
"suggestion.mention.you": "(您)",
|
||||
"suggestion.mention.you": " (您)",
|
||||
"suggestion.search.direct": "私信",
|
||||
"suggestion.search.private": "私有频道",
|
||||
"suggestion.search.public": "公共频道",
|
||||
|
||||
BIN
assets/base/images/emojis/mattermost.png
Normal file
BIN
assets/base/images/emojis/mattermost.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.0 KiB |
@@ -2,12 +2,12 @@ GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
CFPropertyList (3.0.3)
|
||||
addressable (2.7.0)
|
||||
addressable (2.8.0)
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
artifactory (3.0.15)
|
||||
atomos (0.1.3)
|
||||
aws-eventstream (1.1.1)
|
||||
aws-partitions (1.471.0)
|
||||
aws-partitions (1.474.0)
|
||||
aws-sdk-core (3.115.0)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
aws-partitions (~> 1, >= 1.239.0)
|
||||
@@ -35,13 +35,15 @@ GEM
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
dotenv (2.7.6)
|
||||
emoji_regex (3.2.2)
|
||||
excon (0.82.0)
|
||||
faraday (1.4.3)
|
||||
excon (0.84.0)
|
||||
faraday (1.5.0)
|
||||
faraday-em_http (~> 1.0)
|
||||
faraday-em_synchrony (~> 1.0)
|
||||
faraday-excon (~> 1.1)
|
||||
faraday-httpclient (~> 1.0.1)
|
||||
faraday-net_http (~> 1.0)
|
||||
faraday-net_http_persistent (~> 1.1)
|
||||
faraday-patron (~> 1.0)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
ruby2_keywords (>= 0.0.4)
|
||||
faraday-cookie_jar (0.0.7)
|
||||
@@ -50,12 +52,14 @@ GEM
|
||||
faraday-em_http (1.0.0)
|
||||
faraday-em_synchrony (1.0.0)
|
||||
faraday-excon (1.1.0)
|
||||
faraday-httpclient (1.0.1)
|
||||
faraday-net_http (1.0.1)
|
||||
faraday-net_http_persistent (1.1.0)
|
||||
faraday-patron (1.0.0)
|
||||
faraday_middleware (1.0.0)
|
||||
faraday (~> 1.0)
|
||||
fastimage (2.2.4)
|
||||
fastlane (2.186.0)
|
||||
fastlane (2.187.0)
|
||||
CFPropertyList (>= 2.3, < 4.0.0)
|
||||
addressable (>= 2.3, < 3.0.0)
|
||||
artifactory (~> 3.0)
|
||||
@@ -99,31 +103,30 @@ GEM
|
||||
fastlane-plugin-find_replace_string (0.1.0)
|
||||
fastlane-plugin-versioning_android (0.1.0)
|
||||
gh_inspector (1.1.3)
|
||||
google-apis-androidpublisher_v3 (0.6.0)
|
||||
google-apis-core (~> 0.1)
|
||||
google-apis-core (0.3.0)
|
||||
google-apis-androidpublisher_v3 (0.8.0)
|
||||
google-apis-core (>= 0.4, < 2.a)
|
||||
google-apis-core (0.4.0)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
googleauth (~> 0.14)
|
||||
httpclient (>= 2.8.1, < 3.0)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
httpclient (>= 2.8.1, < 3.a)
|
||||
mini_mime (~> 1.0)
|
||||
representable (~> 3.0)
|
||||
retriable (>= 2.0, < 4.0)
|
||||
retriable (>= 2.0, < 4.a)
|
||||
rexml
|
||||
signet (~> 0.14)
|
||||
webrick
|
||||
google-apis-iamcredentials_v1 (0.4.0)
|
||||
google-apis-core (~> 0.1)
|
||||
google-apis-playcustomapp_v1 (0.3.0)
|
||||
google-apis-core (~> 0.1)
|
||||
google-apis-storage_v1 (0.4.0)
|
||||
google-apis-core (~> 0.1)
|
||||
google-apis-iamcredentials_v1 (0.6.0)
|
||||
google-apis-core (>= 0.4, < 2.a)
|
||||
google-apis-playcustomapp_v1 (0.5.0)
|
||||
google-apis-core (>= 0.4, < 2.a)
|
||||
google-apis-storage_v1 (0.6.0)
|
||||
google-apis-core (>= 0.4, < 2.a)
|
||||
google-cloud-core (1.6.0)
|
||||
google-cloud-env (~> 1.0)
|
||||
google-cloud-errors (~> 1.0)
|
||||
google-cloud-env (1.5.0)
|
||||
faraday (>= 0.17.3, < 2.0)
|
||||
google-cloud-errors (1.1.0)
|
||||
google-cloud-storage (1.32.0)
|
||||
google-cloud-storage (1.34.0)
|
||||
addressable (~> 2.5)
|
||||
digest-crc (~> 0.4)
|
||||
google-apis-iamcredentials_v1 (~> 0.1)
|
||||
@@ -160,7 +163,7 @@ GEM
|
||||
plist (3.6.0)
|
||||
public_suffix (4.0.6)
|
||||
racc (1.5.2)
|
||||
rake (13.0.3)
|
||||
rake (13.0.4)
|
||||
representable (3.1.1)
|
||||
declarative (< 0.1.0)
|
||||
trailblazer-option (>= 0.1.1, < 0.2.0)
|
||||
@@ -169,7 +172,7 @@ GEM
|
||||
rexml (3.2.5)
|
||||
rouge (2.0.7)
|
||||
ruby2_keywords (0.0.4)
|
||||
rubyzip (2.3.0)
|
||||
rubyzip (2.3.2)
|
||||
security (0.1.3)
|
||||
signet (0.15.0)
|
||||
addressable (~> 2.3)
|
||||
@@ -195,12 +198,13 @@ GEM
|
||||
unicode-display_width (1.7.0)
|
||||
webrick (1.7.0)
|
||||
word_wrap (1.0.0)
|
||||
xcodeproj (1.19.0)
|
||||
xcodeproj (1.20.0)
|
||||
CFPropertyList (>= 2.3.3, < 4.0)
|
||||
atomos (~> 0.1.3)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
colored2 (~> 3.1)
|
||||
nanaimo (~> 0.3.0)
|
||||
rexml (~> 3.2.4)
|
||||
xcpretty (0.3.0)
|
||||
rouge (~> 2.0.7)
|
||||
xcpretty-travis-formatter (1.0.1)
|
||||
|
||||
@@ -912,7 +912,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CURRENT_PROJECT_VERSION = 360;
|
||||
CURRENT_PROJECT_VERSION = 364;
|
||||
DEAD_CODE_STRIPPING = NO;
|
||||
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
|
||||
ENABLE_BITCODE = NO;
|
||||
@@ -954,7 +954,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CURRENT_PROJECT_VERSION = 360;
|
||||
CURRENT_PROJECT_VERSION = 364;
|
||||
DEAD_CODE_STRIPPING = NO;
|
||||
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
|
||||
ENABLE_BITCODE = NO;
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.44.1</string>
|
||||
<string>1.45.1</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
@@ -37,7 +37,7 @@
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>360</string>
|
||||
<string>364</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>XPC!</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.44.1</string>
|
||||
<string>1.45.1</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>360</string>
|
||||
<string>364</string>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>XPC!</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.44.1</string>
|
||||
<string>1.45.1</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>360</string>
|
||||
<string>364</string>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
|
||||
@@ -693,7 +693,7 @@ SPEC CHECKSUMS:
|
||||
BVLinearGradient: e3aad03778a456d77928f594a649e96995f1c872
|
||||
DoubleConversion: cf9b38bf0b2d048436d9a82ad2abe1404f11e7de
|
||||
FBLazyVector: e686045572151edef46010a6f819ade377dfeb4b
|
||||
FBReactNativeSpec: 74185edd6e8e5cb35512747bd840f8f9ad2381ff
|
||||
FBReactNativeSpec: cef0cc6d50abc92e8cf52f140aa22b5371cfec0b
|
||||
glog: 73c2498ac6884b13ede40eda8228cb1eee9d9d62
|
||||
jail-monkey: 01cd0a75aa1034d08fd851869e6e6c3b063242d7
|
||||
libwebp: e90b9c01d99205d03b6bb8f2c8c415e5a4ef66f0
|
||||
|
||||
2300
package-lock.json
generated
2300
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mattermost-mobile",
|
||||
"version": "1.44.1",
|
||||
"version": "1.45.1",
|
||||
"description": "Mattermost Mobile with React Native",
|
||||
"repository": "git@github.com:mattermost/mattermost-mobile.git",
|
||||
"author": "Mattermost, Inc.",
|
||||
@@ -19,8 +19,8 @@
|
||||
"@rudderstack/rudder-sdk-react-native": "1.0.10",
|
||||
"@sentry/react-native": "2.5.2",
|
||||
"analytics-react-native": "1.2.0",
|
||||
"commonmark": "github:mattermost/commonmark.js#d716e1c89e0a6721051df7bc74ad7683e1ae438f",
|
||||
"commonmark-react-renderer": "github:mattermost/commonmark-react-renderer#81af294317ebe19b5cc195d7fbc4f4a58177854c",
|
||||
"commonmark": "0.30.0",
|
||||
"commonmark-react-renderer": "4.3.5",
|
||||
"deep-equal": "2.0.5",
|
||||
"deepmerge": "4.2.2",
|
||||
"emoji-regex": "9.2.2",
|
||||
|
||||
6470
patches/commonmark+0.30.0.patch
Normal file
6470
patches/commonmark+0.30.0.patch
Normal file
File diff suppressed because it is too large
Load Diff
351
patches/commonmark-react-renderer+4.3.5.patch
Normal file
351
patches/commonmark-react-renderer+4.3.5.patch
Normal file
@@ -0,0 +1,351 @@
|
||||
diff --git a/node_modules/commonmark-react-renderer/src/commonmark-react-renderer.js b/node_modules/commonmark-react-renderer/src/commonmark-react-renderer.js
|
||||
index 91b0001..05b80fa 100644
|
||||
--- a/node_modules/commonmark-react-renderer/src/commonmark-react-renderer.js
|
||||
+++ b/node_modules/commonmark-react-renderer/src/commonmark-react-renderer.js
|
||||
@@ -12,7 +12,12 @@ var typeAliases = {
|
||||
htmlblock: 'html_block',
|
||||
htmlinline: 'html_inline',
|
||||
codeblock: 'code_block',
|
||||
- hardbreak: 'linebreak'
|
||||
+ hardbreak: 'linebreak',
|
||||
+ atmention: 'at_mention',
|
||||
+ channellink: 'channel_link',
|
||||
+ editedindicator: 'edited_indicator',
|
||||
+ tableRow: 'table_row',
|
||||
+ tableCell: 'table_cell'
|
||||
};
|
||||
|
||||
var defaultRenderers = {
|
||||
@@ -24,6 +29,7 @@ var defaultRenderers = {
|
||||
link: 'a',
|
||||
paragraph: 'p',
|
||||
strong: 'strong',
|
||||
+ del: 'del',
|
||||
thematic_break: 'hr', // eslint-disable-line camelcase
|
||||
|
||||
html_block: HtmlRenderer, // eslint-disable-line camelcase
|
||||
@@ -52,7 +58,71 @@ var defaultRenderers = {
|
||||
},
|
||||
|
||||
text: null,
|
||||
- softbreak: null
|
||||
+ softbreak: null,
|
||||
+
|
||||
+ at_mention: function AtMention(props) {
|
||||
+ var newProps = getCoreProps(props);
|
||||
+ if (props.username) {
|
||||
+ props['data-mention-name'] = props.username;
|
||||
+ }
|
||||
+
|
||||
+ return createElement('span', newProps, props.children);
|
||||
+ },
|
||||
+ channel_link: function ChannelLink(props) {
|
||||
+ var newProps = getCoreProps(props);
|
||||
+ if (props.channelName) {
|
||||
+ props['data-channel-name'] = props.channelName;
|
||||
+ }
|
||||
+
|
||||
+ return createElement('span', newProps, props.children);
|
||||
+ },
|
||||
+ emoji: function Emoji(props) {
|
||||
+ var newProps = getCoreProps(props);
|
||||
+ if (props.emojiName) {
|
||||
+ props['data-emoji-name'] = props.emojiName;
|
||||
+ }
|
||||
+
|
||||
+ return createElement('span', newProps, props.children);
|
||||
+ },
|
||||
+ edited_indicator: null,
|
||||
+ hashtag: function Hashtag(props) {
|
||||
+ var newProps = getCoreProps(props);
|
||||
+ if (props.hashtag) {
|
||||
+ props['data-hashtag'] = props.hashtag;
|
||||
+ }
|
||||
+
|
||||
+ return createElement('span', newProps, props.children);
|
||||
+ },
|
||||
+ mention_highlight: function MentionHighlight(props) {
|
||||
+ var newProps = getCoreProps(props);
|
||||
+ newProps['data-mention-highlight'] = 'true';
|
||||
+ return createElement('span', newProps, props.children);
|
||||
+ },
|
||||
+ search_highlight: function SearchHighlight(props) {
|
||||
+ var newProps = getCoreProps(props);
|
||||
+ newProps['data-search-highlight'] = 'true';
|
||||
+ return createElement('span', newProps, props.children);
|
||||
+ },
|
||||
+
|
||||
+ table: function Table(props) {
|
||||
+ var childrenArray = React.Children.toArray(props.children);
|
||||
+
|
||||
+ var children = [createElement('thead', {'key': 'thead'}, childrenArray.slice(0, 1))];
|
||||
+ if (childrenArray.length > 1) {
|
||||
+ children.push(createElement('tbody', {'key': 'tbody'}, childrenArray.slice(1)));
|
||||
+ }
|
||||
+
|
||||
+ return createElement('table', getCoreProps(props), children);
|
||||
+ },
|
||||
+ table_row: 'tr',
|
||||
+ table_cell: function TableCell(props) {
|
||||
+ var newProps = getCoreProps(props);
|
||||
+ if (props.align) {
|
||||
+ newProps.className = 'align-' + props.align;
|
||||
+ }
|
||||
+
|
||||
+ return createElement('td', newProps, props.children);
|
||||
+ }
|
||||
};
|
||||
|
||||
var coreTypes = Object.keys(defaultRenderers);
|
||||
@@ -147,7 +217,7 @@ function flattenPosition(pos) {
|
||||
}
|
||||
|
||||
// For some nodes, we want to include more props than for others
|
||||
-function getNodeProps(node, key, opts, renderer) {
|
||||
+function getNodeProps(node, key, opts, renderer, context) {
|
||||
var props = { key: key }, undef;
|
||||
|
||||
// `sourcePos` is true if the user wants source information (line/column info from markdown source)
|
||||
@@ -194,16 +264,49 @@ function getNodeProps(node, key, opts, renderer) {
|
||||
|
||||
// Commonmark treats image description as children. We just want the text
|
||||
props.alt = node.react.children.join('');
|
||||
- node.react.children = undef;
|
||||
break;
|
||||
case 'list':
|
||||
props.start = node.listStart;
|
||||
props.type = node.listType;
|
||||
props.tight = node.listTight;
|
||||
break;
|
||||
+ case 'at_mention':
|
||||
+ props.mentionName = node.mentionName;
|
||||
+ break;
|
||||
+ case 'channel_link':
|
||||
+ props.channelName = node.channelName;
|
||||
+ break;
|
||||
+ case 'emoji':
|
||||
+ props.emojiName = node.emojiName;
|
||||
+ props.literal = node.literal;
|
||||
+ break;
|
||||
+ case 'hashtag':
|
||||
+ props.hashtag = node.hashtag;
|
||||
+ break;
|
||||
+ case 'paragraph':
|
||||
+ props.first = !(node._prev && node._prev.type === 'paragraph');
|
||||
+ props.last = !(node._next && node._next.type === 'paragraph');
|
||||
+ break;
|
||||
+ case 'edited_indicator':
|
||||
+ break;
|
||||
+ case 'table':
|
||||
+ props.numRows = countRows(node);
|
||||
+ props.numColumns = countColumns(node);
|
||||
+ break;
|
||||
+ case 'table_row':
|
||||
+ props.isHeading = node.isHeading;
|
||||
+ break;
|
||||
+ case 'table_cell':
|
||||
+ props.isHeading = node.isHeading;
|
||||
+ props.align = node.align;
|
||||
+ break;
|
||||
default:
|
||||
}
|
||||
|
||||
+ if (opts.getExtraPropsForNode) {
|
||||
+ props = Object.assign(props, opts.getExtraPropsForNode(node));
|
||||
+ }
|
||||
+
|
||||
if (typeof renderer !== 'string') {
|
||||
props.literal = node.literal;
|
||||
}
|
||||
@@ -213,9 +316,29 @@ function getNodeProps(node, key, opts, renderer) {
|
||||
props.children = children.reduce(reduceChildren, []) || null;
|
||||
}
|
||||
|
||||
+ props.context = context.slice();
|
||||
+
|
||||
return props;
|
||||
}
|
||||
|
||||
+function countChildren(node) {
|
||||
+ var count = 0;
|
||||
+
|
||||
+ for (var child = node.firstChild; child; child = child.next) {
|
||||
+ count += 1;
|
||||
+ }
|
||||
+
|
||||
+ return count;
|
||||
+}
|
||||
+
|
||||
+function countRows(table) {
|
||||
+ return countChildren(table);
|
||||
+}
|
||||
+
|
||||
+function countColumns(table) {
|
||||
+ return countChildren(table.firstChild);
|
||||
+}
|
||||
+
|
||||
function getPosition(node) {
|
||||
if (!node) {
|
||||
return null;
|
||||
@@ -238,26 +361,23 @@ function renderNodes(block) {
|
||||
transformLinkUri: this.transformLinkUri,
|
||||
transformImageUri: this.transformImageUri,
|
||||
softBreak: this.softBreak,
|
||||
- linkTarget: this.linkTarget
|
||||
+ linkTarget: this.linkTarget,
|
||||
+ getExtraPropsForNode: this.getExtraPropsForNode
|
||||
};
|
||||
|
||||
- var e, node, entering, leaving, type, doc, key, nodeProps, prevPos, prevIndex = 0;
|
||||
+ var e;
|
||||
+ var doc;
|
||||
+ var context = [];
|
||||
+ var index = 0;
|
||||
while ((e = walker.next())) {
|
||||
- var pos = getPosition(e.node.sourcepos ? e.node : e.node.parent);
|
||||
- if (prevPos === pos) {
|
||||
- key = pos + prevIndex;
|
||||
- prevIndex++;
|
||||
- } else {
|
||||
- key = pos;
|
||||
- prevIndex = 0;
|
||||
- }
|
||||
+ var key = String(index);
|
||||
+ index += 1;
|
||||
|
||||
- prevPos = pos;
|
||||
- entering = e.entering;
|
||||
- leaving = !entering;
|
||||
- node = e.node;
|
||||
- type = normalizeTypeName(node.type);
|
||||
- nodeProps = null;
|
||||
+ var entering = e.entering;
|
||||
+ var leaving = !entering;
|
||||
+ var node = e.node;
|
||||
+ var type = normalizeTypeName(node.type);
|
||||
+ var nodeProps = null;
|
||||
|
||||
// If we have not assigned a document yet, assume the current node is just that
|
||||
if (!doc) {
|
||||
@@ -270,7 +390,7 @@ function renderNodes(block) {
|
||||
}
|
||||
|
||||
// In HTML, we don't want paragraphs inside of list items
|
||||
- if (type === 'paragraph' && isGrandChildOfList(node)) {
|
||||
+ if (!this.renderParagraphsInLists && type === 'paragraph' && isGrandChildOfList(node)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -289,7 +409,7 @@ function renderNodes(block) {
|
||||
if (this.allowNode && (isCompleteParent || !node.isContainer)) {
|
||||
var nodeChildren = isCompleteParent ? node.react.children : [];
|
||||
|
||||
- nodeProps = getNodeProps(node, key, propOptions, renderer);
|
||||
+ nodeProps = getNodeProps(node, key, propOptions, renderer, context);
|
||||
disallowedByUser = !this.allowNode({
|
||||
type: pascalCase(type),
|
||||
renderer: this.renderers[type],
|
||||
@@ -298,6 +418,30 @@ function renderNodes(block) {
|
||||
});
|
||||
}
|
||||
|
||||
+ if (node.isContainer) {
|
||||
+ var contextType = node.type;
|
||||
+ if (node.level) {
|
||||
+ contextType = node.type + node.level;
|
||||
+ } else if (node.type === 'table_row' && node.parent.firstChild === node) {
|
||||
+ contextType = 'table_header_row';
|
||||
+ } else {
|
||||
+ contextType = node.type;
|
||||
+ }
|
||||
+
|
||||
+ if (entering) {
|
||||
+ context.push(contextType);
|
||||
+ } else {
|
||||
+ var popped = context.pop();
|
||||
+
|
||||
+ if (!popped) {
|
||||
+ throw new Error('Attempted to pop empty stack');
|
||||
+ } else if (!popped === contextType) {
|
||||
+ throw new Error('Popped context of type `' + pascalCase(popped) +
|
||||
+ '` when expecting context of type `' + pascalCase(contextType) + '`');
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
if (!isDocument && (disallowedByUser || disallowedByConfig)) {
|
||||
if (!this.unwrapDisallowed && entering && node.isContainer) {
|
||||
walker.resumeAt(node, false);
|
||||
@@ -313,15 +457,25 @@ function renderNodes(block) {
|
||||
);
|
||||
}
|
||||
|
||||
- if (node.isContainer && entering) {
|
||||
+ if (context.length > this.maxDepth) {
|
||||
+ // Do nothing, we should not regularly be nested this deeply and we don't want to cause React to
|
||||
+ // overflow the stack
|
||||
+ } else if (node.isContainer && entering) {
|
||||
node.react = {
|
||||
component: renderer,
|
||||
props: {},
|
||||
children: []
|
||||
};
|
||||
} else {
|
||||
- var childProps = nodeProps || getNodeProps(node, key, propOptions, renderer);
|
||||
- if (renderer) {
|
||||
+ var childProps = nodeProps || getNodeProps(node, key, propOptions, renderer, context);
|
||||
+ if (renderer === ReactRenderer.forwardChildren) {
|
||||
+ if (childProps.children) {
|
||||
+ for (var i = 0; i < childProps.children.length; i++) {
|
||||
+ var child = childProps.children[i];
|
||||
+ addChild(node, child);
|
||||
+ }
|
||||
+ }
|
||||
+ } else if (renderer) {
|
||||
childProps = typeof renderer === 'string'
|
||||
? childProps
|
||||
: assign(childProps, {nodeKey: childProps.key});
|
||||
@@ -341,6 +495,10 @@ function renderNodes(block) {
|
||||
}
|
||||
}
|
||||
|
||||
+ if (context.length !== 0) {
|
||||
+ throw new Error('Expected context to be empty after rendering, but has `' + context.join(', ') + '`');
|
||||
+ }
|
||||
+
|
||||
return doc.react.children;
|
||||
}
|
||||
|
||||
@@ -401,21 +559,31 @@ function ReactRenderer(options) {
|
||||
renderers: assign({}, defaultRenderers, normalizeRenderers(opts.renderers)),
|
||||
escapeHtml: Boolean(opts.escapeHtml),
|
||||
skipHtml: Boolean(opts.skipHtml),
|
||||
+ renderParagraphsInLists: Boolean(opts.renderParagraphsInLists),
|
||||
transformLinkUri: linkFilter,
|
||||
transformImageUri: imageFilter,
|
||||
allowNode: opts.allowNode,
|
||||
allowedTypes: allowedTypes,
|
||||
unwrapDisallowed: Boolean(opts.unwrapDisallowed),
|
||||
render: renderNodes,
|
||||
- linkTarget: opts.linkTarget || false
|
||||
+ linkTarget: opts.linkTarget || false,
|
||||
+ maxDepth: opts.maxDepth || 30,
|
||||
+ getExtraPropsForNode: opts.getExtraPropsForNode
|
||||
};
|
||||
}
|
||||
|
||||
+function forwardChildren(props) {
|
||||
+ return props.children;
|
||||
+}
|
||||
+
|
||||
ReactRenderer.uriTransformer = defaultLinkUriFilter;
|
||||
ReactRenderer.types = coreTypes.map(pascalCase);
|
||||
ReactRenderer.renderers = coreTypes.reduce(function(renderers, type) {
|
||||
renderers[pascalCase(type)] = defaultRenderers[type];
|
||||
return renderers;
|
||||
}, {});
|
||||
+ReactRenderer.countRows = countRows;
|
||||
+ReactRenderer.countColumns = countColumns;
|
||||
+ReactRenderer.forwardChildren = forwardChildren;
|
||||
|
||||
module.exports = ReactRenderer;
|
||||
@@ -1,3 +1,24 @@
|
||||
diff --git a/node_modules/react-native/Libraries/Components/ScrollView/ScrollView.js b/node_modules/react-native/Libraries/Components/ScrollView/ScrollView.js
|
||||
index a069a24..a21fa38 100644
|
||||
--- a/node_modules/react-native/Libraries/Components/ScrollView/ScrollView.js
|
||||
+++ b/node_modules/react-native/Libraries/Components/ScrollView/ScrollView.js
|
||||
@@ -1222,9 +1222,15 @@ class ScrollView extends React.Component<Props, State> {
|
||||
// Note: we should split props.style on the inner and outer props
|
||||
// however, the ScrollView still needs the baseStyle to be scrollable
|
||||
const {outer, inner} = splitLayoutProps(flattenStyle(props.style));
|
||||
+ let inverted;
|
||||
+ if (inner.scaleY) {
|
||||
+ inverted = {scaleY: -1};
|
||||
+ delete inner['scaleY']
|
||||
+ }
|
||||
+
|
||||
return React.cloneElement(
|
||||
refreshControl,
|
||||
- {style: [baseStyle, outer]},
|
||||
+ {style: [baseStyle, outer, inverted]},
|
||||
<ScrollViewClass
|
||||
{...props}
|
||||
style={[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 dffccc4..dc426c2 100644
|
||||
--- a/node_modules/react-native/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js
|
||||
@@ -40,10 +61,10 @@ index 2249d54..cc303b3 100644
|
||||
horizontallyInverted: {
|
||||
transform: [{scaleX: -1}],
|
||||
diff --git a/node_modules/react-native/react.gradle b/node_modules/react-native/react.gradle
|
||||
index 5995ad5..dbae7e3 100644
|
||||
index dd34c98..4d74938 100644
|
||||
--- a/node_modules/react-native/react.gradle
|
||||
+++ b/node_modules/react-native/react.gradle
|
||||
@@ -157,7 +157,7 @@ afterEvaluate {
|
||||
@@ -151,7 +151,7 @@ afterEvaluate {
|
||||
|
||||
// Set up dev mode
|
||||
def devEnabled = !(config."devDisabledIn${targetName}"
|
||||
@@ -52,7 +73,7 @@ index 5995ad5..dbae7e3 100644
|
||||
|
||||
def extraArgs = config.extraPackagerArgs ?: [];
|
||||
|
||||
@@ -177,7 +177,7 @@ afterEvaluate {
|
||||
@@ -171,7 +171,7 @@ afterEvaluate {
|
||||
def hermesFlags;
|
||||
def hbcTempFile = file("${jsBundleFile}.hbc")
|
||||
exec {
|
||||
@@ -61,7 +82,7 @@ index 5995ad5..dbae7e3 100644
|
||||
// Can't use ?: since that will also substitute valid empty lists
|
||||
hermesFlags = config.hermesFlagsRelease
|
||||
if (hermesFlags == null) hermesFlags = ["-O", "-output-source-map"]
|
||||
@@ -221,7 +221,7 @@ afterEvaluate {
|
||||
@@ -215,7 +215,7 @@ afterEvaluate {
|
||||
? config."bundleIn${targetName}"
|
||||
: config."bundleIn${variant.buildType.name.capitalize()}" != null
|
||||
? config."bundleIn${variant.buildType.name.capitalize()}"
|
||||
@@ -70,7 +91,7 @@ index 5995ad5..dbae7e3 100644
|
||||
}
|
||||
|
||||
// Expose a minimal interface on the application variant and the task itself:
|
||||
@@ -318,7 +318,7 @@ afterEvaluate {
|
||||
@@ -312,7 +312,7 @@ afterEvaluate {
|
||||
// This should really be done by packaging all Hermes releated libs into
|
||||
// two separate HermesDebug and HermesRelease AARs, but until then we'll
|
||||
// kludge it by deleting the .so files out of the /transforms/ directory.
|
||||
|
||||
Reference in New Issue
Block a user