Merge beta into master (#1759)
This commit is contained in:
parent
1cd5fa8625
commit
69aff7e56a
|
@ -148,8 +148,6 @@ jobs:
|
||||||
- run:
|
- run:
|
||||||
name: Configure Gradle
|
name: Configure Gradle
|
||||||
command: |
|
command: |
|
||||||
cd android
|
|
||||||
|
|
||||||
echo -e "" > ./gradle.properties
|
echo -e "" > ./gradle.properties
|
||||||
# echo -e "android.enableAapt2=false" >> ./gradle.properties
|
# echo -e "android.enableAapt2=false" >> ./gradle.properties
|
||||||
echo -e "android.useAndroidX=true" >> ./gradle.properties
|
echo -e "android.useAndroidX=true" >> ./gradle.properties
|
||||||
|
@ -165,6 +163,7 @@ jobs:
|
||||||
|
|
||||||
echo -e "VERSIONCODE=$CIRCLE_BUILD_NUM" >> ./gradle.properties
|
echo -e "VERSIONCODE=$CIRCLE_BUILD_NUM" >> ./gradle.properties
|
||||||
echo -e "BugsnagAPIKey=$BUGSNAG_KEY" >> ./gradle.properties
|
echo -e "BugsnagAPIKey=$BUGSNAG_KEY" >> ./gradle.properties
|
||||||
|
working_directory: android
|
||||||
|
|
||||||
- run:
|
- run:
|
||||||
name: Set Google Services
|
name: Set Google Services
|
||||||
|
@ -172,20 +171,6 @@ jobs:
|
||||||
cp google-services.prod.json google-services.json
|
cp google-services.prod.json google-services.json
|
||||||
working_directory: android/app
|
working_directory: android/app
|
||||||
|
|
||||||
- run:
|
|
||||||
name: Upload sourcemaps to Bugsnag
|
|
||||||
command: |
|
|
||||||
if [[ $BUGSNAG_KEY ]]; then
|
|
||||||
yarn generate-source-maps-android
|
|
||||||
curl https://upload.bugsnag.com/react-native-source-map \
|
|
||||||
-F apiKey=$BUGSNAG_KEY \
|
|
||||||
-F appVersionCode=$CIRCLE_BUILD_NUM \
|
|
||||||
-F dev=false \
|
|
||||||
-F platform=android \
|
|
||||||
-F sourceMap=@android-release.bundle.map \
|
|
||||||
-F bundle=@android-release.bundle
|
|
||||||
fi
|
|
||||||
|
|
||||||
- run:
|
- run:
|
||||||
name: Config variables
|
name: Config variables
|
||||||
command: |
|
command: |
|
||||||
|
@ -194,8 +179,6 @@ jobs:
|
||||||
- run:
|
- run:
|
||||||
name: Build Android App
|
name: Build Android App
|
||||||
command: |
|
command: |
|
||||||
npx jetify
|
|
||||||
cd android
|
|
||||||
if [[ $KEYSTORE ]]; then
|
if [[ $KEYSTORE ]]; then
|
||||||
# TODO: enable app bundle again
|
# TODO: enable app bundle again
|
||||||
./gradlew assembleRelease
|
./gradlew assembleRelease
|
||||||
|
@ -206,6 +189,20 @@ jobs:
|
||||||
mkdir -p /tmp/build
|
mkdir -p /tmp/build
|
||||||
|
|
||||||
mv app/build/outputs /tmp/build/
|
mv app/build/outputs /tmp/build/
|
||||||
|
working_directory: android
|
||||||
|
|
||||||
|
- run:
|
||||||
|
name: Upload sourcemaps to Bugsnag
|
||||||
|
command: |
|
||||||
|
if [[ $BUGSNAG_KEY ]]; then
|
||||||
|
yarn generate-source-maps-android upload \
|
||||||
|
--api-key=$BUGSNAG_KEY \
|
||||||
|
--app-version=$CIRCLE_BUILD_NUM \
|
||||||
|
--minifiedFile=android/app/build/generated/assets/react/release/app.bundle \
|
||||||
|
--source-map=android/app/build/generated/sourcemaps/react/release/app.bundle.map \
|
||||||
|
--minified-url=app.bundle \
|
||||||
|
--upload-sources
|
||||||
|
fi
|
||||||
|
|
||||||
- store_artifacts:
|
- store_artifacts:
|
||||||
path: /tmp/build/outputs
|
path: /tmp/build/outputs
|
||||||
|
|
|
@ -103,7 +103,7 @@ Readme will guide you on how to config.
|
||||||
| Custom Fields on Signup | ✅ |
|
| Custom Fields on Signup | ✅ |
|
||||||
| Report message | ✅ |
|
| Report message | ✅ |
|
||||||
| Theming | ✅ |
|
| Theming | ✅ |
|
||||||
| Settings -> Review the App | ❌ |
|
| Settings -> Review the App | ✅ |
|
||||||
| Settings -> Default Browser | ❌ |
|
| Settings -> Default Browser | ❌ |
|
||||||
| Admin panel | ✅ |
|
| Admin panel | ✅ |
|
||||||
| Reply message from notification | ✅ |
|
| Reply message from notification | ✅ |
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
export default {
|
||||||
|
window: () => null
|
||||||
|
};
|
||||||
|
|
||||||
|
export const uiKitMessage = () => () => null;
|
||||||
|
|
||||||
|
export const uiKitModal = () => () => null;
|
||||||
|
|
||||||
|
export class UiKitParserMessage {}
|
||||||
|
|
||||||
|
export class UiKitParserModal {}
|
|
@ -0,0 +1,8 @@
|
||||||
|
export class Client { }
|
||||||
|
|
||||||
|
export default {
|
||||||
|
bugsnag: () => '',
|
||||||
|
leaveBreadcrumb: () => '',
|
||||||
|
notify: () => '',
|
||||||
|
loggerConfig: () => ''
|
||||||
|
};
|
|
@ -0,0 +1,3 @@
|
||||||
|
export default {
|
||||||
|
analytics: null
|
||||||
|
};
|
File diff suppressed because it is too large
Load Diff
|
@ -138,7 +138,7 @@ android {
|
||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode VERSIONCODE as Integer
|
versionCode VERSIONCODE as Integer
|
||||||
versionName "4.3.0"
|
versionName "4.4.0"
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String]
|
manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String]
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,8 +10,10 @@ import android.content.Context;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.graphics.drawable.Icon;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.app.Person;
|
||||||
|
|
||||||
import com.google.gson.*;
|
import com.google.gson.*;
|
||||||
import com.bumptech.glide.Glide;
|
import com.bumptech.glide.Glide;
|
||||||
|
@ -30,6 +32,7 @@ import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
import static com.wix.reactnativenotifications.Defs.NOTIFICATION_RECEIVED_EVENT_NAME;
|
import static com.wix.reactnativenotifications.Defs.NOTIFICATION_RECEIVED_EVENT_NAME;
|
||||||
|
|
||||||
|
@ -41,7 +44,7 @@ public class CustomPushNotification extends PushNotification {
|
||||||
reactApplicationContext = new ReactApplicationContext(context);
|
reactApplicationContext = new ReactApplicationContext(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Map<String, List<String>> notificationMessages = new HashMap<String, List<String>>();
|
private static Map<String, List<Bundle>> notificationMessages = new HashMap<String, List<Bundle>>();
|
||||||
public static String KEY_REPLY = "KEY_REPLY";
|
public static String KEY_REPLY = "KEY_REPLY";
|
||||||
public static String NOTIFICATION_ID = "NOTIFICATION_ID";
|
public static String NOTIFICATION_ID = "NOTIFICATION_ID";
|
||||||
|
|
||||||
|
@ -53,15 +56,26 @@ public class CustomPushNotification extends PushNotification {
|
||||||
public void onReceived() throws InvalidNotificationException {
|
public void onReceived() throws InvalidNotificationException {
|
||||||
final Bundle bundle = mNotificationProps.asBundle();
|
final Bundle bundle = mNotificationProps.asBundle();
|
||||||
|
|
||||||
String notId = bundle.getString("notId");
|
String notId = bundle.getString("notId", "1");
|
||||||
String message = bundle.getString("message");
|
String title = bundle.getString("title");
|
||||||
|
|
||||||
if (notificationMessages.get(notId) == null) {
|
if (notificationMessages.get(notId) == null) {
|
||||||
notificationMessages.put(notId, new ArrayList<String>());
|
notificationMessages.put(notId, new ArrayList<Bundle>());
|
||||||
}
|
}
|
||||||
notificationMessages.get(notId).add(message);
|
|
||||||
|
|
||||||
super.postNotification(notId != null ? Integer.parseInt(notId) : 1);
|
Gson gson = new Gson();
|
||||||
|
Ejson ejson = gson.fromJson(bundle.getString("ejson", "{}"), Ejson.class);
|
||||||
|
|
||||||
|
boolean hasSender = ejson.sender != null;
|
||||||
|
|
||||||
|
bundle.putLong("time", new Date().getTime());
|
||||||
|
bundle.putString("username", hasSender ? ejson.sender.username : title);
|
||||||
|
bundle.putString("senderId", hasSender ? ejson.sender._id : "1");
|
||||||
|
bundle.putString("avatarUri", ejson.getAvatarUri());
|
||||||
|
|
||||||
|
notificationMessages.get(notId).add(bundle);
|
||||||
|
|
||||||
|
super.postNotification(Integer.parseInt(notId));
|
||||||
|
|
||||||
notifyReceivedToJS();
|
notifyReceivedToJS();
|
||||||
}
|
}
|
||||||
|
@ -69,7 +83,7 @@ public class CustomPushNotification extends PushNotification {
|
||||||
@Override
|
@Override
|
||||||
public void onOpened() {
|
public void onOpened() {
|
||||||
Bundle bundle = mNotificationProps.asBundle();
|
Bundle bundle = mNotificationProps.asBundle();
|
||||||
final String notId = bundle.getString("notId");
|
final String notId = bundle.getString("notId", "1");
|
||||||
notificationMessages.remove(notId);
|
notificationMessages.remove(notId);
|
||||||
digestNotification();
|
digestNotification();
|
||||||
}
|
}
|
||||||
|
@ -79,19 +93,20 @@ public class CustomPushNotification extends PushNotification {
|
||||||
final Notification.Builder notification = new Notification.Builder(mContext);
|
final Notification.Builder notification = new Notification.Builder(mContext);
|
||||||
|
|
||||||
Bundle bundle = mNotificationProps.asBundle();
|
Bundle bundle = mNotificationProps.asBundle();
|
||||||
|
String notId = bundle.getString("notId", "1");
|
||||||
String title = bundle.getString("title");
|
String title = bundle.getString("title");
|
||||||
String message = bundle.getString("message");
|
String message = bundle.getString("message");
|
||||||
String notId = bundle.getString("notId");
|
|
||||||
|
|
||||||
notification
|
notification
|
||||||
.setContentIntent(intent)
|
|
||||||
.setContentTitle(title)
|
.setContentTitle(title)
|
||||||
.setContentText(message)
|
.setContentText(message)
|
||||||
|
.setContentIntent(intent)
|
||||||
.setPriority(Notification.PRIORITY_HIGH)
|
.setPriority(Notification.PRIORITY_HIGH)
|
||||||
.setDefaults(Notification.DEFAULT_ALL)
|
.setDefaults(Notification.DEFAULT_ALL)
|
||||||
.setAutoCancel(true);
|
.setAutoCancel(true);
|
||||||
|
|
||||||
Integer notificationId = notId != null ? Integer.parseInt(notId) : 1;
|
Integer notificationId = Integer.parseInt(notId);
|
||||||
|
notificationColor(notification);
|
||||||
notificationChannel(notification);
|
notificationChannel(notification);
|
||||||
notificationIcons(notification, bundle);
|
notificationIcons(notification, bundle);
|
||||||
notificationStyle(notification, notificationId, bundle);
|
notificationStyle(notification, notificationId, bundle);
|
||||||
|
@ -114,10 +129,18 @@ public class CustomPushNotification extends PushNotification {
|
||||||
.submit(100, 100)
|
.submit(100, 100)
|
||||||
.get();
|
.get();
|
||||||
} catch (final ExecutionException | InterruptedException e) {
|
} catch (final ExecutionException | InterruptedException e) {
|
||||||
return null;
|
return largeIcon();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Bitmap largeIcon() {
|
||||||
|
final Resources res = mContext.getResources();
|
||||||
|
String packageName = mContext.getPackageName();
|
||||||
|
int largeIconResId = res.getIdentifier("ic_launcher", "mipmap", packageName);
|
||||||
|
Bitmap largeIconBitmap = BitmapFactory.decodeResource(res, largeIconResId);
|
||||||
|
return largeIconBitmap;
|
||||||
|
}
|
||||||
|
|
||||||
private void notificationIcons(Notification.Builder notification, Bundle bundle) {
|
private void notificationIcons(Notification.Builder notification, Bundle bundle) {
|
||||||
final Resources res = mContext.getResources();
|
final Resources res = mContext.getResources();
|
||||||
String packageName = mContext.getPackageName();
|
String packageName = mContext.getPackageName();
|
||||||
|
@ -127,9 +150,11 @@ public class CustomPushNotification extends PushNotification {
|
||||||
Gson gson = new Gson();
|
Gson gson = new Gson();
|
||||||
Ejson ejson = gson.fromJson(bundle.getString("ejson", "{}"), Ejson.class);
|
Ejson ejson = gson.fromJson(bundle.getString("ejson", "{}"), Ejson.class);
|
||||||
|
|
||||||
notification
|
notification.setSmallIcon(smallIconResId);
|
||||||
.setSmallIcon(smallIconResId)
|
|
||||||
.setLargeIcon(getAvatar(ejson.getAvatarUri()));
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
||||||
|
notification.setLargeIcon(getAvatar(ejson.getAvatarUri()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void notificationChannel(Notification.Builder notification) {
|
private void notificationChannel(Notification.Builder notification) {
|
||||||
|
@ -148,23 +173,82 @@ public class CustomPushNotification extends PushNotification {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void notificationStyle(Notification.Builder notification, int notId, Bundle bundle) {
|
private String extractMessage(String message, Ejson ejson) {
|
||||||
Notification.InboxStyle messageStyle = new Notification.InboxStyle();
|
if (ejson.type != null && !ejson.type.equals("d")) {
|
||||||
List<String> messages = notificationMessages.get(Integer.toString(notId));
|
int pos = message.indexOf(":");
|
||||||
if (messages != null) {
|
int start = pos == -1 ? 0 : pos + 2;
|
||||||
for (int i = 0; i < messages.size(); i++) {
|
return message.substring(start, message.length());
|
||||||
messageStyle.addLine(messages.get(i));
|
|
||||||
}
|
}
|
||||||
String summary = bundle.getString("summaryText");
|
return message;
|
||||||
messageStyle.setSummaryText(summary.replace("%n%", Integer.toString(messages.size())));
|
|
||||||
notification.setNumber(messages.size());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void notificationColor(Notification.Builder notification) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
notification.setColor(mContext.getColor(R.color.notification_text));
|
notification.setColor(mContext.getColor(R.color.notification_text));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notificationStyle(Notification.Builder notification, int notId, Bundle bundle) {
|
||||||
|
List<Bundle> bundles = notificationMessages.get(Integer.toString(notId));
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
||||||
|
Notification.InboxStyle messageStyle = new Notification.InboxStyle();
|
||||||
|
if (bundles != null) {
|
||||||
|
for (int i = 0; i < bundles.size(); i++) {
|
||||||
|
Bundle data = bundles.get(i);
|
||||||
|
String message = data.getString("message");
|
||||||
|
|
||||||
|
messageStyle.addLine(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
notification.setStyle(messageStyle);
|
notification.setStyle(messageStyle);
|
||||||
|
} else {
|
||||||
|
Notification.MessagingStyle messageStyle;
|
||||||
|
|
||||||
|
Gson gson = new Gson();
|
||||||
|
Ejson ejson = gson.fromJson(bundle.getString("ejson", "{}"), Ejson.class);
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
|
||||||
|
messageStyle = new Notification.MessagingStyle("");
|
||||||
|
} else {
|
||||||
|
Person sender = new Person.Builder()
|
||||||
|
.setKey("")
|
||||||
|
.setName("")
|
||||||
|
.build();
|
||||||
|
messageStyle = new Notification.MessagingStyle(sender);
|
||||||
|
}
|
||||||
|
|
||||||
|
String title = bundle.getString("title");
|
||||||
|
messageStyle.setConversationTitle(title);
|
||||||
|
|
||||||
|
if (bundles != null) {
|
||||||
|
for (int i = 0; i < bundles.size(); i++) {
|
||||||
|
Bundle data = bundles.get(i);
|
||||||
|
|
||||||
|
long timestamp = data.getLong("time");
|
||||||
|
String message = data.getString("message");
|
||||||
|
String username = data.getString("username");
|
||||||
|
String senderId = data.getString("senderId");
|
||||||
|
String avatarUri = data.getString("avatarUri");
|
||||||
|
|
||||||
|
String m = extractMessage(message, ejson);
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
|
||||||
|
messageStyle.addMessage(m, timestamp, username);
|
||||||
|
} else {
|
||||||
|
Person sender = new Person.Builder()
|
||||||
|
.setKey(senderId)
|
||||||
|
.setName(username)
|
||||||
|
.setIcon(Icon.createWithBitmap(getAvatar(avatarUri)))
|
||||||
|
.build();
|
||||||
|
messageStyle.addMessage(m, timestamp, sender);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
notification.setStyle(messageStyle);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void notificationReply(Notification.Builder notification, int notificationId, Bundle bundle) {
|
private void notificationReply(Notification.Builder notification, int notificationId, Bundle bundle) {
|
||||||
|
|
|
@ -14,7 +14,7 @@ public class Ejson {
|
||||||
private SharedPreferences sharedPreferences = RNUserDefaultsModule.getPreferences(CustomPushNotification.reactApplicationContext);
|
private SharedPreferences sharedPreferences = RNUserDefaultsModule.getPreferences(CustomPushNotification.reactApplicationContext);
|
||||||
|
|
||||||
public String getAvatarUri() {
|
public String getAvatarUri() {
|
||||||
if (type == null || !type.equals("d")) {
|
if (type == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return serverURL() + "/avatar/" + this.sender.username + "?rc_token=" + token() + "&rc_uid=" + userId();
|
return serverURL() + "/avatar/" + this.sender.username + "?rc_token=" + token() + "&rc_uid=" + userId();
|
||||||
|
@ -36,7 +36,8 @@ public class Ejson {
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Sender {
|
public class Sender {
|
||||||
String username;
|
String username;
|
||||||
|
String _id;
|
||||||
}
|
}
|
||||||
}
|
}
|
Binary file not shown.
After Width: | Height: | Size: 488 B |
Binary file not shown.
After Width: | Height: | Size: 759 B |
Binary file not shown.
After Width: | Height: | Size: 820 B |
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
|
@ -22,6 +22,7 @@ export const SHARE = createRequestTypes('SHARE', [
|
||||||
export const USER = createRequestTypes('USER', ['SET']);
|
export const USER = createRequestTypes('USER', ['SET']);
|
||||||
export const ROOMS = createRequestTypes('ROOMS', [
|
export const ROOMS = createRequestTypes('ROOMS', [
|
||||||
...defaultTypes,
|
...defaultTypes,
|
||||||
|
'REFRESH',
|
||||||
'SET_SEARCH',
|
'SET_SEARCH',
|
||||||
'CLOSE_SERVER_DROPDOWN',
|
'CLOSE_SERVER_DROPDOWN',
|
||||||
'TOGGLE_SERVER_DROPDOWN',
|
'TOGGLE_SERVER_DROPDOWN',
|
||||||
|
|
|
@ -22,9 +22,10 @@ export function loginFailure(err) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function logout() {
|
export function logout(forcedByServer = false) {
|
||||||
return {
|
return {
|
||||||
type: types.LOGOUT
|
type: types.LOGOUT,
|
||||||
|
forcedByServer
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,8 @@ export function notificationReceived(params) {
|
||||||
return {
|
return {
|
||||||
type: NOTIFICATION.RECEIVED,
|
type: NOTIFICATION.RECEIVED,
|
||||||
payload: {
|
payload: {
|
||||||
|
title: params.title,
|
||||||
|
avatar: params.avatar,
|
||||||
message: params.text,
|
message: params.text,
|
||||||
payload: params.payload
|
payload: params.payload
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import * as types from './actionsTypes';
|
import * as types from './actionsTypes';
|
||||||
|
|
||||||
|
|
||||||
export function roomsRequest() {
|
export function roomsRequest(params = { allData: false }) {
|
||||||
return {
|
return {
|
||||||
type: types.ROOMS.REQUEST
|
type: types.ROOMS.REQUEST,
|
||||||
|
params
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,6 +21,12 @@ export function roomsFailure(err) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function roomsRefresh() {
|
||||||
|
return {
|
||||||
|
type: types.ROOMS.REFRESH
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function setSearch(searchText) {
|
export function setSearch(searchText) {
|
||||||
return {
|
return {
|
||||||
type: types.ROOMS.SET_SEARCH,
|
type: types.ROOMS.SET_SEARCH,
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
export const PLAY_MARKET_LINK = 'https://play.google.com/store/apps/details?id=chat.rocket.reactnative';
|
import { getBundleId, isIOS } from '../utils/deviceInfo';
|
||||||
export const APP_STORE_LINK = 'https://itunes.apple.com/app/rocket-chat-experimental/id1272915472?ls=1&mt=8';
|
|
||||||
|
const APP_STORE_ID = '1272915472';
|
||||||
|
|
||||||
|
export const PLAY_MARKET_LINK = `https://play.google.com/store/apps/details?id=${ getBundleId }`;
|
||||||
|
export const APP_STORE_LINK = `https://itunes.apple.com/app/id${ APP_STORE_ID }`;
|
||||||
export const LICENSE_LINK = 'https://github.com/RocketChat/Rocket.Chat.ReactNative/blob/develop/LICENSE';
|
export const LICENSE_LINK = 'https://github.com/RocketChat/Rocket.Chat.ReactNative/blob/develop/LICENSE';
|
||||||
|
export const STORE_REVIEW_LINK = isIOS ? `itms-apps://itunes.apple.com/app/id${ APP_STORE_ID }?action=write-review` : `market://details?id=${ getBundleId }`;
|
||||||
|
|
|
@ -5,6 +5,9 @@ export default {
|
||||||
Accounts_EmailOrUsernamePlaceholder: {
|
Accounts_EmailOrUsernamePlaceholder: {
|
||||||
type: 'valueAsString'
|
type: 'valueAsString'
|
||||||
},
|
},
|
||||||
|
Accounts_EmailVerification: {
|
||||||
|
type: 'valueAsBoolean'
|
||||||
|
},
|
||||||
Accounts_NamePlaceholder: {
|
Accounts_NamePlaceholder: {
|
||||||
type: 'valueAsString'
|
type: 'valueAsString'
|
||||||
},
|
},
|
||||||
|
@ -32,6 +35,9 @@ export default {
|
||||||
Jitsi_Domain: {
|
Jitsi_Domain: {
|
||||||
type: 'valueAsString'
|
type: 'valueAsString'
|
||||||
},
|
},
|
||||||
|
Jitsi_Enabled_TokenAuth: {
|
||||||
|
type: 'valueAsBoolean'
|
||||||
|
},
|
||||||
Jitsi_URL_Room_Prefix: {
|
Jitsi_URL_Room_Prefix: {
|
||||||
type: 'valueAsString'
|
type: 'valueAsString'
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { StyleSheet, Text } from 'react-native';
|
import { StyleSheet, Text } from 'react-native';
|
||||||
import { RectButton } from 'react-native-gesture-handler';
|
import Touchable from 'react-native-platform-touchable';
|
||||||
|
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
import sharedStyles from '../../views/Styles';
|
import sharedStyles from '../../views/Styles';
|
||||||
import ActivityIndicator from '../ActivityIndicator';
|
import ActivityIndicator from '../ActivityIndicator';
|
||||||
|
|
||||||
/* eslint-disable react-native/no-unused-styles */
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
paddingHorizontal: 15,
|
paddingHorizontal: 15,
|
||||||
|
@ -48,9 +47,9 @@ export default class Button extends React.PureComponent {
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const isPrimary = type === 'primary';
|
const isPrimary = type === 'primary';
|
||||||
return (
|
return (
|
||||||
<RectButton
|
<Touchable
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
enabled={!(disabled || loading)}
|
disabled={disabled || loading}
|
||||||
style={[
|
style={[
|
||||||
styles.container,
|
styles.container,
|
||||||
backgroundColor
|
backgroundColor
|
||||||
|
@ -76,7 +75,7 @@ export default class Button extends React.PureComponent {
|
||||||
</Text>
|
</Text>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</RectButton>
|
</Touchable>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ import Navigation from '../lib/Navigation';
|
||||||
import { getMessageTranslation } from './message/utils';
|
import { getMessageTranslation } from './message/utils';
|
||||||
import { LISTENER } from './Toast';
|
import { LISTENER } from './Toast';
|
||||||
import EventEmitter from '../utils/events';
|
import EventEmitter from '../utils/events';
|
||||||
|
import { showConfirmationAlert } from '../utils/info';
|
||||||
|
|
||||||
class MessageActions extends React.Component {
|
class MessageActions extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -223,29 +224,18 @@ class MessageActions extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDelete = () => {
|
handleDelete = () => {
|
||||||
const { message } = this.props;
|
showConfirmationAlert({
|
||||||
Alert.alert(
|
message: I18n.t('You_will_not_be_able_to_recover_this_message'),
|
||||||
I18n.t('Are_you_sure_question_mark'),
|
callToAction: I18n.t('Delete'),
|
||||||
I18n.t('You_will_not_be_able_to_recover_this_message'),
|
|
||||||
[
|
|
||||||
{
|
|
||||||
text: I18n.t('Cancel'),
|
|
||||||
style: 'cancel'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: I18n.t('Yes_action_it', { action: 'delete' }),
|
|
||||||
style: 'destructive',
|
|
||||||
onPress: async() => {
|
onPress: async() => {
|
||||||
|
const { message } = this.props;
|
||||||
try {
|
try {
|
||||||
await RocketChat.deleteMessage(message.id, message.subscription.id);
|
await RocketChat.deleteMessage(message.id, message.subscription.id);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e);
|
log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
],
|
|
||||||
{ cancelable: false }
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleEdit = () => {
|
handleEdit = () => {
|
||||||
|
@ -262,6 +252,9 @@ class MessageActions extends React.Component {
|
||||||
handleShare = async() => {
|
handleShare = async() => {
|
||||||
const { message } = this.props;
|
const { message } = this.props;
|
||||||
const permalink = await this.getPermalink(message);
|
const permalink = await this.getPermalink(message);
|
||||||
|
if (!permalink) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
Share.share({
|
Share.share({
|
||||||
message: permalink
|
message: permalink
|
||||||
});
|
});
|
||||||
|
|
|
@ -17,7 +17,7 @@ export default class EmojiKeyboard extends React.PureComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
const state = store.getState();
|
const state = store.getState();
|
||||||
this.baseUrl = state.settings.Site_Url || state.server ? state.server.server : '';
|
this.baseUrl = state.server.server;
|
||||||
}
|
}
|
||||||
|
|
||||||
onEmojiSelected = (emoji) => {
|
onEmojiSelected = (emoji) => {
|
||||||
|
|
|
@ -92,7 +92,7 @@ ReplyPreview.propTypes = {
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
useMarkdown: state.markdown.useMarkdown,
|
useMarkdown: state.markdown.useMarkdown,
|
||||||
Message_TimeFormat: state.settings.Message_TimeFormat,
|
Message_TimeFormat: state.settings.Message_TimeFormat,
|
||||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
|
baseUrl: state.server.server
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps)(ReplyPreview);
|
export default connect(mapStateToProps)(ReplyPreview);
|
||||||
|
|
|
@ -9,6 +9,7 @@ import DocumentPicker from 'react-native-document-picker';
|
||||||
import ActionSheet from 'react-native-action-sheet';
|
import ActionSheet from 'react-native-action-sheet';
|
||||||
import { Q } from '@nozbe/watermelondb';
|
import { Q } from '@nozbe/watermelondb';
|
||||||
|
|
||||||
|
import { generateTriggerId } from '../../lib/methods/actions';
|
||||||
import TextInput from '../../presentation/TextInput';
|
import TextInput from '../../presentation/TextInput';
|
||||||
import { userTyping as userTypingAction } from '../../actions/room';
|
import { userTyping as userTypingAction } from '../../actions/room';
|
||||||
import RocketChat from '../../lib/rocketchat';
|
import RocketChat from '../../lib/rocketchat';
|
||||||
|
@ -42,6 +43,8 @@ import {
|
||||||
MENTIONS_TRACKING_TYPE_USERS
|
MENTIONS_TRACKING_TYPE_USERS
|
||||||
} from './constants';
|
} from './constants';
|
||||||
import CommandsPreview from './CommandsPreview';
|
import CommandsPreview from './CommandsPreview';
|
||||||
|
import { Review } from '../../utils/review';
|
||||||
|
import { getUserSelector } from '../../selectors/login';
|
||||||
|
|
||||||
const imagePickerConfig = {
|
const imagePickerConfig = {
|
||||||
cropping: true,
|
cropping: true,
|
||||||
|
@ -103,7 +106,8 @@ class MessageBox extends Component {
|
||||||
isVisible: false
|
isVisible: false
|
||||||
},
|
},
|
||||||
commandPreview: [],
|
commandPreview: [],
|
||||||
showCommandPreview: false
|
showCommandPreview: false,
|
||||||
|
command: {}
|
||||||
};
|
};
|
||||||
this.text = '';
|
this.text = '';
|
||||||
this.focused = false;
|
this.focused = false;
|
||||||
|
@ -279,7 +283,7 @@ class MessageBox extends Component {
|
||||||
try {
|
try {
|
||||||
const command = await commandsCollection.find(name);
|
const command = await commandsCollection.find(name);
|
||||||
if (command.providesPreview) {
|
if (command.providesPreview) {
|
||||||
return this.setCommandPreview(name, params);
|
return this.setCommandPreview(command, name, params);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('Slash command not found');
|
console.log('Slash command not found');
|
||||||
|
@ -338,16 +342,22 @@ class MessageBox extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
onPressCommandPreview = (item) => {
|
onPressCommandPreview = (item) => {
|
||||||
const { rid } = this.props;
|
const { command } = this.state;
|
||||||
|
const {
|
||||||
|
rid, tmid, message: { id: messageTmid }, replyCancel
|
||||||
|
} = this.props;
|
||||||
const { text } = this;
|
const { text } = this;
|
||||||
const command = text.substr(0, text.indexOf(' ')).slice(1);
|
const name = text.substr(0, text.indexOf(' ')).slice(1);
|
||||||
const params = text.substr(text.indexOf(' ') + 1) || 'params';
|
const params = text.substr(text.indexOf(' ') + 1) || 'params';
|
||||||
this.setState({ commandPreview: [], showCommandPreview: false });
|
this.setState({ commandPreview: [], showCommandPreview: false, command: {} });
|
||||||
this.stopTrackingMention();
|
this.stopTrackingMention();
|
||||||
this.clearInput();
|
this.clearInput();
|
||||||
this.handleTyping(false);
|
this.handleTyping(false);
|
||||||
try {
|
try {
|
||||||
RocketChat.executeCommandPreview(command, params, rid, item);
|
const { appId } = command;
|
||||||
|
const triggerId = generateTriggerId(appId);
|
||||||
|
RocketChat.executeCommandPreview(name, params, rid, item, triggerId, tmid || messageTmid);
|
||||||
|
replyCancel();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e);
|
log(e);
|
||||||
}
|
}
|
||||||
|
@ -451,13 +461,13 @@ class MessageBox extends Component {
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
setCommandPreview = async(command, params) => {
|
setCommandPreview = async(command, name, params) => {
|
||||||
const { rid } = this.props;
|
const { rid } = this.props;
|
||||||
try {
|
try {
|
||||||
const { preview } = await RocketChat.getCommandPreview(command, rid, params);
|
const { preview } = await RocketChat.getCommandPreview(name, rid, params);
|
||||||
this.setState({ commandPreview: preview.items, showCommandPreview: true });
|
this.setState({ commandPreview: preview.items, showCommandPreview: true, command });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.setState({ commandPreview: [], showCommandPreview: true });
|
this.setState({ commandPreview: [], showCommandPreview: true, command: {} });
|
||||||
log(e);
|
log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -493,7 +503,7 @@ class MessageBox extends Component {
|
||||||
|
|
||||||
sendMediaMessage = async(file) => {
|
sendMediaMessage = async(file) => {
|
||||||
const {
|
const {
|
||||||
rid, tmid, baseUrl: server, user
|
rid, tmid, baseUrl: server, user, message: { id: messageTmid }, replyCancel
|
||||||
} = this.props;
|
} = this.props;
|
||||||
this.setState({ file: { isVisible: false } });
|
this.setState({ file: { isVisible: false } });
|
||||||
const fileInfo = {
|
const fileInfo = {
|
||||||
|
@ -505,7 +515,9 @@ class MessageBox extends Component {
|
||||||
path: file.path
|
path: file.path
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
await RocketChat.sendFileMessage(rid, fileInfo, tmid, server, user);
|
replyCancel();
|
||||||
|
await RocketChat.sendFileMessage(rid, fileInfo, tmid || messageTmid, server, user);
|
||||||
|
Review.pushPositiveEvent();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e);
|
log(e);
|
||||||
}
|
}
|
||||||
|
@ -518,7 +530,7 @@ class MessageBox extends Component {
|
||||||
this.showUploadModal(image);
|
this.showUploadModal(image);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e);
|
// Do nothing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -529,7 +541,7 @@ class MessageBox extends Component {
|
||||||
this.showUploadModal(video);
|
this.showUploadModal(video);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e);
|
// Do nothing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -540,7 +552,7 @@ class MessageBox extends Component {
|
||||||
this.showUploadModal(image);
|
this.showUploadModal(image);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e);
|
// Do nothing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -639,7 +651,7 @@ class MessageBox extends Component {
|
||||||
|
|
||||||
submit = async() => {
|
submit = async() => {
|
||||||
const {
|
const {
|
||||||
onSubmit, rid: roomId
|
onSubmit, rid: roomId, tmid
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const message = this.text;
|
const message = this.text;
|
||||||
|
|
||||||
|
@ -653,7 +665,7 @@ class MessageBox extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
editing, replying
|
editing, replying, message: { id: messageTmid }, replyCancel
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
// Slash command
|
// Slash command
|
||||||
|
@ -667,7 +679,10 @@ class MessageBox extends Component {
|
||||||
if (slashCommand.length > 0) {
|
if (slashCommand.length > 0) {
|
||||||
try {
|
try {
|
||||||
const messageWithoutCommand = message.replace(/([^\s]+)/, '').trim();
|
const messageWithoutCommand = message.replace(/([^\s]+)/, '').trim();
|
||||||
RocketChat.runSlashCommand(command, roomId, messageWithoutCommand);
|
const [{ appId }] = slashCommand;
|
||||||
|
const triggerId = generateTriggerId(appId);
|
||||||
|
RocketChat.runSlashCommand(command, roomId, messageWithoutCommand, triggerId, tmid || messageTmid);
|
||||||
|
replyCancel();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e);
|
log(e);
|
||||||
}
|
}
|
||||||
|
@ -684,7 +699,7 @@ class MessageBox extends Component {
|
||||||
// Reply
|
// Reply
|
||||||
} else if (replying) {
|
} else if (replying) {
|
||||||
const {
|
const {
|
||||||
message: replyingMessage, replyCancel, threadsEnabled, replyWithMention
|
message: replyingMessage, threadsEnabled, replyWithMention
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
// Thread
|
// Thread
|
||||||
|
@ -872,13 +887,9 @@ class MessageBox extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
|
baseUrl: state.server.server,
|
||||||
threadsEnabled: state.settings.Threads_enabled,
|
threadsEnabled: state.settings.Threads_enabled,
|
||||||
user: {
|
user: getUserSelector(state),
|
||||||
id: state.login.user && state.login.user.id,
|
|
||||||
username: state.login.user && state.login.user.username,
|
|
||||||
token: state.login.user && state.login.user.token
|
|
||||||
},
|
|
||||||
FileUpload_MediaTypeWhiteList: state.settings.FileUpload_MediaTypeWhiteList,
|
FileUpload_MediaTypeWhiteList: state.settings.FileUpload_MediaTypeWhiteList,
|
||||||
FileUpload_MaxFileSize: state.settings.FileUpload_MaxFileSize
|
FileUpload_MaxFileSize: state.settings.FileUpload_MaxFileSize
|
||||||
});
|
});
|
||||||
|
|
|
@ -32,6 +32,8 @@ const RoomTypeIcon = React.memo(({
|
||||||
return <Image source={{ uri: 'hashtag' }} style={[styles.style, style, { width: size, height: size, tintColor: color }]} />;
|
return <Image source={{ uri: 'hashtag' }} style={[styles.style, style, { width: size, height: size, tintColor: color }]} />;
|
||||||
} if (type === 'd') {
|
} if (type === 'd') {
|
||||||
return <CustomIcon name='at' size={13} style={[styles.style, styles.discussion, { color }]} />;
|
return <CustomIcon name='at' size={13} style={[styles.style, styles.discussion, { color }]} />;
|
||||||
|
} if (type === 'l') {
|
||||||
|
return <CustomIcon name='livechat' size={13} style={[styles.style, styles.discussion, { color }]} />;
|
||||||
}
|
}
|
||||||
return <Image source={{ uri: 'lock' }} style={[styles.style, style, { width: size, height: size, tintColor: color }]} />;
|
return <Image source={{ uri: 'lock' }} style={[styles.style, style, { width: size, height: size, tintColor: color }]} />;
|
||||||
});
|
});
|
||||||
|
|
|
@ -37,7 +37,7 @@ const styles = StyleSheet.create({
|
||||||
...sharedStyles.textRegular
|
...sharedStyles.textRegular
|
||||||
},
|
},
|
||||||
cancel: {
|
cancel: {
|
||||||
marginRight: 10
|
marginRight: 15
|
||||||
},
|
},
|
||||||
cancelText: {
|
cancelText: {
|
||||||
...sharedStyles.textRegular,
|
...sharedStyles.textRegular,
|
||||||
|
|
|
@ -7,6 +7,7 @@ import sharedStyles from '../views/Styles';
|
||||||
import TextInput from '../presentation/TextInput';
|
import TextInput from '../presentation/TextInput';
|
||||||
import { themes } from '../constants/colors';
|
import { themes } from '../constants/colors';
|
||||||
import { CustomIcon } from '../lib/Icons';
|
import { CustomIcon } from '../lib/Icons';
|
||||||
|
import ActivityIndicator from './ActivityIndicator';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
error: {
|
error: {
|
||||||
|
@ -56,6 +57,7 @@ export default class RCTextInput extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
label: PropTypes.string,
|
label: PropTypes.string,
|
||||||
error: PropTypes.object,
|
error: PropTypes.object,
|
||||||
|
loading: PropTypes.bool,
|
||||||
secureTextEntry: PropTypes.bool,
|
secureTextEntry: PropTypes.bool,
|
||||||
containerStyle: PropTypes.any,
|
containerStyle: PropTypes.any,
|
||||||
inputStyle: PropTypes.object,
|
inputStyle: PropTypes.object,
|
||||||
|
@ -102,6 +104,11 @@ export default class RCTextInput extends React.PureComponent {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get loading() {
|
||||||
|
const { theme } = this.props;
|
||||||
|
return <ActivityIndicator style={[styles.iconContainer, styles.iconRight, { color: themes[theme].bodyText }]} />;
|
||||||
|
}
|
||||||
|
|
||||||
tooglePassword = () => {
|
tooglePassword = () => {
|
||||||
this.setState(prevState => ({ showPassword: !prevState.showPassword }));
|
this.setState(prevState => ({ showPassword: !prevState.showPassword }));
|
||||||
}
|
}
|
||||||
|
@ -109,7 +116,7 @@ export default class RCTextInput extends React.PureComponent {
|
||||||
render() {
|
render() {
|
||||||
const { showPassword } = this.state;
|
const { showPassword } = this.state;
|
||||||
const {
|
const {
|
||||||
label, error, secureTextEntry, containerStyle, inputRef, iconLeft, inputStyle, testID, placeholder, theme, ...inputProps
|
label, error, loading, secureTextEntry, containerStyle, inputRef, iconLeft, inputStyle, testID, placeholder, theme, ...inputProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { dangerColor } = themes[theme];
|
const { dangerColor } = themes[theme];
|
||||||
return (
|
return (
|
||||||
|
@ -131,10 +138,6 @@ export default class RCTextInput extends React.PureComponent {
|
||||||
<TextInput
|
<TextInput
|
||||||
style={[
|
style={[
|
||||||
styles.input,
|
styles.input,
|
||||||
error.error && {
|
|
||||||
color: dangerColor,
|
|
||||||
borderColor: dangerColor
|
|
||||||
},
|
|
||||||
iconLeft && styles.inputIconLeft,
|
iconLeft && styles.inputIconLeft,
|
||||||
secureTextEntry && styles.inputIconRight,
|
secureTextEntry && styles.inputIconRight,
|
||||||
{
|
{
|
||||||
|
@ -142,6 +145,10 @@ export default class RCTextInput extends React.PureComponent {
|
||||||
borderColor: themes[theme].separatorColor,
|
borderColor: themes[theme].separatorColor,
|
||||||
color: themes[theme].titleText
|
color: themes[theme].titleText
|
||||||
},
|
},
|
||||||
|
error.error && {
|
||||||
|
color: dangerColor,
|
||||||
|
borderColor: dangerColor
|
||||||
|
},
|
||||||
inputStyle
|
inputStyle
|
||||||
]}
|
]}
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
|
@ -158,8 +165,9 @@ export default class RCTextInput extends React.PureComponent {
|
||||||
/>
|
/>
|
||||||
{iconLeft ? this.iconLeft : null}
|
{iconLeft ? this.iconLeft : null}
|
||||||
{secureTextEntry ? this.iconPassword : null}
|
{secureTextEntry ? this.iconPassword : null}
|
||||||
|
{loading ? this.loading : null}
|
||||||
</View>
|
</View>
|
||||||
{error.error ? <Text style={[styles.error, { color: dangerColor }]}>{error.reason}</Text> : null}
|
{error && error.reason ? <Text style={[styles.error, { color: dangerColor }]}>{error.reason}</Text> : null}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,15 +43,19 @@ class Toast extends React.Component {
|
||||||
EventEmitter.removeListener(LISTENER);
|
EventEmitter.removeListener(LISTENER);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getToastRef = toast => this.toast = toast;
|
||||||
|
|
||||||
showToast = ({ message }) => {
|
showToast = ({ message }) => {
|
||||||
|
if (this.toast && this.toast.show) {
|
||||||
this.toast.show(message, 1000);
|
this.toast.show(message, 1000);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { theme } = this.props;
|
const { theme } = this.props;
|
||||||
return (
|
return (
|
||||||
<EasyToast
|
<EasyToast
|
||||||
ref={toast => this.toast = toast}
|
ref={this.getToastRef}
|
||||||
position='center'
|
position='center'
|
||||||
style={[styles.toast, { backgroundColor: themes[theme].toastBackground }]}
|
style={[styles.toast, { backgroundColor: themes[theme].toastBackground }]}
|
||||||
textStyle={[styles.text, { color: themes[theme].buttonText }]}
|
textStyle={[styles.text, { color: themes[theme].buttonText }]}
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
|
||||||
|
|
||||||
|
import Button from '../Button';
|
||||||
|
import I18n from '../../i18n';
|
||||||
|
|
||||||
|
export const Actions = ({
|
||||||
|
blockId, appId, elements, parser, theme
|
||||||
|
}) => {
|
||||||
|
const [showMoreVisible, setShowMoreVisible] = useState(() => elements.length > 5);
|
||||||
|
const renderedElements = showMoreVisible ? elements.slice(0, 5) : elements;
|
||||||
|
|
||||||
|
const Elements = () => renderedElements
|
||||||
|
.map(element => parser.renderActions({ blockId, appId, ...element }, BLOCK_CONTEXT.ACTION, parser));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Elements />
|
||||||
|
{showMoreVisible && (<Button theme={theme} title={I18n.t('Show_more')} onPress={() => setShowMoreVisible(false)} />)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Actions.propTypes = {
|
||||||
|
blockId: PropTypes.string,
|
||||||
|
appId: PropTypes.string,
|
||||||
|
elements: PropTypes.array,
|
||||||
|
parser: PropTypes.object,
|
||||||
|
theme: PropTypes.string
|
||||||
|
};
|
|
@ -0,0 +1,22 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { View, StyleSheet } from 'react-native';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
minHeight: 36,
|
||||||
|
alignItems: 'center',
|
||||||
|
flexDirection: 'row'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const Context = ({ elements, parser }) => (
|
||||||
|
<View style={styles.container}>
|
||||||
|
{elements.map(element => parser.renderContext(element, BLOCK_CONTEXT.CONTEXT, parser))}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
Context.propTypes = {
|
||||||
|
elements: PropTypes.array,
|
||||||
|
parser: PropTypes.object
|
||||||
|
};
|
|
@ -0,0 +1,117 @@
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { View, StyleSheet, Text } from 'react-native';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import DateTimePicker from '@react-native-community/datetimepicker';
|
||||||
|
import Touchable from 'react-native-platform-touchable';
|
||||||
|
import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
|
import Button from '../Button';
|
||||||
|
import { textParser } from './utils';
|
||||||
|
import { themes } from '../../constants/colors';
|
||||||
|
|
||||||
|
import sharedStyles from '../../views/Styles';
|
||||||
|
import { CustomIcon } from '../../lib/Icons';
|
||||||
|
import { isAndroid } from '../../utils/deviceInfo';
|
||||||
|
import ActivityIndicator from '../ActivityIndicator';
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
input: {
|
||||||
|
height: 48,
|
||||||
|
paddingLeft: 16,
|
||||||
|
borderWidth: StyleSheet.hairlineWidth,
|
||||||
|
borderRadius: 2,
|
||||||
|
alignItems: 'center',
|
||||||
|
flexDirection: 'row'
|
||||||
|
},
|
||||||
|
inputText: {
|
||||||
|
...sharedStyles.textRegular,
|
||||||
|
fontSize: 14
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
right: 16,
|
||||||
|
position: 'absolute'
|
||||||
|
},
|
||||||
|
loading: {
|
||||||
|
padding: 0
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const DatePicker = ({
|
||||||
|
element, language, action, context, theme, loading, value, error
|
||||||
|
}) => {
|
||||||
|
const [show, onShow] = useState(false);
|
||||||
|
const { initial_date, placeholder } = element;
|
||||||
|
const [currentDate, onChangeDate] = useState(new Date(initial_date || value));
|
||||||
|
|
||||||
|
const onChange = ({ nativeEvent: { timestamp } }, date) => {
|
||||||
|
const newDate = date || new Date(timestamp);
|
||||||
|
onChangeDate(newDate);
|
||||||
|
action({ value: moment(newDate).format('YYYY-MM-DD') });
|
||||||
|
if (isAndroid) {
|
||||||
|
onShow(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let button = (
|
||||||
|
<Button
|
||||||
|
title={textParser([placeholder])}
|
||||||
|
onPress={() => onShow(!show)}
|
||||||
|
loading={loading}
|
||||||
|
theme={theme}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (context === BLOCK_CONTEXT.FORM) {
|
||||||
|
button = (
|
||||||
|
<Touchable
|
||||||
|
onPress={() => onShow(!show)}
|
||||||
|
style={{ backgroundColor: themes[theme].backgroundColor }}
|
||||||
|
background={Touchable.Ripple(themes[theme].bannerBackground)}
|
||||||
|
>
|
||||||
|
<View style={[styles.input, { borderColor: error ? themes[theme].dangerColor : themes[theme].separatorColor }]}>
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.inputText,
|
||||||
|
{ color: error ? themes[theme].dangerColor : themes[theme].titleText }
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{currentDate.toLocaleDateString(language)}
|
||||||
|
</Text>
|
||||||
|
{
|
||||||
|
loading
|
||||||
|
? <ActivityIndicator style={[styles.loading, styles.icon]} />
|
||||||
|
: <CustomIcon name='calendar' size={20} color={error ? themes[theme].dangerColor : themes[theme].auxiliaryText} style={styles.icon} />
|
||||||
|
}
|
||||||
|
</View>
|
||||||
|
</Touchable>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = show ? (
|
||||||
|
<DateTimePicker
|
||||||
|
mode='date'
|
||||||
|
display='default'
|
||||||
|
value={currentDate}
|
||||||
|
onChange={onChange}
|
||||||
|
textColor={themes[theme].titleText}
|
||||||
|
/>
|
||||||
|
) : null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{button}
|
||||||
|
{content}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
DatePicker.propTypes = {
|
||||||
|
element: PropTypes.object,
|
||||||
|
language: PropTypes.string,
|
||||||
|
action: PropTypes.func,
|
||||||
|
context: PropTypes.number,
|
||||||
|
loading: PropTypes.bool,
|
||||||
|
theme: PropTypes.string,
|
||||||
|
value: PropTypes.string,
|
||||||
|
error: PropTypes.string
|
||||||
|
};
|
|
@ -0,0 +1,18 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { StyleSheet } from 'react-native';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import Separator from '../Separator';
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
separator: {
|
||||||
|
width: '100%',
|
||||||
|
alignSelf: 'center',
|
||||||
|
marginBottom: 16
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const Divider = ({ theme }) => <Separator style={styles.separator} theme={theme} />;
|
||||||
|
Divider.propTypes = {
|
||||||
|
theme: PropTypes.string
|
||||||
|
};
|
|
@ -0,0 +1,61 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { View, StyleSheet } from 'react-native';
|
||||||
|
import FastImage from 'react-native-fast-image';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
|
||||||
|
|
||||||
|
import ImageContainer from '../message/Image';
|
||||||
|
import Navigation from '../../lib/Navigation';
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
image: {
|
||||||
|
borderRadius: 2
|
||||||
|
},
|
||||||
|
mediaContext: {
|
||||||
|
marginRight: 8
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const ThumbContext = args => <View style={styles.mediaContext}><Thumb size={20} {...args} /></View>;
|
||||||
|
|
||||||
|
export const Thumb = ({ element, size = 88 }) => (
|
||||||
|
<FastImage
|
||||||
|
style={[{ width: size, height: size }, styles.image]}
|
||||||
|
source={{ uri: element.imageUrl }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
Thumb.propTypes = {
|
||||||
|
element: PropTypes.object,
|
||||||
|
size: PropTypes.number
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Media = ({ element, theme }) => {
|
||||||
|
const showAttachment = attachment => Navigation.navigate('AttachmentView', { attachment });
|
||||||
|
const { imageUrl } = element;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ImageContainer
|
||||||
|
file={{ image_url: imageUrl }}
|
||||||
|
imageUrl={imageUrl}
|
||||||
|
showAttachment={showAttachment}
|
||||||
|
theme={theme}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
Media.propTypes = {
|
||||||
|
element: PropTypes.object,
|
||||||
|
theme: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
const genericImage = (element, context, theme) => {
|
||||||
|
switch (context) {
|
||||||
|
case BLOCK_CONTEXT.SECTION:
|
||||||
|
return <Thumb element={element} />;
|
||||||
|
case BLOCK_CONTEXT.CONTEXT:
|
||||||
|
return <ThumbContext element={element} />;
|
||||||
|
default:
|
||||||
|
return <Media element={element} theme={theme} />;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Image = ({ element, context, theme }) => genericImage(element, context, theme);
|
|
@ -0,0 +1,55 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { StyleSheet, Text, View } from 'react-native';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
|
||||||
|
|
||||||
|
import sharedStyles from '../../views/Styles';
|
||||||
|
import { themes } from '../../constants/colors';
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
marginBottom: 16
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
fontSize: 14,
|
||||||
|
marginVertical: 10,
|
||||||
|
...sharedStyles.textSemibold
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
marginBottom: 10,
|
||||||
|
fontSize: 15,
|
||||||
|
...sharedStyles.textRegular
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
marginTop: 8,
|
||||||
|
fontSize: 14,
|
||||||
|
...sharedStyles.textRegular,
|
||||||
|
...sharedStyles.textAlignCenter
|
||||||
|
},
|
||||||
|
hint: {
|
||||||
|
fontSize: 14,
|
||||||
|
...sharedStyles.textRegular
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const Input = ({
|
||||||
|
element, parser, label, description, error, hint, theme
|
||||||
|
}) => (
|
||||||
|
<View style={styles.container}>
|
||||||
|
{label ? <Text style={[styles.label, { color: error ? themes[theme].dangerColor : themes[theme].titleText }]}>{label}</Text> : null}
|
||||||
|
{description ? <Text style={[styles.description, { color: themes[theme].auxiliaryText }]}>{description}</Text> : null}
|
||||||
|
{parser.renderInputs({ ...element }, BLOCK_CONTEXT.FORM, parser)}
|
||||||
|
{error ? <Text style={[styles.error, { color: themes[theme].dangerColor }]}>{error}</Text> : null}
|
||||||
|
{hint ? <Text style={[styles.hint, { color: themes[theme].auxiliaryText }]}>{hint}</Text> : null}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
|
||||||
|
Input.propTypes = {
|
||||||
|
element: PropTypes.object,
|
||||||
|
parser: PropTypes.object,
|
||||||
|
label: PropTypes.string,
|
||||||
|
description: PropTypes.string,
|
||||||
|
error: PropTypes.string,
|
||||||
|
hint: PropTypes.string,
|
||||||
|
theme: PropTypes.string
|
||||||
|
};
|
|
@ -0,0 +1,27 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import { UiKitMessage, UiKitModal } from './index';
|
||||||
|
import { KitContext } from './utils';
|
||||||
|
|
||||||
|
export const messageBlockWithContext = context => props => (
|
||||||
|
<KitContext.Provider value={context}>
|
||||||
|
<MessageBlock {...props} />
|
||||||
|
</KitContext.Provider>
|
||||||
|
);
|
||||||
|
|
||||||
|
const MessageBlock = ({ blocks }) => UiKitMessage(blocks);
|
||||||
|
MessageBlock.propTypes = {
|
||||||
|
blocks: PropTypes.any
|
||||||
|
};
|
||||||
|
|
||||||
|
export const modalBlockWithContext = context => data => (
|
||||||
|
<KitContext.Provider value={{ ...context, ...data }}>
|
||||||
|
<ModalBlock {...data} />
|
||||||
|
</KitContext.Provider>
|
||||||
|
);
|
||||||
|
|
||||||
|
const ModalBlock = ({ blocks }) => UiKitModal(blocks);
|
||||||
|
ModalBlock.propTypes = {
|
||||||
|
blocks: PropTypes.any
|
||||||
|
};
|
|
@ -0,0 +1,45 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Text, View, Image } from 'react-native';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import Touchable from 'react-native-platform-touchable';
|
||||||
|
|
||||||
|
import { themes } from '../../../constants/colors';
|
||||||
|
import { textParser } from '../utils';
|
||||||
|
import { CustomIcon } from '../../../lib/Icons';
|
||||||
|
|
||||||
|
import styles from './styles';
|
||||||
|
|
||||||
|
const keyExtractor = item => item.value.toString();
|
||||||
|
|
||||||
|
const Chip = ({ item, onSelect, theme }) => (
|
||||||
|
<Touchable
|
||||||
|
key={item.value}
|
||||||
|
onPress={() => onSelect(item)}
|
||||||
|
style={[styles.chip, { backgroundColor: themes[theme].auxiliaryBackground }]}
|
||||||
|
background={Touchable.Ripple(themes[theme].bannerBackground)}
|
||||||
|
>
|
||||||
|
<>
|
||||||
|
{item.imageUrl ? <Image style={styles.chipImage} source={{ uri: item.imageUrl }} /> : null}
|
||||||
|
<Text style={[styles.chipText, { color: themes[theme].titleText }]}>{textParser([item.text])}</Text>
|
||||||
|
<CustomIcon name='cross' size={16} color={themes[theme].auxiliaryText} />
|
||||||
|
</>
|
||||||
|
</Touchable>
|
||||||
|
);
|
||||||
|
Chip.propTypes = {
|
||||||
|
item: PropTypes.object,
|
||||||
|
onSelect: PropTypes.func,
|
||||||
|
theme: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
const Chips = ({ items, onSelect, theme }) => (
|
||||||
|
<View style={styles.chips}>
|
||||||
|
{items.map(item => <Chip key={keyExtractor(item)} item={item} onSelect={onSelect} theme={theme} />)}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
Chips.propTypes = {
|
||||||
|
items: PropTypes.array,
|
||||||
|
onSelect: PropTypes.func,
|
||||||
|
theme: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Chips;
|
|
@ -0,0 +1,36 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { View } from 'react-native';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import Touchable from 'react-native-platform-touchable';
|
||||||
|
|
||||||
|
import { CustomIcon } from '../../../lib/Icons';
|
||||||
|
import { themes } from '../../../constants/colors';
|
||||||
|
import ActivityIndicator from '../../ActivityIndicator';
|
||||||
|
import styles from './styles';
|
||||||
|
|
||||||
|
const Input = ({
|
||||||
|
children, open, theme, loading
|
||||||
|
}) => (
|
||||||
|
<Touchable
|
||||||
|
onPress={() => open(true)}
|
||||||
|
style={{ backgroundColor: themes[theme].backgroundColor }}
|
||||||
|
background={Touchable.Ripple(themes[theme].bannerBackground)}
|
||||||
|
>
|
||||||
|
<View style={[styles.input, { borderColor: themes[theme].separatorColor }]}>
|
||||||
|
{children}
|
||||||
|
{
|
||||||
|
loading
|
||||||
|
? <ActivityIndicator style={[styles.loading, styles.icon]} />
|
||||||
|
: <CustomIcon name='arrow-down' size={22} color={themes[theme].auxiliaryText} style={styles.icon} />
|
||||||
|
}
|
||||||
|
</View>
|
||||||
|
</Touchable>
|
||||||
|
);
|
||||||
|
Input.propTypes = {
|
||||||
|
children: PropTypes.node,
|
||||||
|
open: PropTypes.func,
|
||||||
|
theme: PropTypes.string,
|
||||||
|
loading: PropTypes.bool
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Input;
|
|
@ -0,0 +1,61 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Text, FlatList } from 'react-native';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import Touchable from 'react-native-platform-touchable';
|
||||||
|
|
||||||
|
import Separator from '../../Separator';
|
||||||
|
import Check from '../../Check';
|
||||||
|
|
||||||
|
import { textParser } from '../utils';
|
||||||
|
import { themes } from '../../../constants/colors';
|
||||||
|
|
||||||
|
import styles from './styles';
|
||||||
|
|
||||||
|
const keyExtractor = item => item.value.toString();
|
||||||
|
|
||||||
|
// RectButton doesn't work on modal (Android)
|
||||||
|
const Item = ({
|
||||||
|
item, selected, onSelect, theme
|
||||||
|
}) => (
|
||||||
|
<Touchable
|
||||||
|
key={item}
|
||||||
|
onPress={() => onSelect(item)}
|
||||||
|
style={[
|
||||||
|
styles.item,
|
||||||
|
{ backgroundColor: themes[theme].backgroundColor }
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<>
|
||||||
|
<Text style={{ color: themes[theme].titleText }}>{textParser([item.text])}</Text>
|
||||||
|
{selected ? <Check theme={theme} /> : null}
|
||||||
|
</>
|
||||||
|
</Touchable>
|
||||||
|
);
|
||||||
|
Item.propTypes = {
|
||||||
|
item: PropTypes.object,
|
||||||
|
selected: PropTypes.number,
|
||||||
|
onSelect: PropTypes.func,
|
||||||
|
theme: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
const Items = ({
|
||||||
|
items, selected, onSelect, theme
|
||||||
|
}) => (
|
||||||
|
<FlatList
|
||||||
|
data={items}
|
||||||
|
style={[styles.items, { backgroundColor: themes[theme].backgroundColor }]}
|
||||||
|
contentContainerStyle={[styles.itemContent, { backgroundColor: themes[theme].backgroundColor }]}
|
||||||
|
keyboardShouldPersistTaps='always'
|
||||||
|
ItemSeparatorComponent={() => <Separator theme={theme} />}
|
||||||
|
keyExtractor={keyExtractor}
|
||||||
|
renderItem={({ item }) => <Item item={item} onSelect={onSelect} theme={theme} selected={selected.find(s => s === item.value)} />}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
Items.propTypes = {
|
||||||
|
items: PropTypes.array,
|
||||||
|
selected: PropTypes.array,
|
||||||
|
onSelect: PropTypes.func,
|
||||||
|
theme: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Items;
|
|
@ -0,0 +1,171 @@
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
View, Text, TouchableWithoutFeedback, Modal, KeyboardAvoidingView, Animated, Easing
|
||||||
|
} from 'react-native';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
|
||||||
|
|
||||||
|
import Button from '../../Button';
|
||||||
|
import TextInput from '../../TextInput';
|
||||||
|
|
||||||
|
import { textParser } from '../utils';
|
||||||
|
import { themes } from '../../../constants/colors';
|
||||||
|
|
||||||
|
import Chips from './Chips';
|
||||||
|
import Items from './Items';
|
||||||
|
import Input from './Input';
|
||||||
|
|
||||||
|
import styles from './styles';
|
||||||
|
|
||||||
|
const ANIMATION_DURATION = 200;
|
||||||
|
const ANIMATION_PROPS = {
|
||||||
|
duration: ANIMATION_DURATION,
|
||||||
|
easing: Easing.inOut(Easing.quad),
|
||||||
|
useNativeDriver: true
|
||||||
|
};
|
||||||
|
const animatedValue = new Animated.Value(0);
|
||||||
|
|
||||||
|
export const MultiSelect = React.memo(({
|
||||||
|
options = [],
|
||||||
|
onChange,
|
||||||
|
placeholder = { text: 'Search' },
|
||||||
|
context,
|
||||||
|
loading,
|
||||||
|
value: values,
|
||||||
|
multiselect = false,
|
||||||
|
theme
|
||||||
|
}) => {
|
||||||
|
const [selected, select] = useState(values || []);
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const [search, onSearchChange] = useState('');
|
||||||
|
const [currentValue, setCurrentValue] = useState('');
|
||||||
|
const [showContent, setShowContent] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setOpen(showContent);
|
||||||
|
}, [showContent]);
|
||||||
|
|
||||||
|
const onShow = () => {
|
||||||
|
Animated.timing(
|
||||||
|
animatedValue,
|
||||||
|
{
|
||||||
|
toValue: 1,
|
||||||
|
...ANIMATION_PROPS
|
||||||
|
}
|
||||||
|
).start();
|
||||||
|
setShowContent(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onHide = () => {
|
||||||
|
Animated.timing(
|
||||||
|
animatedValue,
|
||||||
|
{
|
||||||
|
toValue: 0,
|
||||||
|
...ANIMATION_PROPS
|
||||||
|
}
|
||||||
|
).start(() => setShowContent(false));
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSelect = (item) => {
|
||||||
|
const { value } = item;
|
||||||
|
if (multiselect) {
|
||||||
|
let newSelect = [];
|
||||||
|
if (!selected.includes(value)) {
|
||||||
|
newSelect = [...selected, value];
|
||||||
|
} else {
|
||||||
|
newSelect = selected.filter(s => s !== value);
|
||||||
|
}
|
||||||
|
select(newSelect);
|
||||||
|
onChange({ value: newSelect });
|
||||||
|
} else {
|
||||||
|
onChange({ value });
|
||||||
|
setCurrentValue(value);
|
||||||
|
setOpen(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderContent = () => {
|
||||||
|
const items = options.filter(option => textParser([option.text]).toLowerCase().includes(search.toLowerCase()));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={[styles.modal, { backgroundColor: themes[theme].backgroundColor }]}>
|
||||||
|
<View style={[styles.content, { backgroundColor: themes[theme].backgroundColor }]}>
|
||||||
|
<TextInput
|
||||||
|
onChangeText={onSearchChange}
|
||||||
|
placeholder={placeholder.text}
|
||||||
|
theme={theme}
|
||||||
|
/>
|
||||||
|
<Items items={items} selected={selected} onSelect={onSelect} theme={theme} />
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const translateY = animatedValue.interpolate({
|
||||||
|
inputRange: [0, 1],
|
||||||
|
outputRange: [600, 0]
|
||||||
|
});
|
||||||
|
|
||||||
|
let button = multiselect ? (
|
||||||
|
<Button
|
||||||
|
title={`${ selected.length } selecteds`}
|
||||||
|
onPress={onShow}
|
||||||
|
loading={loading}
|
||||||
|
theme={theme}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Input
|
||||||
|
open={onShow}
|
||||||
|
theme={theme}
|
||||||
|
loading={loading}
|
||||||
|
>
|
||||||
|
<Text style={[styles.pickerText, { color: themes[theme].auxiliaryText }]}>{currentValue}</Text>
|
||||||
|
</Input>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (context === BLOCK_CONTEXT.FORM) {
|
||||||
|
button = (
|
||||||
|
<Input
|
||||||
|
open={onShow}
|
||||||
|
theme={theme}
|
||||||
|
loading={loading}
|
||||||
|
>
|
||||||
|
<Chips items={options.filter(option => selected.includes(option.value))} onSelect={onSelect} theme={theme} />
|
||||||
|
</Input>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Modal
|
||||||
|
animationType='fade'
|
||||||
|
transparent
|
||||||
|
visible={open}
|
||||||
|
onRequestClose={onHide}
|
||||||
|
onShow={onShow}
|
||||||
|
>
|
||||||
|
<TouchableWithoutFeedback onPress={onHide}>
|
||||||
|
<View style={styles.container}>
|
||||||
|
<View style={[styles.backdrop, { backgroundColor: themes[theme].backdropColor }]} />
|
||||||
|
<KeyboardAvoidingView style={styles.keyboardView} behavior='padding'>
|
||||||
|
<Animated.View style={[styles.animatedContent, { transform: [{ translateY }] }]}>
|
||||||
|
{showContent ? renderContent() : null}
|
||||||
|
</Animated.View>
|
||||||
|
</KeyboardAvoidingView>
|
||||||
|
</View>
|
||||||
|
</TouchableWithoutFeedback>
|
||||||
|
</Modal>
|
||||||
|
{button}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
MultiSelect.propTypes = {
|
||||||
|
options: PropTypes.array,
|
||||||
|
onChange: PropTypes.func,
|
||||||
|
placeholder: PropTypes.object,
|
||||||
|
context: PropTypes.number,
|
||||||
|
loading: PropTypes.bool,
|
||||||
|
multiselect: PropTypes.bool,
|
||||||
|
value: PropTypes.array,
|
||||||
|
theme: PropTypes.string
|
||||||
|
};
|
|
@ -0,0 +1,84 @@
|
||||||
|
import { StyleSheet } from 'react-native';
|
||||||
|
|
||||||
|
import sharedStyles from '../../../views/Styles';
|
||||||
|
|
||||||
|
export default StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'flex-end'
|
||||||
|
},
|
||||||
|
backdrop: {
|
||||||
|
...StyleSheet.absoluteFill,
|
||||||
|
opacity: 0.3
|
||||||
|
},
|
||||||
|
modal: {
|
||||||
|
height: 300,
|
||||||
|
width: '100%',
|
||||||
|
borderTopRightRadius: 16,
|
||||||
|
borderTopLeftRadius: 16,
|
||||||
|
overflow: 'hidden'
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
padding: 16
|
||||||
|
},
|
||||||
|
animatedContent: {
|
||||||
|
width: '100%'
|
||||||
|
},
|
||||||
|
keyboardView: {
|
||||||
|
width: '100%'
|
||||||
|
},
|
||||||
|
pickerText: {
|
||||||
|
...sharedStyles.textRegular,
|
||||||
|
fontSize: 16
|
||||||
|
},
|
||||||
|
item: {
|
||||||
|
height: 48,
|
||||||
|
alignItems: 'center',
|
||||||
|
flexDirection: 'row'
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
minHeight: 48,
|
||||||
|
padding: 8,
|
||||||
|
paddingBottom: 0,
|
||||||
|
borderWidth: StyleSheet.hairlineWidth,
|
||||||
|
borderRadius: 2,
|
||||||
|
alignItems: 'center',
|
||||||
|
flexDirection: 'row'
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
position: 'absolute',
|
||||||
|
right: 16
|
||||||
|
},
|
||||||
|
itemContent: {
|
||||||
|
paddingBottom: 36
|
||||||
|
},
|
||||||
|
items: {
|
||||||
|
height: 226
|
||||||
|
},
|
||||||
|
chips: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
marginRight: 16
|
||||||
|
},
|
||||||
|
chip: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
borderRadius: 2,
|
||||||
|
height: 28,
|
||||||
|
alignItems: 'center',
|
||||||
|
paddingHorizontal: 4,
|
||||||
|
marginBottom: 8,
|
||||||
|
marginRight: 8
|
||||||
|
},
|
||||||
|
chipText: {
|
||||||
|
paddingHorizontal: 8,
|
||||||
|
...sharedStyles.textMedium,
|
||||||
|
fontSize: 14
|
||||||
|
},
|
||||||
|
chipImage: {
|
||||||
|
marginLeft: 4,
|
||||||
|
borderRadius: 2,
|
||||||
|
width: 20,
|
||||||
|
height: 20
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,103 @@
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Text, FlatList, StyleSheet } from 'react-native';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import Popover from 'react-native-popover-view';
|
||||||
|
import Touchable from 'react-native-platform-touchable';
|
||||||
|
|
||||||
|
import { CustomIcon } from '../../lib/Icons';
|
||||||
|
import Separator from '../Separator';
|
||||||
|
import ActivityIndicator from '../ActivityIndicator';
|
||||||
|
import { themes } from '../../constants/colors';
|
||||||
|
import { BUTTON_HIT_SLOP } from '../message/utils';
|
||||||
|
|
||||||
|
const keyExtractor = item => item.value;
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
menu: {
|
||||||
|
justifyContent: 'center'
|
||||||
|
},
|
||||||
|
option: {
|
||||||
|
padding: 8,
|
||||||
|
minHeight: 32
|
||||||
|
},
|
||||||
|
loading: {
|
||||||
|
padding: 0
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const Option = ({
|
||||||
|
option: { text, value }, onOptionPress, parser, theme
|
||||||
|
}) => (
|
||||||
|
<Touchable
|
||||||
|
onPress={() => onOptionPress({ value })}
|
||||||
|
background={Touchable.Ripple(themes[theme].bannerBackground)}
|
||||||
|
style={styles.option}
|
||||||
|
>
|
||||||
|
<Text>{parser.text(text)}</Text>
|
||||||
|
</Touchable>
|
||||||
|
);
|
||||||
|
Option.propTypes = {
|
||||||
|
option: PropTypes.object,
|
||||||
|
onOptionPress: PropTypes.func,
|
||||||
|
parser: PropTypes.object,
|
||||||
|
theme: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
const Options = ({
|
||||||
|
options, onOptionPress, parser, theme
|
||||||
|
}) => (
|
||||||
|
<FlatList
|
||||||
|
data={options}
|
||||||
|
renderItem={({ item }) => <Option option={item} onOptionPress={onOptionPress} parser={parser} theme={theme} />}
|
||||||
|
keyExtractor={keyExtractor}
|
||||||
|
ItemSeparatorComponent={() => <Separator theme={theme} />}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
Options.propTypes = {
|
||||||
|
options: PropTypes.array,
|
||||||
|
onOptionPress: PropTypes.func,
|
||||||
|
parser: PropTypes.object,
|
||||||
|
theme: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
const touchable = {};
|
||||||
|
|
||||||
|
export const Overflow = ({
|
||||||
|
element, loading, action, parser, theme
|
||||||
|
}) => {
|
||||||
|
const { options, blockId } = element;
|
||||||
|
const [show, onShow] = useState(false);
|
||||||
|
|
||||||
|
const onOptionPress = ({ value }) => {
|
||||||
|
onShow(false);
|
||||||
|
action({ value });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Touchable
|
||||||
|
ref={ref => touchable[blockId] = ref}
|
||||||
|
background={Touchable.Ripple(themes[theme].bannerBackground)}
|
||||||
|
onPress={() => onShow(!show)}
|
||||||
|
hitSlop={BUTTON_HIT_SLOP}
|
||||||
|
style={styles.menu}
|
||||||
|
>
|
||||||
|
{!loading ? <CustomIcon size={18} name='menu' color={themes[theme].bodyText} /> : <ActivityIndicator style={styles.loading} theme={theme} />}
|
||||||
|
</Touchable>
|
||||||
|
<Popover
|
||||||
|
isVisible={show}
|
||||||
|
fromView={touchable[blockId]}
|
||||||
|
onRequestClose={() => onShow(false)}
|
||||||
|
>
|
||||||
|
<Options options={options} onOptionPress={onOptionPress} parser={parser} theme={theme} />
|
||||||
|
</Popover>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
Overflow.propTypes = {
|
||||||
|
element: PropTypes.any,
|
||||||
|
action: PropTypes.func,
|
||||||
|
loading: PropTypes.bool,
|
||||||
|
parser: PropTypes.object,
|
||||||
|
theme: PropTypes.string
|
||||||
|
};
|
|
@ -0,0 +1,70 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { View, Text, StyleSheet } from 'react-native';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
|
||||||
|
|
||||||
|
import { themes } from '../../constants/colors';
|
||||||
|
import sharedStyles from '../../views/Styles';
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
content: {
|
||||||
|
marginBottom: 8
|
||||||
|
},
|
||||||
|
row: {
|
||||||
|
flexDirection: 'row'
|
||||||
|
},
|
||||||
|
column: {
|
||||||
|
justifyContent: 'center'
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
flex: 1,
|
||||||
|
padding: 4,
|
||||||
|
fontSize: 16,
|
||||||
|
lineHeight: 22,
|
||||||
|
textAlignVertical: 'center',
|
||||||
|
...sharedStyles.textRegular
|
||||||
|
},
|
||||||
|
field: {
|
||||||
|
marginVertical: 6
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const Accessory = ({
|
||||||
|
blockId, appId, element, parser
|
||||||
|
}) => parser.renderAccessories(
|
||||||
|
{ blockId, appId, ...element },
|
||||||
|
BLOCK_CONTEXT.SECTION,
|
||||||
|
parser
|
||||||
|
);
|
||||||
|
|
||||||
|
const Fields = ({ fields, parser, theme }) => fields.map(field => (
|
||||||
|
<Text style={[styles.text, styles.field, { color: themes[theme].bodyText }]}>
|
||||||
|
{parser.text(field)}
|
||||||
|
</Text>
|
||||||
|
));
|
||||||
|
|
||||||
|
const accessoriesRight = ['image', 'overflow'];
|
||||||
|
|
||||||
|
export const Section = ({
|
||||||
|
blockId, appId, text, fields, accessory, parser, theme
|
||||||
|
}) => (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
styles.content,
|
||||||
|
accessory && accessoriesRight.includes(accessory.type) ? styles.row : styles.column
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{text ? <Text style={[styles.text, { color: themes[theme].bodyText }]}>{parser.text(text)}</Text> : null}
|
||||||
|
{fields ? <Fields fields={fields} theme={theme} parser={parser} /> : null}
|
||||||
|
{accessory ? <Accessory element={{ blockId, appId, ...accessory }} parser={parser} /> : null}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
Section.propTypes = {
|
||||||
|
blockId: PropTypes.string,
|
||||||
|
appId: PropTypes.string,
|
||||||
|
text: PropTypes.object,
|
||||||
|
fields: PropTypes.array,
|
||||||
|
accessory: PropTypes.any,
|
||||||
|
theme: PropTypes.string,
|
||||||
|
parser: PropTypes.object
|
||||||
|
};
|
|
@ -0,0 +1,89 @@
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { StyleSheet } from 'react-native';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import RNPickerSelect from 'react-native-picker-select';
|
||||||
|
|
||||||
|
import sharedStyles from '../../views/Styles';
|
||||||
|
import { themes } from '../../constants/colors';
|
||||||
|
import { CustomIcon } from '../../lib/Icons';
|
||||||
|
import { textParser } from './utils';
|
||||||
|
import { isAndroid, isIOS } from '../../utils/deviceInfo';
|
||||||
|
import ActivityIndicator from '../ActivityIndicator';
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
iosPadding: {
|
||||||
|
height: 48,
|
||||||
|
justifyContent: 'center'
|
||||||
|
},
|
||||||
|
viewContainer: {
|
||||||
|
marginBottom: 16,
|
||||||
|
paddingHorizontal: 16,
|
||||||
|
borderWidth: StyleSheet.hairlineWidth,
|
||||||
|
borderRadius: 2,
|
||||||
|
justifyContent: 'center'
|
||||||
|
},
|
||||||
|
pickerText: {
|
||||||
|
...sharedStyles.textRegular,
|
||||||
|
fontSize: 16
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
right: 16
|
||||||
|
},
|
||||||
|
loading: {
|
||||||
|
padding: 0
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const Select = ({
|
||||||
|
options = [],
|
||||||
|
placeholder,
|
||||||
|
onChange,
|
||||||
|
loading,
|
||||||
|
disabled,
|
||||||
|
value: initialValue,
|
||||||
|
theme
|
||||||
|
}) => {
|
||||||
|
const [selected, setSelected] = useState(!Array.isArray(initialValue) && initialValue);
|
||||||
|
const items = options.map(option => ({ label: textParser([option.text]), value: option.value }));
|
||||||
|
const pickerStyle = {
|
||||||
|
...styles.viewContainer,
|
||||||
|
...(isIOS ? styles.iosPadding : {}),
|
||||||
|
borderColor: themes[theme].separatorColor,
|
||||||
|
backgroundColor: themes[theme].backgroundColor
|
||||||
|
};
|
||||||
|
|
||||||
|
const Icon = () => (
|
||||||
|
loading
|
||||||
|
? <ActivityIndicator style={styles.loading} />
|
||||||
|
: <CustomIcon size={22} name='arrow-down' style={isAndroid && styles.icon} color={themes[theme].auxiliaryText} />
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RNPickerSelect
|
||||||
|
items={items}
|
||||||
|
placeholder={placeholder ? { label: textParser([placeholder]), value: null } : {}}
|
||||||
|
useNativeAndroidPickerStyle={false}
|
||||||
|
value={selected}
|
||||||
|
disabled={disabled}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
onChange({ value });
|
||||||
|
setSelected(value);
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
viewContainer: pickerStyle,
|
||||||
|
inputAndroidContainer: pickerStyle
|
||||||
|
}}
|
||||||
|
Icon={Icon}
|
||||||
|
textInputProps={{ style: { ...styles.pickerText, color: selected ? themes[theme].titleText : themes[theme].auxiliaryText } }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
Select.propTypes = {
|
||||||
|
options: PropTypes.array,
|
||||||
|
placeholder: PropTypes.string,
|
||||||
|
onChange: PropTypes.func,
|
||||||
|
loading: PropTypes.bool,
|
||||||
|
disabled: PropTypes.bool,
|
||||||
|
value: PropTypes.array,
|
||||||
|
theme: PropTypes.string
|
||||||
|
};
|
|
@ -0,0 +1,246 @@
|
||||||
|
/* eslint-disable class-methods-use-this */
|
||||||
|
import React, { useContext } from 'react';
|
||||||
|
import { StyleSheet } from 'react-native';
|
||||||
|
import {
|
||||||
|
uiKitMessage,
|
||||||
|
UiKitParserMessage,
|
||||||
|
uiKitModal,
|
||||||
|
UiKitParserModal,
|
||||||
|
BLOCK_CONTEXT
|
||||||
|
} from '@rocket.chat/ui-kit';
|
||||||
|
|
||||||
|
import Markdown from '../markdown';
|
||||||
|
import Button from '../Button';
|
||||||
|
import TextInput from '../TextInput';
|
||||||
|
|
||||||
|
import { useBlockContext } from './utils';
|
||||||
|
import { themes } from '../../constants/colors';
|
||||||
|
|
||||||
|
import { Divider } from './Divider';
|
||||||
|
import { Section } from './Section';
|
||||||
|
import { Actions } from './Actions';
|
||||||
|
import { Image } from './Image';
|
||||||
|
import { Select } from './Select';
|
||||||
|
import { Context } from './Context';
|
||||||
|
import { MultiSelect } from './MultiSelect';
|
||||||
|
import { Input } from './Input';
|
||||||
|
import { DatePicker } from './DatePicker';
|
||||||
|
import { Overflow } from './Overflow';
|
||||||
|
import { ThemeContext } from '../../theme';
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
input: {
|
||||||
|
marginBottom: 0
|
||||||
|
},
|
||||||
|
multiline: {
|
||||||
|
height: 130
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
marginBottom: 16
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const plainText = ({ text } = { text: '' }) => text;
|
||||||
|
|
||||||
|
class MessageParser extends UiKitParserMessage {
|
||||||
|
text({ text, type } = { text: '' }, context) {
|
||||||
|
const { theme } = useContext(ThemeContext);
|
||||||
|
if (type !== 'mrkdwn') {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isContext = context === BLOCK_CONTEXT.CONTEXT;
|
||||||
|
return (
|
||||||
|
<Markdown
|
||||||
|
msg={text}
|
||||||
|
theme={theme}
|
||||||
|
style={[isContext && { color: themes[theme].auxiliaryText }]}
|
||||||
|
preview={isContext}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
button(element, context) {
|
||||||
|
const {
|
||||||
|
text, value, actionId, style
|
||||||
|
} = element;
|
||||||
|
const [{ loading }, action] = useBlockContext(element, context);
|
||||||
|
const { theme } = useContext(ThemeContext);
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
key={actionId}
|
||||||
|
type={style}
|
||||||
|
title={this.text(text)}
|
||||||
|
loading={loading}
|
||||||
|
onPress={() => action({ value })}
|
||||||
|
style={styles.button}
|
||||||
|
theme={theme}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
divider() {
|
||||||
|
const { theme } = useContext(ThemeContext);
|
||||||
|
return <Divider theme={theme} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
section(args) {
|
||||||
|
const { theme } = useContext(ThemeContext);
|
||||||
|
return <Section {...args} theme={theme} parser={this} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
actions(args) {
|
||||||
|
const { theme } = useContext(ThemeContext);
|
||||||
|
return <Actions {...args} theme={theme} parser={this} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
overflow(element, context) {
|
||||||
|
const [{ loading }, action] = useBlockContext(element, context);
|
||||||
|
const { theme } = useContext(ThemeContext);
|
||||||
|
return (
|
||||||
|
<Overflow
|
||||||
|
element={element}
|
||||||
|
context={context}
|
||||||
|
loading={loading}
|
||||||
|
action={action}
|
||||||
|
theme={theme}
|
||||||
|
parser={this}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
datePicker(element, context) {
|
||||||
|
const [{
|
||||||
|
loading, value, error, language
|
||||||
|
}, action] = useBlockContext(element, context);
|
||||||
|
const { theme } = useContext(ThemeContext);
|
||||||
|
return (
|
||||||
|
<DatePicker
|
||||||
|
element={element}
|
||||||
|
language={language}
|
||||||
|
theme={theme}
|
||||||
|
value={value}
|
||||||
|
action={action}
|
||||||
|
context={context}
|
||||||
|
loading={loading}
|
||||||
|
error={error}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
image(element, context) {
|
||||||
|
const { theme } = useContext(ThemeContext);
|
||||||
|
return <Image element={element} theme={theme} context={context} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
context(args) {
|
||||||
|
const { theme } = useContext(ThemeContext);
|
||||||
|
return <Context {...args} theme={theme} parser={this} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
multiStaticSelect(element, context) {
|
||||||
|
const [{ loading, value }, action] = useBlockContext(element, context);
|
||||||
|
const { theme } = useContext(ThemeContext);
|
||||||
|
return (
|
||||||
|
<MultiSelect
|
||||||
|
{...element}
|
||||||
|
theme={theme}
|
||||||
|
value={value}
|
||||||
|
onChange={action}
|
||||||
|
context={context}
|
||||||
|
loading={loading}
|
||||||
|
multiselect
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
staticSelect(element, context) {
|
||||||
|
const [{ loading, value }, action] = useBlockContext(element, context);
|
||||||
|
const { theme } = useContext(ThemeContext);
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
{...element}
|
||||||
|
theme={theme}
|
||||||
|
value={value}
|
||||||
|
onChange={action}
|
||||||
|
loading={loading}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
selectInput(element, context) {
|
||||||
|
const [{ loading, value }, action] = useBlockContext(element, context);
|
||||||
|
const { theme } = useContext(ThemeContext);
|
||||||
|
return (
|
||||||
|
<MultiSelect
|
||||||
|
{...element}
|
||||||
|
theme={theme}
|
||||||
|
value={value}
|
||||||
|
onChange={action}
|
||||||
|
context={context}
|
||||||
|
loading={loading}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ModalParser extends UiKitParserModal {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
Object.getOwnPropertyNames(MessageParser.prototype).forEach((method) => {
|
||||||
|
ModalParser.prototype[method] = ModalParser.prototype[method] || MessageParser.prototype[method];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
input({
|
||||||
|
element, blockId, appId, label, description, hint
|
||||||
|
}, context) {
|
||||||
|
const [{ error }] = useBlockContext({ ...element, appId, blockId }, context);
|
||||||
|
const { theme } = useContext(ThemeContext);
|
||||||
|
return (
|
||||||
|
<Input
|
||||||
|
parser={this}
|
||||||
|
element={{ ...element, appId, blockId }}
|
||||||
|
label={plainText(label)}
|
||||||
|
description={plainText(description)}
|
||||||
|
hint={plainText(hint)}
|
||||||
|
error={error}
|
||||||
|
theme={theme}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
image(element, context) {
|
||||||
|
const { theme } = useContext(ThemeContext);
|
||||||
|
return <Image element={element} theme={theme} context={context} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
plainInput(element, context) {
|
||||||
|
const [{ loading, value, error }, action] = useBlockContext(element, context);
|
||||||
|
const { theme } = useContext(ThemeContext);
|
||||||
|
const { multiline, actionId, placeholder } = element;
|
||||||
|
return (
|
||||||
|
<TextInput
|
||||||
|
id={actionId}
|
||||||
|
placeholder={plainText(placeholder)}
|
||||||
|
onInput={action}
|
||||||
|
multiline={multiline}
|
||||||
|
loading={loading}
|
||||||
|
onChangeText={text => action({ value: text })}
|
||||||
|
inputStyle={multiline && styles.multiline}
|
||||||
|
containerStyle={styles.input}
|
||||||
|
value={value}
|
||||||
|
error={{ error }}
|
||||||
|
theme={theme}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const messageParser = new MessageParser();
|
||||||
|
export const modalParser = new ModalParser();
|
||||||
|
|
||||||
|
export const UiKitMessage = uiKitMessage(messageParser);
|
||||||
|
export const UiKitModal = uiKitModal(modalParser);
|
||||||
|
|
||||||
|
export const UiKitComponent = ({ render, blocks }) => render(blocks);
|
|
@ -0,0 +1,63 @@
|
||||||
|
/* eslint-disable no-shadow */
|
||||||
|
import React, { useContext, useState } from 'react';
|
||||||
|
import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
|
||||||
|
|
||||||
|
export const textParser = ([{ text }]) => text;
|
||||||
|
|
||||||
|
export const defaultContext = {
|
||||||
|
action: (...args) => console.log(args),
|
||||||
|
state: console.log,
|
||||||
|
appId: '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz',
|
||||||
|
errors: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const KitContext = React.createContext(defaultContext);
|
||||||
|
|
||||||
|
export const useBlockContext = ({
|
||||||
|
blockId, actionId, appId, initialValue
|
||||||
|
}, context) => {
|
||||||
|
const {
|
||||||
|
action, appId: appIdFromContext, viewId, state, language, errors, values = {}
|
||||||
|
} = useContext(KitContext);
|
||||||
|
const { value = initialValue } = values[actionId] || {};
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
const error = errors && actionId && errors[actionId];
|
||||||
|
|
||||||
|
if ([BLOCK_CONTEXT.SECTION, BLOCK_CONTEXT.ACTION].includes(context)) {
|
||||||
|
return [{
|
||||||
|
loading, setLoading, error, value, language
|
||||||
|
}, async({ value }) => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
await action({
|
||||||
|
blockId,
|
||||||
|
appId: appId || appIdFromContext,
|
||||||
|
actionId,
|
||||||
|
value,
|
||||||
|
viewId
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [{
|
||||||
|
loading, setLoading, value, error, language
|
||||||
|
}, async({ value }) => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
await state({
|
||||||
|
blockId,
|
||||||
|
appId,
|
||||||
|
actionId,
|
||||||
|
value
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
}];
|
||||||
|
};
|
|
@ -28,21 +28,27 @@ const AtMention = React.memo(({
|
||||||
}
|
}
|
||||||
|
|
||||||
const handlePress = () => {
|
const handlePress = () => {
|
||||||
if (mentions && mentions.length && mentions.findIndex(m => m.username === mention) !== -1) {
|
|
||||||
const index = mentions.findIndex(m => m.username === mention);
|
const index = mentions.findIndex(m => m.username === mention);
|
||||||
const navParam = {
|
const navParam = {
|
||||||
t: 'd',
|
t: 'd',
|
||||||
rid: mentions[index]._id
|
rid: mentions[index]._id
|
||||||
};
|
};
|
||||||
navToRoomInfo(navParam);
|
navToRoomInfo(navParam);
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (mentions && mentions.length && mentions.findIndex(m => m.username === mention) !== -1) {
|
||||||
return (
|
return (
|
||||||
<Text
|
<Text
|
||||||
style={[preview ? { ...styles.text, color: themes[theme].bodyText } : mentionStyle, ...style]}
|
style={[preview ? { ...styles.text, color: themes[theme].bodyText } : mentionStyle, ...style]}
|
||||||
onPress={preview ? undefined : handlePress}
|
onPress={preview ? undefined : handlePress}
|
||||||
>
|
>
|
||||||
|
{mention}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Text style={[styles.text, { color: themes[theme].bodyText }, ...style]}>
|
||||||
{`@${ mention }`}
|
{`@${ mention }`}
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
|
|
|
@ -24,12 +24,12 @@ const Hashtag = React.memo(({
|
||||||
style={[preview ? { ...styles.text, color: themes[theme].bodyText } : styles.mention, ...style]}
|
style={[preview ? { ...styles.text, color: themes[theme].bodyText } : styles.mention, ...style]}
|
||||||
onPress={preview ? undefined : handlePress}
|
onPress={preview ? undefined : handlePress}
|
||||||
>
|
>
|
||||||
{`#${ hashtag }`}
|
{hashtag}
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Text style={[preview ? { ...styles.text, color: themes[theme].bodyText } : styles.mention, ...style]}>
|
<Text style={[styles.text, { color: themes[theme].bodyText }, ...style]}>
|
||||||
{`#${ hashtag }`}
|
{`#${ hashtag }`}
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Text } from 'react-native';
|
import { Text, Clipboard } from 'react-native';
|
||||||
|
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
import openLink from '../../utils/openLink';
|
import openLink from '../../utils/openLink';
|
||||||
|
import { LISTENER } from '../Toast';
|
||||||
|
import EventEmitter from '../../utils/events';
|
||||||
|
import I18n from '../../i18n';
|
||||||
|
|
||||||
const Link = React.memo(({
|
const Link = React.memo(({
|
||||||
children, link, preview, theme
|
children, link, preview, theme
|
||||||
|
@ -17,11 +20,16 @@ const Link = React.memo(({
|
||||||
};
|
};
|
||||||
|
|
||||||
const childLength = React.Children.toArray(children).filter(o => o).length;
|
const childLength = React.Children.toArray(children).filter(o => o).length;
|
||||||
|
const onLongPress = () => {
|
||||||
|
Clipboard.setString(link);
|
||||||
|
EventEmitter.emit(LISTENER, { message: I18n.t('Copied_to_clipboard') });
|
||||||
|
};
|
||||||
|
|
||||||
// if you have a [](https://rocket.chat) render https://rocket.chat
|
// if you have a [](https://rocket.chat) render https://rocket.chat
|
||||||
return (
|
return (
|
||||||
<Text
|
<Text
|
||||||
onPress={preview ? undefined : handlePress}
|
onPress={preview ? undefined : handlePress}
|
||||||
|
onLongPress={preview ? undefined : onLongPress}
|
||||||
style={
|
style={
|
||||||
!preview
|
!preview
|
||||||
? { ...styles.link, color: themes[theme].actionTintColor }
|
? { ...styles.link, color: themes[theme].actionTintColor }
|
||||||
|
|
|
@ -38,7 +38,7 @@ const Table = React.memo(({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onPress = () => Navigation.navigate('TableView', { renderRows, tableWidth: getTableWidth() });
|
const onPress = () => Navigation.navigate('MarkdownTableView', { renderRows, tableWidth: getTableWidth() });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity onPress={onPress}>
|
<TouchableOpacity onPress={onPress}>
|
||||||
|
|
|
@ -60,6 +60,8 @@ const emojiCount = (str) => {
|
||||||
return counter;
|
return counter;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const parser = new Parser();
|
||||||
|
|
||||||
class Markdown extends PureComponent {
|
class Markdown extends PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
msg: PropTypes.string,
|
msg: PropTypes.string,
|
||||||
|
@ -81,13 +83,9 @@ class Markdown extends PureComponent {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.parser = this.createParser();
|
|
||||||
this.renderer = this.createRenderer(props.preview);
|
this.renderer = this.createRenderer(props.preview);
|
||||||
}
|
}
|
||||||
|
|
||||||
createParser = () => new Parser();
|
|
||||||
|
|
||||||
createRenderer = (preview = false) => new Renderer({
|
createRenderer = (preview = false) => new Renderer({
|
||||||
renderers: {
|
renderers: {
|
||||||
text: this.renderText,
|
text: this.renderText,
|
||||||
|
@ -385,7 +383,7 @@ class Markdown extends PureComponent {
|
||||||
|
|
||||||
if (preview) {
|
if (preview) {
|
||||||
m = m.split('\n').reduce((lines, line) => `${ lines } ${ line }`, '');
|
m = m.split('\n').reduce((lines, line) => `${ lines } ${ line }`, '');
|
||||||
const ast = this.parser.parse(m);
|
const ast = parser.parse(m);
|
||||||
return this.renderer.render(ast);
|
return this.renderer.render(ast);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -393,7 +391,7 @@ class Markdown extends PureComponent {
|
||||||
return <Text style={[styles.text, { color: themes[theme].bodyText }]} numberOfLines={numberOfLines}>{m}</Text>;
|
return <Text style={[styles.text, { color: themes[theme].bodyText }]} numberOfLines={numberOfLines}>{m}</Text>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ast = this.parser.parse(m);
|
const ast = parser.parse(m);
|
||||||
this.isMessageContainsOnlyEmoji = isOnlyEmoji(m) && emojiCount(m) <= 3;
|
this.isMessageContainsOnlyEmoji = isOnlyEmoji(m) && emojiCount(m) <= 3;
|
||||||
|
|
||||||
this.editedMessage(ast);
|
this.editedMessage(ast);
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { messageBlockWithContext } from '../UIKit/MessageBlock';
|
||||||
|
|
||||||
|
const Blocks = React.memo(({
|
||||||
|
blocks, id: mid, rid, blockAction
|
||||||
|
}) => {
|
||||||
|
if (blocks && blocks.length > 0) {
|
||||||
|
const [, secondBlock] = blocks;
|
||||||
|
const { appId = '' } = secondBlock;
|
||||||
|
return React.createElement(
|
||||||
|
messageBlockWithContext({
|
||||||
|
action: async({ actionId, value, blockId }) => {
|
||||||
|
await blockAction({
|
||||||
|
actionId,
|
||||||
|
appId,
|
||||||
|
value,
|
||||||
|
blockId,
|
||||||
|
rid,
|
||||||
|
mid
|
||||||
|
});
|
||||||
|
},
|
||||||
|
appId,
|
||||||
|
rid
|
||||||
|
}), { blocks }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
Blocks.propTypes = {
|
||||||
|
blocks: PropTypes.array,
|
||||||
|
id: PropTypes.string,
|
||||||
|
rid: PropTypes.string,
|
||||||
|
blockAction: PropTypes.func
|
||||||
|
};
|
||||||
|
Blocks.displayName = 'MessageBlocks';
|
||||||
|
|
||||||
|
export default Blocks;
|
|
@ -28,7 +28,7 @@ const Button = React.memo(({
|
||||||
</Touchable>
|
</Touchable>
|
||||||
));
|
));
|
||||||
|
|
||||||
const Image = React.memo(({ img, theme }) => (
|
export const MessageImage = React.memo(({ img, theme }) => (
|
||||||
<ImageProgress
|
<ImageProgress
|
||||||
style={[styles.image, { borderColor: themes[theme].borderColor }]}
|
style={[styles.image, { borderColor: themes[theme].borderColor }]}
|
||||||
source={{ uri: encodeURI(img) }}
|
source={{ uri: encodeURI(img) }}
|
||||||
|
@ -41,9 +41,9 @@ const Image = React.memo(({ img, theme }) => (
|
||||||
));
|
));
|
||||||
|
|
||||||
const ImageContainer = React.memo(({
|
const ImageContainer = React.memo(({
|
||||||
file, baseUrl, user, useMarkdown, showAttachment, getCustomEmoji, split, theme
|
file, imageUrl, baseUrl, user, useMarkdown, showAttachment, getCustomEmoji, split, theme
|
||||||
}) => {
|
}) => {
|
||||||
const img = formatAttachmentUrl(file.image_url, user.id, user.token, baseUrl);
|
const img = imageUrl || formatAttachmentUrl(file.image_url, user.id, user.token, baseUrl);
|
||||||
if (!img) {
|
if (!img) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,7 @@ const ImageContainer = React.memo(({
|
||||||
return (
|
return (
|
||||||
<Button split={split} theme={theme} onPress={onPress}>
|
<Button split={split} theme={theme} onPress={onPress}>
|
||||||
<View>
|
<View>
|
||||||
<Image img={img} theme={theme} />
|
<MessageImage img={img} theme={theme} />
|
||||||
<Markdown msg={file.description} baseUrl={baseUrl} username={user.username} getCustomEmoji={getCustomEmoji} useMarkdown={useMarkdown} theme={theme} />
|
<Markdown msg={file.description} baseUrl={baseUrl} username={user.username} getCustomEmoji={getCustomEmoji} useMarkdown={useMarkdown} theme={theme} />
|
||||||
</View>
|
</View>
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -63,13 +63,14 @@ const ImageContainer = React.memo(({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button split={split} theme={theme} onPress={onPress}>
|
<Button split={split} theme={theme} onPress={onPress}>
|
||||||
<Image img={img} theme={theme} />
|
<MessageImage img={img} theme={theme} />
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
}, (prevProps, nextProps) => equal(prevProps.file, nextProps.file) && prevProps.split === nextProps.split && prevProps.theme === nextProps.theme);
|
}, (prevProps, nextProps) => equal(prevProps.file, nextProps.file) && prevProps.split === nextProps.split && prevProps.theme === nextProps.theme);
|
||||||
|
|
||||||
ImageContainer.propTypes = {
|
ImageContainer.propTypes = {
|
||||||
file: PropTypes.object,
|
file: PropTypes.object,
|
||||||
|
imageUrl: PropTypes.string,
|
||||||
baseUrl: PropTypes.string,
|
baseUrl: PropTypes.string,
|
||||||
user: PropTypes.object,
|
user: PropTypes.object,
|
||||||
useMarkdown: PropTypes.bool,
|
useMarkdown: PropTypes.bool,
|
||||||
|
@ -80,7 +81,7 @@ ImageContainer.propTypes = {
|
||||||
};
|
};
|
||||||
ImageContainer.displayName = 'MessageImageContainer';
|
ImageContainer.displayName = 'MessageImageContainer';
|
||||||
|
|
||||||
Image.propTypes = {
|
MessageImage.propTypes = {
|
||||||
img: PropTypes.string,
|
img: PropTypes.string,
|
||||||
theme: PropTypes.string
|
theme: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,6 +10,7 @@ import MessageAvatar from './MessageAvatar';
|
||||||
import Attachments from './Attachments';
|
import Attachments from './Attachments';
|
||||||
import Urls from './Urls';
|
import Urls from './Urls';
|
||||||
import Thread from './Thread';
|
import Thread from './Thread';
|
||||||
|
import Blocks from './Blocks';
|
||||||
import Reactions from './Reactions';
|
import Reactions from './Reactions';
|
||||||
import Broadcast from './Broadcast';
|
import Broadcast from './Broadcast';
|
||||||
import Discussion from './Discussion';
|
import Discussion from './Discussion';
|
||||||
|
@ -35,6 +36,16 @@ const MessageInner = React.memo((props) => {
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (props.blocks && props.blocks.length) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<User {...props} />
|
||||||
|
<Blocks {...props} />
|
||||||
|
<Thread {...props} />
|
||||||
|
<Reactions {...props} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<User {...props} />
|
<User {...props} />
|
||||||
|
@ -139,7 +150,8 @@ Message.propTypes = {
|
||||||
};
|
};
|
||||||
|
|
||||||
MessageInner.propTypes = {
|
MessageInner.propTypes = {
|
||||||
type: PropTypes.string
|
type: PropTypes.string,
|
||||||
|
blocks: PropTypes.array
|
||||||
};
|
};
|
||||||
|
|
||||||
export default MessageTouchable;
|
export default MessageTouchable;
|
||||||
|
|
|
@ -8,7 +8,7 @@ import styles from './styles';
|
||||||
const MessageAvatar = React.memo(({
|
const MessageAvatar = React.memo(({
|
||||||
isHeader, avatar, author, baseUrl, user, small, navToRoomInfo
|
isHeader, avatar, author, baseUrl, user, small, navToRoomInfo
|
||||||
}) => {
|
}) => {
|
||||||
if (isHeader) {
|
if (isHeader && author) {
|
||||||
const navParam = {
|
const navParam = {
|
||||||
t: 'd',
|
t: 'd',
|
||||||
rid: author._id
|
rid: author._id
|
||||||
|
|
|
@ -56,7 +56,7 @@ const Reaction = React.memo(({
|
||||||
const Reactions = React.memo(({
|
const Reactions = React.memo(({
|
||||||
reactions, user, baseUrl, onReactionPress, reactionInit, onReactionLongPress, getCustomEmoji, theme
|
reactions, user, baseUrl, onReactionPress, reactionInit, onReactionLongPress, getCustomEmoji, theme
|
||||||
}) => {
|
}) => {
|
||||||
if (!reactions || reactions.length === 0) {
|
if (!Array.isArray(reactions) || reactions.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View, Text, StyleSheet } from 'react-native';
|
import {
|
||||||
|
View, Text, StyleSheet, Clipboard
|
||||||
|
} from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import FastImage from 'react-native-fast-image';
|
import FastImage from 'react-native-fast-image';
|
||||||
import Touchable from 'react-native-platform-touchable';
|
import Touchable from 'react-native-platform-touchable';
|
||||||
|
@ -10,6 +12,9 @@ import sharedStyles from '../../views/Styles';
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
import { withTheme } from '../../theme';
|
import { withTheme } from '../../theme';
|
||||||
import { withSplit } from '../../split';
|
import { withSplit } from '../../split';
|
||||||
|
import { LISTENER } from '../Toast';
|
||||||
|
import EventEmitter from '../../utils/events';
|
||||||
|
import I18n from '../../i18n';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
button: {
|
button: {
|
||||||
|
@ -82,9 +87,15 @@ const Url = React.memo(({
|
||||||
|
|
||||||
const onPress = () => openLink(url.url, theme);
|
const onPress = () => openLink(url.url, theme);
|
||||||
|
|
||||||
|
const onLongPress = () => {
|
||||||
|
Clipboard.setString(url.url);
|
||||||
|
EventEmitter.emit(LISTENER, { message: I18n.t('Copied_to_clipboard') });
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Touchable
|
<Touchable
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
|
onLongPress={onLongPress}
|
||||||
style={[
|
style={[
|
||||||
styles.button,
|
styles.button,
|
||||||
index > 0 && styles.marginTop,
|
index > 0 && styles.marginTop,
|
||||||
|
|
|
@ -16,6 +16,7 @@ class MessageContainer extends React.Component {
|
||||||
username: PropTypes.string.isRequired,
|
username: PropTypes.string.isRequired,
|
||||||
token: PropTypes.string.isRequired
|
token: PropTypes.string.isRequired
|
||||||
}),
|
}),
|
||||||
|
rid: PropTypes.string,
|
||||||
timeFormat: PropTypes.string,
|
timeFormat: PropTypes.string,
|
||||||
customThreadTimeFormat: PropTypes.string,
|
customThreadTimeFormat: PropTypes.string,
|
||||||
style: PropTypes.any,
|
style: PropTypes.any,
|
||||||
|
@ -44,11 +45,25 @@ class MessageContainer extends React.Component {
|
||||||
onReactionLongPress: PropTypes.func,
|
onReactionLongPress: PropTypes.func,
|
||||||
navToRoomInfo: PropTypes.func,
|
navToRoomInfo: PropTypes.func,
|
||||||
callJitsi: PropTypes.func,
|
callJitsi: PropTypes.func,
|
||||||
|
blockAction: PropTypes.func,
|
||||||
theme: PropTypes.string
|
theme: PropTypes.string
|
||||||
}
|
}
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
|
getCustomEmoji: () => {},
|
||||||
onLongPress: () => {},
|
onLongPress: () => {},
|
||||||
|
onReactionPress: () => {},
|
||||||
|
onDiscussionPress: () => {},
|
||||||
|
onThreadPress: () => {},
|
||||||
|
errorActionsShow: () => {},
|
||||||
|
replyBroadcast: () => {},
|
||||||
|
reactionInit: () => {},
|
||||||
|
fetchThreadName: () => {},
|
||||||
|
showAttachment: () => {},
|
||||||
|
onReactionLongPress: () => {},
|
||||||
|
navToRoomInfo: () => {},
|
||||||
|
callJitsi: () => {},
|
||||||
|
blockAction: () => {},
|
||||||
archived: false,
|
archived: false,
|
||||||
broadcast: false,
|
broadcast: false,
|
||||||
theme: 'light'
|
theme: 'light'
|
||||||
|
@ -212,10 +227,10 @@ class MessageContainer extends React.Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
item, user, style, archived, baseUrl, useRealName, broadcast, fetchThreadName, customThreadTimeFormat, showAttachment, timeFormat, useMarkdown, isReadReceiptEnabled, autoTranslateRoom, autoTranslateLanguage, navToRoomInfo, getCustomEmoji, isThreadRoom, callJitsi, theme
|
item, user, style, archived, baseUrl, useRealName, broadcast, fetchThreadName, customThreadTimeFormat, showAttachment, timeFormat, useMarkdown, isReadReceiptEnabled, autoTranslateRoom, autoTranslateLanguage, navToRoomInfo, getCustomEmoji, isThreadRoom, callJitsi, blockAction, rid, theme
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const {
|
const {
|
||||||
id, msg, ts, attachments, urls, reactions, t, avatar, u, alias, editedBy, role, drid, dcount, dlm, tmid, tcount, tlm, tmsg, mentions, channels, unread, autoTranslate: autoTranslateMessage
|
id, msg, ts, attachments, urls, reactions, t, avatar, u, alias, editedBy, role, drid, dcount, dlm, tmid, tcount, tlm, tmsg, mentions, channels, unread, blocks, autoTranslate: autoTranslateMessage
|
||||||
} = item;
|
} = item;
|
||||||
|
|
||||||
let message = msg;
|
let message = msg;
|
||||||
|
@ -229,10 +244,12 @@ class MessageContainer extends React.Component {
|
||||||
<Message
|
<Message
|
||||||
id={id}
|
id={id}
|
||||||
msg={message}
|
msg={message}
|
||||||
|
rid={rid}
|
||||||
author={u}
|
author={u}
|
||||||
ts={ts}
|
ts={ts}
|
||||||
type={t}
|
type={t}
|
||||||
attachments={attachments}
|
attachments={attachments}
|
||||||
|
blocks={blocks}
|
||||||
urls={urls}
|
urls={urls}
|
||||||
reactions={reactions}
|
reactions={reactions}
|
||||||
alias={alias}
|
alias={alias}
|
||||||
|
@ -279,6 +296,7 @@ class MessageContainer extends React.Component {
|
||||||
getCustomEmoji={getCustomEmoji}
|
getCustomEmoji={getCustomEmoji}
|
||||||
navToRoomInfo={navToRoomInfo}
|
navToRoomInfo={navToRoomInfo}
|
||||||
callJitsi={callJitsi}
|
callJitsi={callJitsi}
|
||||||
|
blockAction={blockAction}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -6,9 +6,12 @@ import en from './locales/en';
|
||||||
import ru from './locales/ru';
|
import ru from './locales/ru';
|
||||||
import fr from './locales/fr';
|
import fr from './locales/fr';
|
||||||
import de from './locales/de';
|
import de from './locales/de';
|
||||||
|
import nl from './locales/nl';
|
||||||
import ptBR from './locales/pt-BR';
|
import ptBR from './locales/pt-BR';
|
||||||
import zhCN from './locales/zh-CN';
|
import zhCN from './locales/zh-CN';
|
||||||
import ptPT from './locales/pt-PT';
|
import ptPT from './locales/pt-PT';
|
||||||
|
import esES from './locales/es-ES';
|
||||||
|
import it from './locales/it';
|
||||||
|
|
||||||
i18n.translations = {
|
i18n.translations = {
|
||||||
en,
|
en,
|
||||||
|
@ -17,7 +20,10 @@ i18n.translations = {
|
||||||
'zh-CN': zhCN,
|
'zh-CN': zhCN,
|
||||||
fr,
|
fr,
|
||||||
de,
|
de,
|
||||||
'pt-PT': ptPT
|
'pt-PT': ptPT,
|
||||||
|
'es-ES': esES,
|
||||||
|
nl,
|
||||||
|
it
|
||||||
};
|
};
|
||||||
i18n.fallbacks = true;
|
i18n.fallbacks = true;
|
||||||
|
|
||||||
|
|
|
@ -82,10 +82,10 @@ export default {
|
||||||
Add_Reaction: 'Reaktion hinzufügen',
|
Add_Reaction: 'Reaktion hinzufügen',
|
||||||
Add_Server: 'Server hinzufügen',
|
Add_Server: 'Server hinzufügen',
|
||||||
Add_users: 'Nutzer hinzufügen',
|
Add_users: 'Nutzer hinzufügen',
|
||||||
Admin_Panel: 'Admin Panel',
|
Admin_Panel: 'Admin-Panel',
|
||||||
Alert: 'Warnen',
|
Alert: 'Benachrichtigung',
|
||||||
alert: 'warnen',
|
alert: 'Benachrichtigung',
|
||||||
alerts: 'Warnungen',
|
alerts: 'Benachrichtigungen',
|
||||||
All_users_in_the_channel_can_write_new_messages: 'Alle Benutzer im Kanal können neue Nachrichten schreiben',
|
All_users_in_the_channel_can_write_new_messages: 'Alle Benutzer im Kanal können neue Nachrichten schreiben',
|
||||||
All: 'Alles',
|
All: 'Alles',
|
||||||
All_Messages: 'Alle Nachrichten',
|
All_Messages: 'Alle Nachrichten',
|
||||||
|
@ -96,11 +96,11 @@ export default {
|
||||||
announcement: 'Ankündigung',
|
announcement: 'Ankündigung',
|
||||||
Announcement: 'Ankündigung',
|
Announcement: 'Ankündigung',
|
||||||
Apply_Your_Certificate: 'Wenden Sie Ihr Zertifikat an',
|
Apply_Your_Certificate: 'Wenden Sie Ihr Zertifikat an',
|
||||||
Applying_a_theme_will_change_how_the_app_looks: 'Ein Theme zu setzen wird das Aussehen der Anwendung ändern.',
|
Applying_a_theme_will_change_how_the_app_looks: 'Das Erscheinungsbild festzulegen wird das Aussehen der Anwendung ändern.',
|
||||||
ARCHIVE: 'ARCHIV',
|
ARCHIVE: 'ARCHIV',
|
||||||
archive: 'Archiv',
|
archive: 'Archiv',
|
||||||
are_typing: 'tippen',
|
are_typing: 'tippen',
|
||||||
Are_you_sure_question_mark: 'Sind Sie sicher?',
|
Are_you_sure_question_mark: 'Bist du sicher?',
|
||||||
Are_you_sure_you_want_to_leave_the_room: 'Möchten Sie den Raum wirklich verlassen {{room}}?',
|
Are_you_sure_you_want_to_leave_the_room: 'Möchten Sie den Raum wirklich verlassen {{room}}?',
|
||||||
Audio: 'Audio',
|
Audio: 'Audio',
|
||||||
Authenticating: 'Authentifizierung',
|
Authenticating: 'Authentifizierung',
|
||||||
|
@ -121,6 +121,7 @@ export default {
|
||||||
Cancel: 'Abbrechen',
|
Cancel: 'Abbrechen',
|
||||||
changing_avatar: 'Avatar wechseln',
|
changing_avatar: 'Avatar wechseln',
|
||||||
creating_channel: 'Kanal erstellen',
|
creating_channel: 'Kanal erstellen',
|
||||||
|
creating_invite: 'Einladung erstellen',
|
||||||
Channel_Name: 'Kanal Name',
|
Channel_Name: 'Kanal Name',
|
||||||
Channels: 'Kanäle',
|
Channels: 'Kanäle',
|
||||||
Chats: 'Chats',
|
Chats: 'Chats',
|
||||||
|
@ -146,6 +147,7 @@ export default {
|
||||||
Copy: 'Kopieren',
|
Copy: 'Kopieren',
|
||||||
Permalink: 'Permalink',
|
Permalink: 'Permalink',
|
||||||
Certificate_password: 'Zertifikats-Passwort',
|
Certificate_password: 'Zertifikats-Passwort',
|
||||||
|
Clear_cache: 'Lokalen Server-Cache leeren',
|
||||||
Whats_the_password_for_your_certificate: 'Wie lautet das Passwort für Ihr Zertifikat?',
|
Whats_the_password_for_your_certificate: 'Wie lautet das Passwort für Ihr Zertifikat?',
|
||||||
Create_account: 'Ein Konto erstellen',
|
Create_account: 'Ein Konto erstellen',
|
||||||
Create_Channel: 'Kanal erstellen',
|
Create_Channel: 'Kanal erstellen',
|
||||||
|
@ -172,6 +174,7 @@ export default {
|
||||||
edit: 'bearbeiten',
|
edit: 'bearbeiten',
|
||||||
edited: 'bearbeitet',
|
edited: 'bearbeitet',
|
||||||
Edit: 'Bearbeiten',
|
Edit: 'Bearbeiten',
|
||||||
|
Edit_Invite: 'Einladung bearbeiten',
|
||||||
Email_or_password_field_is_empty: 'Das E-Mail- oder Passwortfeld ist leer',
|
Email_or_password_field_is_empty: 'Das E-Mail- oder Passwortfeld ist leer',
|
||||||
Email: 'Email',
|
Email: 'Email',
|
||||||
EMAIL: 'EMAIL',
|
EMAIL: 'EMAIL',
|
||||||
|
@ -182,6 +185,7 @@ export default {
|
||||||
Everyone_can_access_this_channel: 'Jeder kann auf diesen Kanal zugreifen',
|
Everyone_can_access_this_channel: 'Jeder kann auf diesen Kanal zugreifen',
|
||||||
erasing_room: 'lösche Raum',
|
erasing_room: 'lösche Raum',
|
||||||
Error_uploading: 'Fehler beim Hochladen',
|
Error_uploading: 'Fehler beim Hochladen',
|
||||||
|
Expiration_Days: 'läuft ab (Tage)',
|
||||||
Favorite: 'Favorisieren',
|
Favorite: 'Favorisieren',
|
||||||
Favorites: 'Favoriten',
|
Favorites: 'Favoriten',
|
||||||
Files: 'Dateien',
|
Files: 'Dateien',
|
||||||
|
@ -195,6 +199,7 @@ export default {
|
||||||
Forgot_password: 'Passwort vergessen',
|
Forgot_password: 'Passwort vergessen',
|
||||||
Forgot_Password: 'Passwort vergessen',
|
Forgot_Password: 'Passwort vergessen',
|
||||||
Full_table: 'Klicken um die ganze Tabelle anzuzeigen',
|
Full_table: 'Klicken um die ganze Tabelle anzuzeigen',
|
||||||
|
Generate_New_Link: 'Neuen Link erstellen',
|
||||||
Group_by_favorites: 'Nach Favoriten gruppieren',
|
Group_by_favorites: 'Nach Favoriten gruppieren',
|
||||||
Group_by_type: 'Gruppieren nach Typ',
|
Group_by_type: 'Gruppieren nach Typ',
|
||||||
Hide: 'Ausblenden',
|
Hide: 'Ausblenden',
|
||||||
|
@ -208,7 +213,10 @@ export default {
|
||||||
is_a_valid_RocketChat_instance: 'ist eine gültige Rocket.Chat-Instanz',
|
is_a_valid_RocketChat_instance: 'ist eine gültige Rocket.Chat-Instanz',
|
||||||
is_not_a_valid_RocketChat_instance: 'ist keine gültige Rocket.Chat-Instanz',
|
is_not_a_valid_RocketChat_instance: 'ist keine gültige Rocket.Chat-Instanz',
|
||||||
is_typing: 'tippt',
|
is_typing: 'tippt',
|
||||||
|
Invalid_or_expired_invite_token: 'Ungültiger oder abgelaufener Einladungscode',
|
||||||
Invalid_server_version: 'Der Server, zu dem Sie eine Verbindung herstellen möchten, verwendet eine Version, die von der App nicht mehr unterstützt wird: {{currentVersion}}.\n\nWir benötigen Version {{MinVersion}}.',
|
Invalid_server_version: 'Der Server, zu dem Sie eine Verbindung herstellen möchten, verwendet eine Version, die von der App nicht mehr unterstützt wird: {{currentVersion}}.\n\nWir benötigen Version {{MinVersion}}.',
|
||||||
|
Invite_Link: 'Einladungs-Link',
|
||||||
|
Invite_users: 'Benutzer einladen',
|
||||||
Join_the_community: 'Trete der Community bei',
|
Join_the_community: 'Trete der Community bei',
|
||||||
Join: 'Beitreten',
|
Join: 'Beitreten',
|
||||||
Just_invited_people_can_access_this_channel: 'Nur eingeladene Personen können auf diesen Kanal zugreifen',
|
Just_invited_people_can_access_this_channel: 'Nur eingeladene Personen können auf diesen Kanal zugreifen',
|
||||||
|
@ -224,7 +232,8 @@ export default {
|
||||||
Login: 'Anmeldung',
|
Login: 'Anmeldung',
|
||||||
Login_error: 'Ihre Zugangsdaten wurden abgelehnt! Bitte versuchen Sie es erneut.',
|
Login_error: 'Ihre Zugangsdaten wurden abgelehnt! Bitte versuchen Sie es erneut.',
|
||||||
Login_with: 'Einloggen mit',
|
Login_with: 'Einloggen mit',
|
||||||
Logout: 'Ausloggen',
|
Logout: 'Abmelden',
|
||||||
|
Max_number_of_uses: 'Maximale Anzahl der Benutzungen',
|
||||||
members: 'Mitglieder',
|
members: 'Mitglieder',
|
||||||
Members: 'Mitglieder',
|
Members: 'Mitglieder',
|
||||||
Mentioned_Messages: 'Erwähnte Nachrichten',
|
Mentioned_Messages: 'Erwähnte Nachrichten',
|
||||||
|
@ -236,6 +245,7 @@ export default {
|
||||||
Message_removed: 'Nachricht entfernt',
|
Message_removed: 'Nachricht entfernt',
|
||||||
message: 'Nachricht',
|
message: 'Nachricht',
|
||||||
messages: 'Nachrichten',
|
messages: 'Nachrichten',
|
||||||
|
Message: 'Nachricht',
|
||||||
Messages: 'Mitteilungen',
|
Messages: 'Mitteilungen',
|
||||||
Message_Reported: 'Nachricht gemeldet',
|
Message_Reported: 'Nachricht gemeldet',
|
||||||
Microphone_Permission_Message: 'Rocket.Chat benötigt Zugriff auf Ihr Mikrofon, damit Sie eine Audionachricht senden können.',
|
Microphone_Permission_Message: 'Rocket.Chat benötigt Zugriff auf Ihr Mikrofon, damit Sie eine Audionachricht senden können.',
|
||||||
|
@ -247,12 +257,14 @@ export default {
|
||||||
N_users: '{{n}} Benutzer',
|
N_users: '{{n}} Benutzer',
|
||||||
name: 'Name',
|
name: 'Name',
|
||||||
Name: 'Name',
|
Name: 'Name',
|
||||||
|
Never: 'Niemals',
|
||||||
New_Message: 'Neue Nachricht',
|
New_Message: 'Neue Nachricht',
|
||||||
New_Password: 'Neues Kennwort',
|
New_Password: 'Neues Kennwort',
|
||||||
New_Server: 'Neuer Server',
|
New_Server: 'Neuer Server',
|
||||||
Next: 'Nächster',
|
Next: 'Nächster',
|
||||||
No_files: 'Keine Dateien',
|
No_files: 'Keine Dateien',
|
||||||
No_mentioned_messages: 'Keine erwähnten Nachrichten',
|
No_limit: 'Kein Limit',
|
||||||
|
No_mentioned_messages: 'Keine Nachrichten mit Erwähnungen',
|
||||||
No_pinned_messages: 'Keine angehefteten Nachrichten',
|
No_pinned_messages: 'Keine angehefteten Nachrichten',
|
||||||
No_results_found: 'Keine Ergebnisse gefunden',
|
No_results_found: 'Keine Ergebnisse gefunden',
|
||||||
No_starred_messages: 'Keine markierten Nachrichten',
|
No_starred_messages: 'Keine markierten Nachrichten',
|
||||||
|
@ -322,6 +334,13 @@ export default {
|
||||||
Reset_password: 'Passwort zurücksetzen',
|
Reset_password: 'Passwort zurücksetzen',
|
||||||
resetting_password: 'Passwort zurücksetzen',
|
resetting_password: 'Passwort zurücksetzen',
|
||||||
RESET: 'ZURÜCKSETZEN',
|
RESET: 'ZURÜCKSETZEN',
|
||||||
|
Review_app_title: 'Gefällt dir diese App?',
|
||||||
|
Review_app_desc: 'Gib uns 5 Sterne im {{store}}',
|
||||||
|
Review_app_yes: 'Sicher!',
|
||||||
|
Review_app_no: 'Nein',
|
||||||
|
Review_app_later: 'Vielleicht später',
|
||||||
|
Review_app_unable_store: 'Kann {{store}} nicht öffnen',
|
||||||
|
Review_this_app: 'App bewerten',
|
||||||
Roles: 'Rollen',
|
Roles: 'Rollen',
|
||||||
Room_actions: 'Raumaktionen',
|
Room_actions: 'Raumaktionen',
|
||||||
Room_changed_announcement: 'Raumansage geändert in: {{announcement}} von {{userBy}}',
|
Room_changed_announcement: 'Raumansage geändert in: {{announcement}} von {{userBy}}',
|
||||||
|
@ -362,7 +381,9 @@ export default {
|
||||||
Settings: 'Einstellungen',
|
Settings: 'Einstellungen',
|
||||||
Settings_succesfully_changed: 'Einstellungen erfolgreich geändert!',
|
Settings_succesfully_changed: 'Einstellungen erfolgreich geändert!',
|
||||||
Share: 'Teilen',
|
Share: 'Teilen',
|
||||||
Share_this_app: 'Teile diese App',
|
Share_Link: 'Link teilen',
|
||||||
|
Share_this_app: 'App teilen',
|
||||||
|
Show_more: 'Mehr anzeigen …',
|
||||||
Show_Unread_Counter: 'Zähler anzeigen',
|
Show_Unread_Counter: 'Zähler anzeigen',
|
||||||
Show_Unread_Counter_Info: 'Anzahl der ungelesenen Nachrichten anzeigen',
|
Show_Unread_Counter_Info: 'Anzahl der ungelesenen Nachrichten anzeigen',
|
||||||
Sign_in_your_server: 'Melden Sie sich bei Ihrem Server an',
|
Sign_in_your_server: 'Melden Sie sich bei Ihrem Server an',
|
||||||
|
@ -385,7 +406,7 @@ export default {
|
||||||
tap_to_change_status: 'Tippen um den Status zu ändern',
|
tap_to_change_status: 'Tippen um den Status zu ändern',
|
||||||
Tap_to_view_servers_list: 'Tippen Sie hier, um die Serverliste anzuzeigen',
|
Tap_to_view_servers_list: 'Tippen Sie hier, um die Serverliste anzuzeigen',
|
||||||
Terms_of_Service: ' Nutzungsbedingungen',
|
Terms_of_Service: ' Nutzungsbedingungen',
|
||||||
Theme: 'Theme',
|
Theme: 'Erscheinungsbild',
|
||||||
The_URL_is_invalid: 'Die eingegebene URL ist ungültig. Überprüfen Sie es und versuchen Sie es bitte erneut!',
|
The_URL_is_invalid: 'Die eingegebene URL ist ungültig. Überprüfen Sie es und versuchen Sie es bitte erneut!',
|
||||||
There_was_an_error_while_action: 'Während {{action}} ist ein Fehler aufgetreten!',
|
There_was_an_error_while_action: 'Während {{action}} ist ein Fehler aufgetreten!',
|
||||||
This_room_is_blocked: 'Dieser Raum ist gesperrt',
|
This_room_is_blocked: 'Dieser Raum ist gesperrt',
|
||||||
|
@ -410,18 +431,19 @@ export default {
|
||||||
Unpin: 'Nachricht nicht mehr anheften',
|
Unpin: 'Nachricht nicht mehr anheften',
|
||||||
unread_messages: 'ungelesene',
|
unread_messages: 'ungelesene',
|
||||||
Unread: 'Ungelesen',
|
Unread: 'Ungelesen',
|
||||||
Unread_on_top: 'Ungelesen an der Spitze',
|
Unread_on_top: 'Ungelesene oben',
|
||||||
Unstar: 'von Favoriten entfernen',
|
Unstar: 'von Favoriten entfernen',
|
||||||
Updating: 'Aktualisierung …',
|
Updating: 'Aktualisierung …',
|
||||||
Uploading: 'Hochladen',
|
Uploading: 'Hochladen',
|
||||||
Upload_file_question_mark: 'Datei hochladen?',
|
Upload_file_question_mark: 'Datei hochladen?',
|
||||||
Users: 'Benutzer',
|
Users: 'Benutzer',
|
||||||
User_added_by: 'Benutzer {{userAdded}} hinzugefügt von {{userBy}}',
|
User_added_by: 'Benutzer {{userAdded}} hinzugefügt von {{userBy}}',
|
||||||
|
User_Info: 'Nutzerinfo',
|
||||||
User_has_been_key: 'Benutzer wurde {{key}}!',
|
User_has_been_key: 'Benutzer wurde {{key}}!',
|
||||||
User_is_no_longer_role_by_: '{{user}} ist nicht länger {{role}} von {{userBy}}',
|
User_is_no_longer_role_by_: '{{user}} ist nicht länger {{role}} von {{userBy}}',
|
||||||
User_muted_by: 'Benutzer {{userMuted}} von {{userBy}} stummgeschaltet',
|
User_muted_by: 'Benutzer {{userMuted}} von {{userBy}} stummgeschaltet',
|
||||||
User_removed_by: 'Benutzer {{userRemoved}} wurde von {{userBy}} entfernt',
|
User_removed_by: 'Benutzer {{userRemoved}} wurde von {{userBy}} entfernt',
|
||||||
User_sent_an_attachment: '{{user}} hat eine Anlage gesendet',
|
User_sent_an_attachment: '{{user}}: eine Datei gesendet',
|
||||||
User_unmuted_by: 'Benutzer {{userUnmuted}} nicht stummgeschaltet von {{userBy}}',
|
User_unmuted_by: 'Benutzer {{userUnmuted}} nicht stummgeschaltet von {{userBy}}',
|
||||||
User_was_set_role_by_: '{{user}} wurde von {{userBy}} {{role}} festgelegt.',
|
User_was_set_role_by_: '{{user}} wurde von {{userBy}} {{role}} festgelegt.',
|
||||||
Username_is_empty: 'Der Benutzername ist leer',
|
Username_is_empty: 'Der Benutzername ist leer',
|
||||||
|
@ -447,8 +469,13 @@ export default {
|
||||||
you_were_mentioned: 'Sie wurden erwähnt',
|
you_were_mentioned: 'Sie wurden erwähnt',
|
||||||
you: 'Sie',
|
you: 'Sie',
|
||||||
You: 'Sie',
|
You: 'Sie',
|
||||||
|
Logged_out_by_server: 'Du bist vom Server abgemeldet worden. Bitte melde dich wieder an.',
|
||||||
You_need_to_access_at_least_one_RocketChat_server_to_share_something: 'Sie benötigen Zugang zu mindestens einem Rocket.Chat-Server um etwas zu teilen.',
|
You_need_to_access_at_least_one_RocketChat_server_to_share_something: 'Sie benötigen Zugang zu mindestens einem Rocket.Chat-Server um etwas zu teilen.',
|
||||||
Your_certificate: 'Ihr Zertifikat',
|
Your_certificate: 'Ihr Zertifikat',
|
||||||
|
Your_invite_link_will_expire_after__usesLeft__uses: 'Dein Einladungs-Link wird nach {{usesLeft}} Benutzungen ablaufen.',
|
||||||
|
Your_invite_link_will_expire_on__date__or_after__usesLeft__uses: 'Dein Einladungs-Link wird am {{date}} oder nach {{usesLeft}} Benutzungen ablaufen.',
|
||||||
|
Your_invite_link_will_expire_on__date__: 'Dein Einladungs-Link wird am {{date}} ablaufen.',
|
||||||
|
Your_invite_link_will_never_expire: 'Dein Einladungs-Link wird niemals ablaufen.',
|
||||||
Version_no: 'Version: {{version}}',
|
Version_no: 'Version: {{version}}',
|
||||||
You_will_not_be_able_to_recover_this_message: 'Sie können diese Nachricht nicht wiederherstellen!',
|
You_will_not_be_able_to_recover_this_message: 'Sie können diese Nachricht nicht wiederherstellen!',
|
||||||
Change_Language: 'Sprache ändern',
|
Change_Language: 'Sprache ändern',
|
||||||
|
@ -466,5 +493,8 @@ export default {
|
||||||
Server_selection: 'Server-Auswahl',
|
Server_selection: 'Server-Auswahl',
|
||||||
Server_selection_numbers: 'Server-Auswahl 1...9',
|
Server_selection_numbers: 'Server-Auswahl 1...9',
|
||||||
Add_server: 'Server hinufügen',
|
Add_server: 'Server hinufügen',
|
||||||
New_line: 'Zeilenumbruch'
|
New_line: 'Zeilenumbruch',
|
||||||
|
You_will_be_logged_out_of_this_application: 'Du wirst in dieser Anwendung vom Server abgemeldet.',
|
||||||
|
Clear: 'Löschen',
|
||||||
|
This_will_clear_all_your_offline_data: 'Dies wird deine Offline-Daten löschen.'
|
||||||
};
|
};
|
||||||
|
|
|
@ -147,6 +147,7 @@ export default {
|
||||||
Copy: 'Copy',
|
Copy: 'Copy',
|
||||||
Permalink: 'Permalink',
|
Permalink: 'Permalink',
|
||||||
Certificate_password: 'Certificate Password',
|
Certificate_password: 'Certificate Password',
|
||||||
|
Clear_cache: 'Clear local server cache',
|
||||||
Whats_the_password_for_your_certificate: 'What\'s the password for your certificate?',
|
Whats_the_password_for_your_certificate: 'What\'s the password for your certificate?',
|
||||||
Create_account: 'Create an account',
|
Create_account: 'Create an account',
|
||||||
Create_Channel: 'Create Channel',
|
Create_Channel: 'Create Channel',
|
||||||
|
@ -244,6 +245,7 @@ export default {
|
||||||
Message_removed: 'Message removed',
|
Message_removed: 'Message removed',
|
||||||
message: 'message',
|
message: 'message',
|
||||||
messages: 'messages',
|
messages: 'messages',
|
||||||
|
Message: 'Message',
|
||||||
Messages: 'Messages',
|
Messages: 'Messages',
|
||||||
Message_Reported: 'Message reported',
|
Message_Reported: 'Message reported',
|
||||||
Microphone_Permission_Message: 'Rocket Chat needs access to your microphone so you can send audio message.',
|
Microphone_Permission_Message: 'Rocket Chat needs access to your microphone so you can send audio message.',
|
||||||
|
@ -332,6 +334,13 @@ export default {
|
||||||
Reset_password: 'Reset password',
|
Reset_password: 'Reset password',
|
||||||
resetting_password: 'resetting password',
|
resetting_password: 'resetting password',
|
||||||
RESET: 'RESET',
|
RESET: 'RESET',
|
||||||
|
Review_app_title: 'Are you enjoying this app?',
|
||||||
|
Review_app_desc: 'Give us 5 stars on {{store}}',
|
||||||
|
Review_app_yes: 'Sure!',
|
||||||
|
Review_app_no: 'No',
|
||||||
|
Review_app_later: 'Maybe later',
|
||||||
|
Review_app_unable_store: 'Unable to open {{store}}',
|
||||||
|
Review_this_app: 'Review this app',
|
||||||
Roles: 'Roles',
|
Roles: 'Roles',
|
||||||
Room_actions: 'Room actions',
|
Room_actions: 'Room actions',
|
||||||
Room_changed_announcement: 'Room announcement changed to: {{announcement}} by {{userBy}}',
|
Room_changed_announcement: 'Room announcement changed to: {{announcement}} by {{userBy}}',
|
||||||
|
@ -374,6 +383,7 @@ export default {
|
||||||
Share: 'Share',
|
Share: 'Share',
|
||||||
Share_Link: 'Share Link',
|
Share_Link: 'Share Link',
|
||||||
Share_this_app: 'Share this app',
|
Share_this_app: 'Share this app',
|
||||||
|
Show_more: 'Show more..',
|
||||||
Show_Unread_Counter: 'Show Unread Counter',
|
Show_Unread_Counter: 'Show Unread Counter',
|
||||||
Show_Unread_Counter_Info: 'Unread counter is displayed as a badge on the right of the channel, in the list',
|
Show_Unread_Counter_Info: 'Unread counter is displayed as a badge on the right of the channel, in the list',
|
||||||
Sign_in_your_server: 'Sign in your server',
|
Sign_in_your_server: 'Sign in your server',
|
||||||
|
@ -440,6 +450,8 @@ export default {
|
||||||
Username: 'Username',
|
Username: 'Username',
|
||||||
Username_or_email: 'Username or email',
|
Username_or_email: 'Username or email',
|
||||||
Validating: 'Validating',
|
Validating: 'Validating',
|
||||||
|
Verify_email_title: 'Registration Succeeded!',
|
||||||
|
Verify_email_desc: 'We have sent you an email to confirm your registration. If you do not receive an email shortly, please come back and try again.',
|
||||||
Video_call: 'Video call',
|
Video_call: 'Video call',
|
||||||
View_Original: 'View Original',
|
View_Original: 'View Original',
|
||||||
Voice_call: 'Voice call',
|
Voice_call: 'Voice call',
|
||||||
|
@ -459,6 +471,7 @@ export default {
|
||||||
you_were_mentioned: 'you were mentioned',
|
you_were_mentioned: 'you were mentioned',
|
||||||
you: 'you',
|
you: 'you',
|
||||||
You: 'You',
|
You: 'You',
|
||||||
|
Logged_out_by_server: 'You\'ve been logged out by the server. Please log in again.',
|
||||||
You_need_to_access_at_least_one_RocketChat_server_to_share_something: 'You need to access at least one Rocket.Chat server to share something.',
|
You_need_to_access_at_least_one_RocketChat_server_to_share_something: 'You need to access at least one Rocket.Chat server to share something.',
|
||||||
Your_certificate: 'Your Certificate',
|
Your_certificate: 'Your Certificate',
|
||||||
Your_invite_link_will_expire_after__usesLeft__uses: 'Your invite link will expire after {{usesLeft}} uses.',
|
Your_invite_link_will_expire_after__usesLeft__uses: 'Your invite link will expire after {{usesLeft}} uses.',
|
||||||
|
@ -482,5 +495,8 @@ export default {
|
||||||
Server_selection: 'Server selection',
|
Server_selection: 'Server selection',
|
||||||
Server_selection_numbers: 'Server selection 1...9',
|
Server_selection_numbers: 'Server selection 1...9',
|
||||||
Add_server: 'Add server',
|
Add_server: 'Add server',
|
||||||
New_line: 'New line'
|
New_line: 'New line',
|
||||||
|
You_will_be_logged_out_of_this_application: 'You will be logged out of this application.',
|
||||||
|
Clear: 'Clear',
|
||||||
|
This_will_clear_all_your_offline_data: 'This will clear all your offline data.'
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,466 @@
|
||||||
|
export default {
|
||||||
|
'1_person_reacted': '1 persona reaccionó',
|
||||||
|
'1_user': '1 usuario',
|
||||||
|
'error-action-not-allowed': '{{action}} no permitida',
|
||||||
|
'error-application-not-found': 'Aplicación no encontrada',
|
||||||
|
'error-archived-duplicate-name': 'Hay un canal archivado con nombre {{room_name}}',
|
||||||
|
'error-avatar-invalid-url': 'URL de avatar inválida: {{url}}',
|
||||||
|
'error-avatar-url-handling': 'Error durante el procesamiento de ajuste de imagen de usuario desde una dirección URL ({{url}}) para {{username}}',
|
||||||
|
'error-cant-invite-for-direct-room': 'No se puede invitar a los usuarios a salas de chat directas',
|
||||||
|
'error-could-not-change-email': 'No es posible cambiar la dirección de correo electrónico',
|
||||||
|
'error-could-not-change-name': 'No es posible cambiar el nombre',
|
||||||
|
'error-could-not-change-username': 'No es posible cambiar el nombre de usuario',
|
||||||
|
'error-delete-protected-role': 'No se puede eliminar un rol protegido',
|
||||||
|
'error-department-not-found': 'Departamento no encontrado',
|
||||||
|
'error-direct-message-file-upload-not-allowed': 'No se permite compartir archivos en mensajes directos',
|
||||||
|
'error-duplicate-channel-name': 'Ya existe un canal con nombre {{channel_name}}',
|
||||||
|
'error-email-domain-blacklisted': 'El dominio del correo electrónico está en la lista negra',
|
||||||
|
'error-email-send-failed': 'Error al enviar el correo electrónico: {{message}}',
|
||||||
|
'error-field-unavailable': '{{field}} ya está en uso :(',
|
||||||
|
'error-file-too-large': 'El archivo es demasiado grande',
|
||||||
|
'error-importer-not-defined': 'El importador no se configuró correctamente. Falta la clase de importación',
|
||||||
|
'error-input-is-not-a-valid-field': '{{input}} no es válido {{field}}',
|
||||||
|
'error-invalid-actionlink': 'Enlace de acción inválido',
|
||||||
|
'error-invalid-arguments': 'Los argumentos no son correctos',
|
||||||
|
'error-invalid-asset': 'El archivo archivo no es correcto',
|
||||||
|
'error-invalid-channel': 'El canal no es correcto.',
|
||||||
|
'error-invalid-channel-start-with-chars': 'Canal incorrecto. Debe comenzar con @ o #',
|
||||||
|
'error-invalid-custom-field': 'Invalid custom field',
|
||||||
|
'error-invalid-custom-field-name': 'Nombre inválido para el campo personalizado. Utilice sólo letras, números, guiones o guión bajo',
|
||||||
|
'error-invalid-date': 'La fecha proporcionada no es correcta.',
|
||||||
|
'error-invalid-description': 'La descipción no es correcta',
|
||||||
|
'error-invalid-domain': 'El dominio no es correcto',
|
||||||
|
'error-invalid-email': 'El email {{emai}} no es correcto',
|
||||||
|
'error-invalid-email-address': 'La dirección de correo no es correcta',
|
||||||
|
'error-invalid-file-height': 'La altura de la imagen no es correcta',
|
||||||
|
'error-invalid-file-type': 'El formato del archivo no es correcto',
|
||||||
|
'error-invalid-file-width': 'El ancho de la imagen o es correcto',
|
||||||
|
'error-invalid-from-address': 'La dirección del remitente (FROM) no es correcta.',
|
||||||
|
'error-invalid-integration': 'La integración no es correcta',
|
||||||
|
'error-invalid-message': 'El mensaje no es correcto',
|
||||||
|
'error-invalid-method': 'El método no es correcto',
|
||||||
|
'error-invalid-name': 'El nombre no es correcto',
|
||||||
|
'error-invalid-password': 'La contraseña no es correcta',
|
||||||
|
'error-invalid-redirectUri': 'La URL de redirección no es correcta.',
|
||||||
|
'error-invalid-role': 'El rol no es correcto',
|
||||||
|
'error-invalid-room': 'La sala no es correcta',
|
||||||
|
'error-invalid-room-name': 'No se puede asignar el nombre {{name}} a una sala.',
|
||||||
|
'error-invalid-room-type': 'No se puede asginar el tipo {{type}} a una sala.',
|
||||||
|
'error-invalid-settings': 'La configuración proporcionada no es correcta',
|
||||||
|
'error-invalid-subscription': 'La subscripción no es correcta',
|
||||||
|
'error-invalid-token': 'El token no es correcto',
|
||||||
|
'error-invalid-triggerWords': 'El triggerWords no es correcto',
|
||||||
|
'error-invalid-urls': 'Las URLs no son correctas',
|
||||||
|
'error-invalid-user': 'El usuario no es correcto',
|
||||||
|
'error-invalid-username': 'El nombre de usuario no es correcto',
|
||||||
|
'error-invalid-webhook-response': 'El webhook no ha respondido con código de estado HTTP 200.',
|
||||||
|
'error-message-deleting-blocked': 'No está permitido eliminar mensajes',
|
||||||
|
'error-message-editing-blocked': 'No está permitido editar mensajes',
|
||||||
|
'error-message-size-exceeded': 'El mensaje supera el tamaño máximo permitido',
|
||||||
|
'error-missing-unsubscribe-link': 'Debes proporcionar el enlace para cancelar la suscripción [unsubscribe].',
|
||||||
|
'error-no-tokens-for-this-user': 'No hay tokens asignados para el usuario',
|
||||||
|
'error-not-allowed': 'No permitido',
|
||||||
|
'error-not-authorized': 'No autorizado',
|
||||||
|
'error-push-disabled': 'El Push está desactivado',
|
||||||
|
'error-remove-last-owner': 'El usuario el único propietario existente. Debes establecer un nuevo propietario antes de eliminarlo.',
|
||||||
|
'error-role-in-use': 'No puedes eliminar el rol dado que está en uso',
|
||||||
|
'error-role-name-required': 'Debes indicar el nombre del rol',
|
||||||
|
'error-the-field-is-required': 'El campo {{field}} es obligatorio.',
|
||||||
|
'error-too-many-requests': 'Hemos recibido demasiadas peticiones. Debes esperar {{seconds}} segundos antes de continuar. Por favor, sé paciente.',
|
||||||
|
'error-user-is-not-activated': 'El usuario no está activo',
|
||||||
|
'error-user-has-no-roles': 'El usuario no tiene roles',
|
||||||
|
'error-user-limit-exceeded': 'El número de usuarios que quieres invitiar al canal #channel_name supera el límite establecido por el adminitrador.',
|
||||||
|
'error-user-not-in-room': 'El usuario no está en la sala',
|
||||||
|
'error-user-registration-custom-field': 'error-user-registration-custom-field',
|
||||||
|
'error-user-registration-disabled': 'El registro de usuario está deshabilitador',
|
||||||
|
'error-user-registration-secret': 'El registro de usuarios sólo está permitido por URL secretas',
|
||||||
|
'error-you-are-last-owner': 'El usuario el único propietario existente. Debes establecer un nuevo propietario antes de abandonar la sala.',
|
||||||
|
Actions: 'Acciones',
|
||||||
|
activity: 'actividad',
|
||||||
|
Activity: 'Actividad',
|
||||||
|
Add_Reaction: 'Reaccionar',
|
||||||
|
Add_Server: 'Añadir servidor',
|
||||||
|
Add_user: 'Añadir usuario',
|
||||||
|
Admin_Panel: 'Panel de Control',
|
||||||
|
Alert: 'Alerta',
|
||||||
|
alert: 'alerta',
|
||||||
|
alerts: 'alertas',
|
||||||
|
All_users_in_the_channel_can_write_new_messages: 'Todos los usuarios en el canal pueden escribir mensajes',
|
||||||
|
All: 'Todos',
|
||||||
|
All_Messages: 'Todos los mensajes',
|
||||||
|
Allow_Reactions: 'Permitir reacciones',
|
||||||
|
Alphabetical: 'Alfabético',
|
||||||
|
and_more: 'más',
|
||||||
|
and: 'y',
|
||||||
|
announcement: 'anuncio',
|
||||||
|
Announcement: 'Anuncio',
|
||||||
|
Apply_Your_Certificate: 'Applica tu Certificación',
|
||||||
|
Applying_a_theme_will_change_how_the_app_looks: 'Aplicando un tema modificará el aspecto de la App.',
|
||||||
|
ARCHIVE: 'FICHERO',
|
||||||
|
archive: 'Fichero',
|
||||||
|
are_typing: 'escribiendo',
|
||||||
|
Are_you_sure_question_mark: '¿Estás seguro?',
|
||||||
|
Are_you_sure_you_want_to_leave_the_room: '¿Deseas salir de la sala {{room}}?',
|
||||||
|
Audio: 'Audio',
|
||||||
|
Authenticating: 'Autenticando',
|
||||||
|
Automatic: 'Automático',
|
||||||
|
Auto_Translate: 'Auto-Translate',
|
||||||
|
Avatar_changed_successfully: 'Has cambiado tu Avatar!',
|
||||||
|
Avatar_Url: 'URL del Avatar',
|
||||||
|
Away: 'Ausente',
|
||||||
|
Back: 'Volver',
|
||||||
|
Black: 'Black',
|
||||||
|
Block_user: 'Bloquear usuario',
|
||||||
|
Broadcast_channel_Description: 'Sólo los usuario permitidos pueden escribir nuevos mensajes, el resto podrán responder sobre los mismos.',
|
||||||
|
Broadcast_Channel: 'Canal de Transmisión',
|
||||||
|
Busy: 'Ocupado',
|
||||||
|
By_proceeding_you_are_agreeing: 'Al proceder estarás de acuerdo',
|
||||||
|
Cancel_editing: 'Cancelar edición',
|
||||||
|
Cancel_recording: 'Cancelar grabación',
|
||||||
|
Cancel: 'Cancelar',
|
||||||
|
changing_avatar: 'cambiando avatar',
|
||||||
|
creating_channel: 'creando channel',
|
||||||
|
Channel_Name: 'Nombre sala',
|
||||||
|
Channels: 'Salas',
|
||||||
|
Chats: 'Chats',
|
||||||
|
Call_already_ended: 'La llamada ya ha finalizado!',
|
||||||
|
Click_to_join: 'Unirme!',
|
||||||
|
Close: 'Cerrar',
|
||||||
|
Close_emoji_selector: 'Cerrar selector de emojis',
|
||||||
|
Choose: 'Seleccionar',
|
||||||
|
Choose_from_library: 'Seleccionar desde Galería',
|
||||||
|
Choose_file: 'Seleccionar Archivo',
|
||||||
|
Code: 'Código',
|
||||||
|
Collaborative: 'Colaborativo',
|
||||||
|
Confirm: 'Confirmar',
|
||||||
|
Connect: 'Conectar',
|
||||||
|
Connect_to_a_server: 'Conectar a servidor',
|
||||||
|
Connected: 'Conectado',
|
||||||
|
connecting_server: 'conectando a servidor',
|
||||||
|
Connecting: 'Conectando...',
|
||||||
|
Contact_us: 'Contactar',
|
||||||
|
Contact_your_server_admin: 'Contacta con el administrador.',
|
||||||
|
Continue_with: 'Continuar con',
|
||||||
|
Copied_to_clipboard: 'Copiado al portapapeles!',
|
||||||
|
Copy: 'Copiar',
|
||||||
|
Permalink: 'Enlace permanente',
|
||||||
|
Certificate_password: 'Contraseña del certificado',
|
||||||
|
Whats_the_password_for_your_certificate: '¿Cuál es la contraseña de tu cerficiado?',
|
||||||
|
Create_account: 'Crear una cuenta',
|
||||||
|
Create_Channel: 'Crear Sala',
|
||||||
|
Created_snippet: 'Crear snippet',
|
||||||
|
Create_a_new_workspace: 'Crear un Workspace',
|
||||||
|
Create: 'Crear',
|
||||||
|
Dark: 'Óscuro',
|
||||||
|
Dark_level: 'Nivel',
|
||||||
|
Default: 'Por defecto',
|
||||||
|
Delete_Room_Warning: 'Eliminar a un usuario causará la eliminación de todos los mensajes creados por dicho usuario. Esta operación no se puede deshacer.',
|
||||||
|
delete: 'eliminar',
|
||||||
|
Delete: 'Eliminar',
|
||||||
|
DELETE: 'ELIMINAR',
|
||||||
|
description: 'descripción',
|
||||||
|
Description: 'Descripción',
|
||||||
|
DESKTOP_OPTIONS: 'OPCIONES DE ESCRITORIO',
|
||||||
|
Directory: 'Directorio',
|
||||||
|
Direct_Messages: 'Mensajes directo',
|
||||||
|
Disable_notifications: 'Desactivar notificaciones',
|
||||||
|
Discussions: 'Conversaciones',
|
||||||
|
Dont_Have_An_Account: '¿Todavía no tienes una cuenta?',
|
||||||
|
Do_you_have_a_certificate: '¿Tienes un certificado?',
|
||||||
|
Do_you_really_want_to_key_this_room_question_mark: '¿Deseas {{key}} de esta sala?',
|
||||||
|
edit: 'editar',
|
||||||
|
edited: 'editado',
|
||||||
|
Edit: 'Editar',
|
||||||
|
Email_or_password_field_is_empty: 'El email o la contraseña están vacios',
|
||||||
|
Email: 'Email',
|
||||||
|
EMAIL: 'EMAIL',
|
||||||
|
email: 'e-mail',
|
||||||
|
Enable_Auto_Translate: 'Permitir Auto-Translate',
|
||||||
|
Enable_markdown: 'Permitir markdown',
|
||||||
|
Enable_notifications: 'Permitir notificaciones',
|
||||||
|
Everyone_can_access_this_channel: 'Todos los usuarios pueden acceder a este canal',
|
||||||
|
erasing_room: 'eliminando sala',
|
||||||
|
Error_uploading: 'Error en la subida',
|
||||||
|
Favorite: 'Favorito',
|
||||||
|
Favorites: 'Favoritos',
|
||||||
|
Files: 'Archivos',
|
||||||
|
File_description: 'Descripción del archivo',
|
||||||
|
File_name: 'Nombre del archivo',
|
||||||
|
Finish_recording: 'Finalizar grabación',
|
||||||
|
Following_thread: 'Siguiendo hilo',
|
||||||
|
For_your_security_you_must_enter_your_current_password_to_continue: 'Por seguridad, debes introducir tu contraseña para continuar',
|
||||||
|
Forgot_my_password: 'He olvidado mi contraseña',
|
||||||
|
Forgot_password_If_this_email_is_registered: 'Si este email está registrado, te enviaremos las instrucciones para resetear tu contraseña.Si no recibes un email en un rato, vuelve aquí e inténtalo de nuevo.',
|
||||||
|
Forgot_password: 'Restablecer mi contraseña',
|
||||||
|
Forgot_Password: 'Restabler mi Contraseña',
|
||||||
|
Full_table: 'Click para ver la tabla completa',
|
||||||
|
Group_by_favorites: 'Agrupar por favoritos',
|
||||||
|
Group_by_type: 'Agrupar por tipo',
|
||||||
|
Hide: 'Ocultar',
|
||||||
|
Has_joined_the_channel: 'Se ha unido al canal',
|
||||||
|
Has_joined_the_conversation: 'Se ha unido a la conversación',
|
||||||
|
Has_left_the_channel: 'Ha dejado el canal',
|
||||||
|
IN_APP_AND_DESKTOP: 'IN-APP AND DESKTOP',
|
||||||
|
In_App_and_Desktop_Alert_info: 'Muestra un banner en la parte superior de la pantalla cuando la aplicación está abierta y muestra una notificación en el escritorio',
|
||||||
|
Invisible: 'Invisible',
|
||||||
|
Invite: 'Invitar',
|
||||||
|
is_a_valid_RocketChat_instance: 'es una instancia válida Rocket.Chat',
|
||||||
|
is_not_a_valid_RocketChat_instance: 'no es una instancia válida Rocket.Chat',
|
||||||
|
is_typing: 'escribiendo',
|
||||||
|
Invalid_server_version: 'El servidor que intentas conectar está usando una versión que ya no es soportada por la aplicación : {{currentVersion}}. Requerimos una versión {{minVersion}}.',
|
||||||
|
Join_the_community: 'Conectar con la comunidad',
|
||||||
|
Join: 'Conectar',
|
||||||
|
Just_invited_people_can_access_this_channel: 'Sólo gente invitada puede acceder a este canal.',
|
||||||
|
Language: 'Idioma',
|
||||||
|
last_message: 'último mensaje',
|
||||||
|
Leave_channel: 'Abandonar canal',
|
||||||
|
leaving_room: 'abandonando sala',
|
||||||
|
leave: 'abandonar',
|
||||||
|
Legal: 'Legal',
|
||||||
|
Light: 'Claro',
|
||||||
|
License: 'Licencia',
|
||||||
|
Livechat: 'Livechat',
|
||||||
|
Login: 'Acceder',
|
||||||
|
Login_error: '¡Sus credenciales fueron rechazadas! Por favor, inténtelo de nuevo.',
|
||||||
|
Login_with: 'Acceder con',
|
||||||
|
Logout: 'Salir',
|
||||||
|
members: 'miembros',
|
||||||
|
Members: 'Miembros',
|
||||||
|
Mentioned_Messages: 'Mensajes mencionados',
|
||||||
|
mentioned: 'mencionado',
|
||||||
|
Mentions: 'Menciones',
|
||||||
|
Message_accessibility: 'Mensaje de {{user}} a las {{time}}: {{message}}',
|
||||||
|
Message_actions: 'Acciones de mensaje',
|
||||||
|
Message_pinned: 'Mensaje fijado',
|
||||||
|
Message_removed: 'Mensaje eliminado',
|
||||||
|
message: 'mensaje',
|
||||||
|
messages: 'mensajes',
|
||||||
|
Messages: 'Mensajes',
|
||||||
|
Message_Reported: 'Mensaje notificado',
|
||||||
|
Microphone_Permission_Message: 'Rocket Chat necesita acceso a su micrófono para que pueda enviar un mensaje de audio.',
|
||||||
|
Microphone_Permission: 'Permiso de micrófono',
|
||||||
|
Mute: 'Mutear',
|
||||||
|
muted: 'muteado',
|
||||||
|
My_servers: 'Mis servidores',
|
||||||
|
N_people_reacted: 'Han reaccionado {{n}} personas',
|
||||||
|
N_users: '{{n}} usuarios',
|
||||||
|
name: 'nombre',
|
||||||
|
Name: 'Nombre',
|
||||||
|
New_Message: 'Nuevo mensaje',
|
||||||
|
New_Password: 'Nueva contraseña',
|
||||||
|
New_Server: 'Nuevo servidor',
|
||||||
|
Next: 'Siguiente',
|
||||||
|
No_files: 'No hay archivos',
|
||||||
|
No_mentioned_messages: 'No hay mensajes mencionados',
|
||||||
|
No_pinned_messages: 'No hay mensajes fijados',
|
||||||
|
No_results_found: 'No hay resultados',
|
||||||
|
No_starred_messages: 'No hay mensajes destacados',
|
||||||
|
No_thread_messages: 'No hay hilots',
|
||||||
|
No_announcement_provided: 'No se ha indicado un anuncio',
|
||||||
|
No_description_provided: 'No se ha indicado descripción',
|
||||||
|
No_topic_provided: 'No se ha indicado asunto.',
|
||||||
|
No_Message: 'Sin mensajes',
|
||||||
|
No_messages_yet: 'No hay todavía mensajes',
|
||||||
|
No_Reactions: 'No hay reacciones',
|
||||||
|
No_Read_Receipts: 'No hay confirmaciones de lectura',
|
||||||
|
Not_logged: 'No logueado',
|
||||||
|
Not_RC_Server: 'Esto no es un servidor de Rocket.Chat.\n{{contact}}',
|
||||||
|
Nothing: 'Nada',
|
||||||
|
Nothing_to_save: 'No hay nada para guardar!',
|
||||||
|
Notify_active_in_this_room: 'Notificar usuarios activos en esta sala',
|
||||||
|
Notify_all_in_this_room: 'Notificar a todos en esta sala',
|
||||||
|
Notifications: 'Notificaciones',
|
||||||
|
Notification_Duration: 'Duración notificación',
|
||||||
|
Notification_Preferences: 'Configuración de notificaciones',
|
||||||
|
Offline: 'Sin conexión',
|
||||||
|
Oops: 'Oops!',
|
||||||
|
Online: 'Conectado',
|
||||||
|
Only_authorized_users_can_write_new_messages: 'Sólo pueden escribir mensajes usuarios autorizados',
|
||||||
|
Open_emoji_selector: 'Abrir selector de emojis',
|
||||||
|
Open_Source_Communication: 'Comunicación Open Source',
|
||||||
|
Password: 'Contraseña',
|
||||||
|
Permalink_copied_to_clipboard: 'Enlace permanente copiado al portapapeles!',
|
||||||
|
Pin: 'Fijar',
|
||||||
|
Pinned_Messages: 'Mensajes fijados',
|
||||||
|
pinned: 'fijado',
|
||||||
|
Pinned: 'Fijado',
|
||||||
|
Please_enter_your_password: 'Por favor introduce tu contraseña',
|
||||||
|
Preferences: 'Configuración',
|
||||||
|
Preferences_saved: 'Configuración guardada!',
|
||||||
|
Privacy_Policy: 'Política de Privacidad',
|
||||||
|
Private_Channel: 'Canal privado',
|
||||||
|
Private_Groups: 'Grupos privados',
|
||||||
|
Private: 'Privado',
|
||||||
|
Processing: 'Procesando...',
|
||||||
|
Profile_saved_successfully: 'Perfil guardado correctamente!',
|
||||||
|
Profile: 'Perfil',
|
||||||
|
Public_Channel: 'Canal público',
|
||||||
|
Public: 'Público',
|
||||||
|
PUSH_NOTIFICATIONS: 'PUSH NOTIFICATIONS',
|
||||||
|
Push_Notifications_Alert_Info: 'Estas notificaciones se le entregan cuando la aplicación no está abierta',
|
||||||
|
Quote: 'Citar',
|
||||||
|
Reactions_are_disabled: 'Las reacciones están desactivadas',
|
||||||
|
Reactions_are_enabled: 'Las reacciones están habilitadas',
|
||||||
|
Reactions: 'Reacciones',
|
||||||
|
Read: 'Leer',
|
||||||
|
Read_Only_Channel: 'Canal de sólo lectura',
|
||||||
|
Read_Only: 'Sólo lectura ',
|
||||||
|
Read_Receipt: 'Comprobante de lectura',
|
||||||
|
Receive_Group_Mentions: 'Recibir menciones de grupo',
|
||||||
|
Receive_Group_Mentions_Info: 'Recibir menciones @all y @here',
|
||||||
|
Register: 'Registrar',
|
||||||
|
Repeat_Password: 'Repetir contraseña',
|
||||||
|
Replied_on: 'Respondido el:',
|
||||||
|
replies: 'respuestas',
|
||||||
|
reply: 'respuesta',
|
||||||
|
Reply: 'Respuesta',
|
||||||
|
Report: 'Informe',
|
||||||
|
Receive_Notification: 'Recibir notificación',
|
||||||
|
Receive_notifications_from: 'Recibir notificación de {{name}}',
|
||||||
|
Resend: 'Reenviar',
|
||||||
|
Reset_password: 'Resetear contraseña',
|
||||||
|
resetting_password: 'reseteando contraseña',
|
||||||
|
RESET: 'RESET',
|
||||||
|
Roles: 'Roles',
|
||||||
|
Room_actions: 'Acciones de sala',
|
||||||
|
Room_changed_announcement: 'El anuncio de la sala cambió a: {{announcement}} por {{userBy}}',
|
||||||
|
Room_changed_description: 'La descripción de la sala cambió a: {{description}} por {{userBy}}',
|
||||||
|
Room_changed_privacy: 'El tipo de la sala cambió a: {{type}} por {{userBy}}',
|
||||||
|
Room_changed_topic: 'El asunto de la sala cambió a: {{topic}} por {{userBy}}',
|
||||||
|
Room_Files: 'Archivos',
|
||||||
|
Room_Info_Edit: 'Editar información de la sala',
|
||||||
|
Room_Info: 'Información de la sala',
|
||||||
|
Room_Members: 'Miembros de la sala',
|
||||||
|
Room_name_changed: 'El nombre de la sala cambió a: {{name}} por {{userBy}}',
|
||||||
|
SAVE: 'SAVE',
|
||||||
|
Save_Changes: 'Guardar cambios',
|
||||||
|
Save: 'Guardar',
|
||||||
|
saving_preferences: 'guardando preferencias',
|
||||||
|
saving_profile: 'guardando perfil',
|
||||||
|
saving_settings: 'guardando confiración',
|
||||||
|
Search_Messages: 'Buscar mensajes',
|
||||||
|
Search: 'Buscar',
|
||||||
|
Search_by: 'Buscar por',
|
||||||
|
Search_global_users: 'Buscar por usuarios globales',
|
||||||
|
Search_global_users_description: 'Si lo activas, puedes buscar cualquier usuario de otras empresas o servidores.',
|
||||||
|
Seconds: '{{second}} segundos',
|
||||||
|
Select_Avatar: 'Selecciona avatar',
|
||||||
|
Select_Server: 'Selecciona servidor',
|
||||||
|
Select_Users: 'Selecciona usuarios',
|
||||||
|
Send: 'Enviar',
|
||||||
|
Send_audio_message: 'Enviar nota de audio',
|
||||||
|
Send_crash_report: 'Enviar informe errores',
|
||||||
|
Send_message: 'Enviar mensaje',
|
||||||
|
Send_to: 'Enviar a..',
|
||||||
|
Sent_an_attachment: 'Enviar un adjunto',
|
||||||
|
Server: 'Servidor',
|
||||||
|
Servers: 'Servidores',
|
||||||
|
Server_version: 'Versión servidor: {{version}}',
|
||||||
|
Set_username_subtitle: 'El nombre de usuario se utiliza para permitir que otros le mencionen en los mensajes',
|
||||||
|
Settings: 'Configuración',
|
||||||
|
Settings_succesfully_changed: 'Configuración cambiada correctamente!',
|
||||||
|
Share: 'Compartir',
|
||||||
|
Share_this_app: 'Compartir esta App',
|
||||||
|
Show_Unread_Counter: 'Mostrar contador No leídos',
|
||||||
|
Show_Unread_Counter_Info: 'El contador de no leídos se muestra como una insignia a la derecha del canal, en la lista',
|
||||||
|
Sign_in_your_server: 'Accede a tu servidor',
|
||||||
|
Sign_Up: 'Acceder',
|
||||||
|
Some_field_is_invalid_or_empty: 'Algún campo es incorrecto o vacío',
|
||||||
|
Sorting_by: 'Ordenado por {{key}}',
|
||||||
|
Sound: 'Sonido',
|
||||||
|
Star_room: 'Destacar sala',
|
||||||
|
Star: 'Destacar',
|
||||||
|
Starred_Messages: 'Mensajes destacados',
|
||||||
|
starred: 'destacado',
|
||||||
|
Starred: 'Destacado',
|
||||||
|
Start_of_conversation: 'Comiezo de la conversación',
|
||||||
|
Started_discussion: 'Comenzar una conversación:',
|
||||||
|
Started_call: 'Llamada iniciada por {{userBy}}',
|
||||||
|
Submit: 'Enviar',
|
||||||
|
Table: 'Tabla',
|
||||||
|
Take_a_photo: 'Enviar Foto',
|
||||||
|
Take_a_video: 'Enviar Vídeo',
|
||||||
|
tap_to_change_status: 'pulsa para cambiar el estado',
|
||||||
|
Tap_to_view_servers_list: 'Pulsa para ver la lista de servidores',
|
||||||
|
Terms_of_Service: 'Términos de servicio',
|
||||||
|
Theme: 'Tema',
|
||||||
|
The_URL_is_invalid: 'URL inválida o no se puede establecer una conexión segura.\n{{contact}}',
|
||||||
|
There_was_an_error_while_action: 'Ha habido un error mientras {{action}}!',
|
||||||
|
This_room_is_blocked: 'La sala está bloqueada',
|
||||||
|
This_room_is_read_only: 'Esta sala es de sólo lectura',
|
||||||
|
Thread: 'Hilo',
|
||||||
|
Threads: 'Hilos',
|
||||||
|
Timezone: 'Zona horaria',
|
||||||
|
To: 'Para',
|
||||||
|
topic: 'asunto',
|
||||||
|
Topic: 'Asunto',
|
||||||
|
Translate: 'Traducir',
|
||||||
|
Try_again: 'Intentar de nuevo',
|
||||||
|
Two_Factor_Authentication: 'Autenticación de doble factor',
|
||||||
|
Type_the_channel_name_here: 'Escribe el nombre del canal aquí',
|
||||||
|
unarchive: 'reactivar',
|
||||||
|
UNARCHIVE: 'UNARCHIVE',
|
||||||
|
Unblock_user: 'Desbloquear usuario',
|
||||||
|
Unfavorite: 'Quitar Favorito',
|
||||||
|
Unfollowed_thread: 'Dejar de seguir el Hilo',
|
||||||
|
Unmute: 'Desmutear',
|
||||||
|
unmuted: 'Desmuteado',
|
||||||
|
Unpin: 'Quitar estado Fijado',
|
||||||
|
unread_messages: 'marcar como No leído',
|
||||||
|
Unread: 'Marcar como No leído',
|
||||||
|
Unread_on_top: 'Mensajes No leídos en la parte superior',
|
||||||
|
Unstar: 'Quitar Destacado',
|
||||||
|
Updating: 'Actualizando...',
|
||||||
|
Uploading: 'Subiendo',
|
||||||
|
Upload_file_question_mark: 'Subir fichero?',
|
||||||
|
Users: 'Usuarios',
|
||||||
|
User_added_by: 'Usuario {{userAdded}} añadido por {{userBy}}',
|
||||||
|
User_has_been_key: 'El usuario ha sido {{key}}!',
|
||||||
|
User_is_no_longer_role_by_: '{{user}} ha dejado de ser {{role}} por {{userBy}}',
|
||||||
|
User_muted_by: 'Usuario {{userMuted}} muteado por {{userBy}}',
|
||||||
|
User_removed_by: 'Usuario {{userRemoved}} eliminado por {{userBy}}',
|
||||||
|
User_sent_an_attachment: '{{user}} envío un adjunto',
|
||||||
|
User_unmuted_by: 'Usuario {{userUnmuted}} desmuteado por {{userBy}}',
|
||||||
|
User_was_set_role_by_: '{{user}} ha comenzado a ser {{role}} por {{userBy}}',
|
||||||
|
Username_is_empty: 'Nombre de usuario está vacío',
|
||||||
|
Username: 'Nombre de usuario',
|
||||||
|
Username_or_email: 'Nombre de usuario o email',
|
||||||
|
Validating: 'Validando',
|
||||||
|
Video_call: 'Vídeo llamada',
|
||||||
|
View_Original: 'Ver original',
|
||||||
|
Voice_call: 'Llamada de voz',
|
||||||
|
Websocket_disabled: 'Websocket está deshabilitado para este servidor.\n{{contact}}',
|
||||||
|
Welcome: 'Bienvenido',
|
||||||
|
Welcome_to_RocketChat: 'Bienvenido a Rocket.Chat',
|
||||||
|
Whats_your_2fa: '¿Cuál es tu código 2FA?',
|
||||||
|
Without_Servers: 'Sin servidores',
|
||||||
|
Yes_action_it: 'Sí, {{action}}!',
|
||||||
|
Yesterday: 'Ayer',
|
||||||
|
You_are_in_preview_mode: 'Estás en modo Vista Previa',
|
||||||
|
You_are_offline: 'Estás desconectado',
|
||||||
|
You_can_search_using_RegExp_eg: 'Puedes usar expresiones regulares. Por ejemplo, `/^text$/i`',
|
||||||
|
You_colon: 'Tú: ',
|
||||||
|
you_were_mentioned: 'has sido mencionado',
|
||||||
|
you: 'tú',
|
||||||
|
You: 'Tú',
|
||||||
|
You_need_to_access_at_least_one_RocketChat_server_to_share_something: 'Necesita acceder al menos a un servidor Rocket.Chat para compartir algo.',
|
||||||
|
Your_certificate: 'Tu certificado',
|
||||||
|
Version_no: 'Versión: {{version}}',
|
||||||
|
You_will_not_be_able_to_recover_this_message: 'No podrás recuperar este mensaje!',
|
||||||
|
Change_Language: 'Cambiar idioma',
|
||||||
|
Crash_report_disclaimer: 'Nunca rastreamos el contenido de sus conversaciones. El informe del error sólo contiene información relevante para nosotros con el fin de identificar los problemas y solucionarlos.',
|
||||||
|
Type_message: 'Escribir mensaje',
|
||||||
|
Room_search: 'Búsqueda de salas',
|
||||||
|
Room_selection: 'Selecciona sala 1...9',
|
||||||
|
Next_room: 'Siguiente sala',
|
||||||
|
Previous_room: 'Sala anterior',
|
||||||
|
New_room: 'Nueva sala',
|
||||||
|
Upload_room: 'Subir a sala',
|
||||||
|
Search_messages: 'Buscar mensajes',
|
||||||
|
Scroll_messages: 'Scroll mensajes',
|
||||||
|
Reply_latest: 'Responder al último',
|
||||||
|
Server_selection: 'Seleccionar servidor',
|
||||||
|
Server_selection_numbers: 'Seleccionar servidor 1...9',
|
||||||
|
Add_server: 'Añadir servidor',
|
||||||
|
New_line: 'Nueva línea'
|
||||||
|
};
|
|
@ -0,0 +1,486 @@
|
||||||
|
export default {
|
||||||
|
'1_person_reacted': '1 persona ha reagito',
|
||||||
|
'1_user': '1 utente',
|
||||||
|
'error-action-not-allowed': '{{action}} non autorizzata',
|
||||||
|
'error-application-not-found': 'Applicazione non trovata',
|
||||||
|
'error-archived-duplicate-name': 'Esiste già un canale archiviato con nome {{room_name}}',
|
||||||
|
'error-avatar-invalid-url': 'URL avatar non valido: {{url}}',
|
||||||
|
'error-avatar-url-handling': 'Errore nella gestione dell\'impostazione avatar dall\'URL ({{url}}) per {{username}}',
|
||||||
|
'error-cant-invite-for-direct-room': 'Impossibile invitare l\'utente alle stanze dirette',
|
||||||
|
'error-could-not-change-email': 'Impossibile cambiare l\'indirizzo e-mail',
|
||||||
|
'error-could-not-change-name': 'Impossibile cambiare nome',
|
||||||
|
'error-could-not-change-username': 'Impossibile cambiare username',
|
||||||
|
'error-delete-protected-role': 'Impossibile eliminare un ruolo protetto',
|
||||||
|
'error-department-not-found': 'Reparto non trovato',
|
||||||
|
'error-direct-message-file-upload-not-allowed': 'La condivisione di file non è autorizzata nei messaggi diretti',
|
||||||
|
'error-duplicate-channel-name': 'Esiste già un canale con nome {{channel_name}}',
|
||||||
|
'error-email-domain-blacklisted': 'Il dominio e-mail è nella lista nera',
|
||||||
|
'error-email-send-failed': 'Errore nel tentativo di invio e-mail: {{message}}',
|
||||||
|
'error-save-image': 'Errore nel salvataggio dell\'immagine',
|
||||||
|
'error-field-unavailable': '{{field}} è già in uso :(',
|
||||||
|
'error-file-too-large': 'File troppo grande',
|
||||||
|
'error-importer-not-defined': 'L\'importatore non è stato definito correttamente: classe Import mancante.',
|
||||||
|
'error-input-is-not-a-valid-field': '{{input}} non è valido come {{field}}',
|
||||||
|
'error-invalid-actionlink': 'Link azione non valido',
|
||||||
|
'error-invalid-arguments': 'Parametri non validi',
|
||||||
|
'error-invalid-asset': 'Asset non valido',
|
||||||
|
'error-invalid-channel': 'Canale non valido.',
|
||||||
|
'error-invalid-channel-start-with-chars': 'Canale non valido. Inizia con @ o #',
|
||||||
|
'error-invalid-custom-field': 'Campo personalizzato non valido',
|
||||||
|
'error-invalid-custom-field-name': 'Nome campo personalizzato non valido. Usa solo lettere, numeri, trattini e underscore.',
|
||||||
|
'error-invalid-date': 'Data fornita non valida.',
|
||||||
|
'error-invalid-description': 'Descrizione non valida',
|
||||||
|
'error-invalid-domain': 'Dominio non valido',
|
||||||
|
'error-invalid-email': 'E-mail {{emai}} non valida',
|
||||||
|
'error-invalid-email-address': 'Indirizzo e-mail non valido',
|
||||||
|
'error-invalid-file-height': 'Altezza del file non valida',
|
||||||
|
'error-invalid-file-type': 'Tipo di file non valido',
|
||||||
|
'error-invalid-file-width': 'Larghezza del file non valida',
|
||||||
|
'error-invalid-from-address': 'Hai informato un indirizzo FROM non valido.',
|
||||||
|
'error-invalid-integration': 'Integrazione non valida',
|
||||||
|
'error-invalid-message': 'Messaggio non valido',
|
||||||
|
'error-invalid-method': 'Metodo o funzione non valida',
|
||||||
|
'error-invalid-name': 'Nome non corretto',
|
||||||
|
'error-invalid-password': 'Password non corretta',
|
||||||
|
'error-invalid-redirectUri': 'redirectUri non valido',
|
||||||
|
'error-invalid-role': 'Ruolo non valido',
|
||||||
|
'error-invalid-room': 'Stanza non valida',
|
||||||
|
'error-invalid-room-name': '{{room_name}} non è un nome di stanza valido',
|
||||||
|
'error-invalid-room-type': '{{type}} non è un tipo di stanza valido',
|
||||||
|
'error-invalid-settings': 'Impostazioni fornite non valide',
|
||||||
|
'error-invalid-subscription': 'Iscrizione non valida',
|
||||||
|
'error-invalid-token': 'Token non valido',
|
||||||
|
'error-invalid-triggerWords': 'triggerWords non valide',
|
||||||
|
'error-invalid-urls': 'URL non validi',
|
||||||
|
'error-invalid-user': 'Utente non valido',
|
||||||
|
'error-invalid-username': 'Nome utente non valido',
|
||||||
|
'error-invalid-webhook-response': 'L\'URL del webhook ha risposto con uno stato diverso da 200',
|
||||||
|
'error-message-deleting-blocked': 'Cancellazione di messaggi bloccata',
|
||||||
|
'error-message-editing-blocked': 'Modifica di messaggi bloccata',
|
||||||
|
'error-message-size-exceeded': 'La dimensione del messaggio supera Message_MaxAllowedSize',
|
||||||
|
'error-missing-unsubscribe-link': 'Devi fornire il link [unsubscribe].',
|
||||||
|
'error-no-tokens-for-this-user': 'Non ci sono token per questo utente',
|
||||||
|
'error-not-allowed': 'Non permesso',
|
||||||
|
'error-not-authorized': 'Non autorizzato',
|
||||||
|
'error-push-disabled': 'Push è disabilitato',
|
||||||
|
'error-remove-last-owner': 'Questo è l\'ultimo proprietario rimasto. Imposta un nuovo proprietario prima di rimuoverlo.',
|
||||||
|
'error-role-in-use': 'Impossibile eliminare il ruolo perchè ancora in uso',
|
||||||
|
'error-role-name-required': 'Il nome del ruolo è obbligatorio',
|
||||||
|
'error-the-field-is-required': 'Il campo {{field}} è obbligatorio.',
|
||||||
|
'error-too-many-requests': 'Errore, troppe richieste effettuate. Rallenta. Devi attendere {{seconds}} secondi prima di riprovare.',
|
||||||
|
'error-user-is-not-activated': 'L\'utente non è attivato',
|
||||||
|
'error-user-has-no-roles': 'L\'utente non ha ruoli',
|
||||||
|
'error-user-limit-exceeded': 'Il numero di utenti che stai invitando in #channel_name supera il limite imposto dall\'amministratore',
|
||||||
|
'error-user-not-in-room': 'L\'utente non è in questa stanza',
|
||||||
|
'error-user-registration-custom-field': 'error-user-registration-custom-field',
|
||||||
|
'error-user-registration-disabled': 'Registrazione utente disabilitata',
|
||||||
|
'error-user-registration-secret': 'Registrazione utente permessa solo via URL segreto',
|
||||||
|
'error-you-are-last-owner': 'Sei l\'ultimo proprietario rimasto. Imposta un nuovo proprietario prima di lasciare la stanza.',
|
||||||
|
Actions: 'Azioni',
|
||||||
|
activity: 'attività',
|
||||||
|
Activity: 'Attività',
|
||||||
|
Add_Reaction: 'Aggiungi reazione',
|
||||||
|
Add_Server: 'Aggiungi server',
|
||||||
|
Add_users: 'Aggiungi utenti',
|
||||||
|
Admin_Panel: 'Amministrazione',
|
||||||
|
Alert: 'Avviso',
|
||||||
|
alert: 'avviso',
|
||||||
|
alerts: 'avvisi',
|
||||||
|
All_users_in_the_channel_can_write_new_messages: 'Tutti gli utenti nel canale possono scrivere messaggi',
|
||||||
|
All: 'Tutti',
|
||||||
|
All_Messages: 'Tutti i messaggi',
|
||||||
|
Allow_Reactions: 'Permetti reazioni',
|
||||||
|
Alphabetical: 'Alfabetico',
|
||||||
|
and_more: 'e altro',
|
||||||
|
and: 'e',
|
||||||
|
announcement: 'annuncio',
|
||||||
|
Announcement: 'Annuncio',
|
||||||
|
Apply_Your_Certificate: 'Applica il tuo certificato',
|
||||||
|
Applying_a_theme_will_change_how_the_app_looks: 'Applicare un tema cambierà l\'aspetto dell\'app.',
|
||||||
|
ARCHIVE: 'ARCHIVIO',
|
||||||
|
archive: 'archivio',
|
||||||
|
are_typing: 'stanno scrivendo',
|
||||||
|
Are_you_sure_question_mark: 'Sei sicuro?',
|
||||||
|
Are_you_sure_you_want_to_leave_the_room: 'Sei sicuro di voler lasciare la stanza {{room}}?',
|
||||||
|
Audio: 'Audio',
|
||||||
|
Authenticating: 'Autenticazione',
|
||||||
|
Automatic: 'Automatico',
|
||||||
|
Auto_Translate: 'Traduzione automatica',
|
||||||
|
Avatar_changed_successfully: 'Avatar aggiornato correttamente!',
|
||||||
|
Avatar_Url: 'URL avatar',
|
||||||
|
Away: 'Assente',
|
||||||
|
Back: 'Indietro',
|
||||||
|
Black: 'Nero',
|
||||||
|
Block_user: 'Blocca utente',
|
||||||
|
Broadcast_channel_Description: 'Solo gli utenti autorizzati possono scrivere messaggi, ma gli altri utenti saranno in grado di rispondere',
|
||||||
|
Broadcast_Channel: 'Canale broadcast',
|
||||||
|
Busy: 'Occupato',
|
||||||
|
By_proceeding_you_are_agreeing: 'Procedendo accetti i nostri',
|
||||||
|
Cancel_editing: 'Annulla modifica',
|
||||||
|
Cancel_recording: 'Annulla registrazione',
|
||||||
|
Cancel: 'Annulla',
|
||||||
|
changing_avatar: 'cambio avatar',
|
||||||
|
creating_channel: 'creo canale',
|
||||||
|
creating_invite: 'creo invito',
|
||||||
|
Channel_Name: 'Nome canale',
|
||||||
|
Channels: 'Canali',
|
||||||
|
Chats: 'Chat',
|
||||||
|
Call_already_ended: 'Chiamata già terminata!',
|
||||||
|
Click_to_join: 'Clicca per unirti!',
|
||||||
|
Close: 'Chiudi',
|
||||||
|
Close_emoji_selector: 'Chiudi selettore emoji',
|
||||||
|
Choose: 'Scegli',
|
||||||
|
Choose_from_library: 'Scegli dalla libreria',
|
||||||
|
Choose_file: 'Scegli file',
|
||||||
|
Code: 'Codice',
|
||||||
|
Collaborative: 'Collaborativo',
|
||||||
|
Confirm: 'Conferma',
|
||||||
|
Connect: 'Connetti',
|
||||||
|
Connect_to_a_server: 'Connetti ad un server',
|
||||||
|
Connected: 'Connesso',
|
||||||
|
connecting_server: 'connessione al server',
|
||||||
|
Connecting: 'Connessione...',
|
||||||
|
Contact_us: 'Contattaci',
|
||||||
|
Contact_your_server_admin: 'Contatta l\'amministratore.',
|
||||||
|
Continue_with: 'Continua con',
|
||||||
|
Copied_to_clipboard: 'Copiato in Appunti!',
|
||||||
|
Copy: 'Copia',
|
||||||
|
Permalink: 'Permalink',
|
||||||
|
Certificate_password: 'Password certificato',
|
||||||
|
Whats_the_password_for_your_certificate: 'Qual\'è la password del tuo certificato?',
|
||||||
|
Create_account: 'Crea un account',
|
||||||
|
Create_Channel: 'Crea canale',
|
||||||
|
Created_snippet: 'Snippet creato',
|
||||||
|
Create_a_new_workspace: 'Crea un nuovo workspace',
|
||||||
|
Create: 'Crea',
|
||||||
|
Dark: 'Scuro',
|
||||||
|
Dark_level: 'Contrasto',
|
||||||
|
Default: 'Predefinito',
|
||||||
|
Delete_Room_Warning: 'Eliminare una stanza cancellerà tutti i messaggi in essa contenuti. Questa azione non può essere annullata.',
|
||||||
|
delete: 'elimina',
|
||||||
|
Delete: 'Elimina',
|
||||||
|
DELETE: 'ELIMINA',
|
||||||
|
description: 'descrizione',
|
||||||
|
Description: 'Descrizione',
|
||||||
|
DESKTOP_OPTIONS: 'OPZIONI DESKTOP',
|
||||||
|
Directory: 'Rubrica',
|
||||||
|
Direct_Messages: 'Messaggi diretti',
|
||||||
|
Disable_notifications: 'Disabilita notifiche',
|
||||||
|
Discussions: 'Discussioni',
|
||||||
|
Dont_Have_An_Account: 'Non hai un account?',
|
||||||
|
Do_you_have_a_certificate: 'Hai un certificato?',
|
||||||
|
Do_you_really_want_to_key_this_room_question_mark: 'Sei sicuro di voler {{key}} questa stanza?',
|
||||||
|
edit: 'modifica',
|
||||||
|
edited: 'modificato',
|
||||||
|
Edit: 'Modifica',
|
||||||
|
Edit_Invite: 'Modifica invito',
|
||||||
|
Email_or_password_field_is_empty: 'Il campo e-mail o password sono vuoti',
|
||||||
|
Email: 'E-mail',
|
||||||
|
EMAIL: 'E-MAIL',
|
||||||
|
email: 'e-mail',
|
||||||
|
Enable_Auto_Translate: 'Abilita traduzione automatica',
|
||||||
|
Enable_markdown: 'Abilita Markdown',
|
||||||
|
Enable_notifications: 'Abilita notifiche',
|
||||||
|
Everyone_can_access_this_channel: 'Tutti hanno accesso a questo canale',
|
||||||
|
erasing_room: 'cancellazione stanza',
|
||||||
|
Error_uploading: 'Errore nel caricamento di',
|
||||||
|
Expiration_Days: 'Scadenza (giorni)',
|
||||||
|
Favorite: 'Preferito',
|
||||||
|
Favorites: 'Preferiti',
|
||||||
|
Files: 'File',
|
||||||
|
File_description: 'Descrizione file',
|
||||||
|
File_name: 'Nome file',
|
||||||
|
Finish_recording: 'Termina registrazione',
|
||||||
|
Following_thread: 'Thread seguito',
|
||||||
|
For_your_security_you_must_enter_your_current_password_to_continue: 'Per garantire la sicurezza del tuo account, inserisci la password per continuare.',
|
||||||
|
Forgot_my_password: 'Ho dimenticato la password',
|
||||||
|
Forgot_password_If_this_email_is_registered: 'Se questa e-mail è registrata, manderemo istruzioni su come resettare la tua password. Se non ricevi nulla, torna qui e riprova di nuovo.',
|
||||||
|
Forgot_password: 'Password dimenticata',
|
||||||
|
Forgot_Password: 'Password dimenticata',
|
||||||
|
Full_table: 'Clicca per la tabella completa',
|
||||||
|
Generate_New_Link: 'Genera nuovo link',
|
||||||
|
Group_by_favorites: 'Raggruppa per preferiti',
|
||||||
|
Group_by_type: 'Raggruppa per tipo',
|
||||||
|
Hide: 'Nascondi',
|
||||||
|
Has_joined_the_channel: 'si è unito al canale',
|
||||||
|
Has_joined_the_conversation: 'si è unito alla conversazione',
|
||||||
|
Has_left_the_channel: 'ha lasciato il canale',
|
||||||
|
IN_APP_AND_DESKTOP: 'IN-APP E DESKTOP',
|
||||||
|
In_App_and_Desktop_Alert_info: 'Mostra una notifica in cima allo schermo quando l\'app è aperta, e mostra una notifica sul desktop',
|
||||||
|
Invisible: 'Invisibile',
|
||||||
|
Invite: 'Invita',
|
||||||
|
is_a_valid_RocketChat_instance: 'è un\'istanza di Rocket.Chat valida',
|
||||||
|
is_not_a_valid_RocketChat_instance: 'non è una valida istanza di Rocket.Chat',
|
||||||
|
is_typing: 'sta scrivendo',
|
||||||
|
Invalid_or_expired_invite_token: 'Token di invito non valido o scaduto',
|
||||||
|
Invalid_server_version: 'Il server a cui stai cercando di connetterti sta utilizzando una versione non più supportata dall\'app: {{currentVersion}}.\n\nVersione minima richiesta: {{minVersion}}',
|
||||||
|
Invite_Link: 'Link di invito',
|
||||||
|
Invite_users: 'Invita utenti',
|
||||||
|
Join_the_community: 'Unisciti alla community',
|
||||||
|
Join: 'Entra',
|
||||||
|
Just_invited_people_can_access_this_channel: 'Solo le persone invitate possono accedere a questo canale',
|
||||||
|
Language: 'Lingua',
|
||||||
|
last_message: 'ultimo messaggio',
|
||||||
|
Leave_channel: 'Abbandona canale',
|
||||||
|
leaving_room: 'abbandonando stanza',
|
||||||
|
leave: 'abbandona',
|
||||||
|
Legal: 'Informazioni',
|
||||||
|
Light: 'Chiaro',
|
||||||
|
License: 'Licenza',
|
||||||
|
Livechat: 'Livechat',
|
||||||
|
Login: 'Accedi',
|
||||||
|
Login_error: 'Le tue credenziali sono state rifiutate! Prova di nuovo.',
|
||||||
|
Login_with: 'Accedi con',
|
||||||
|
Logout: 'Disconnetti',
|
||||||
|
Max_number_of_uses: 'Max numero di utilizzi',
|
||||||
|
members: 'membri',
|
||||||
|
Members: 'Membri',
|
||||||
|
Mentioned_Messages: 'Messaggi menzionati',
|
||||||
|
mentioned: 'menzionato',
|
||||||
|
Mentions: 'Menzioni',
|
||||||
|
Message_accessibility: 'Messaggio da {{user}} alle {{time}}: {{message}}',
|
||||||
|
Message_actions: 'Azioni messaggio',
|
||||||
|
Message_pinned: 'Messaggio attaccato',
|
||||||
|
Message_removed: 'Messaggio rimosso',
|
||||||
|
message: 'messaggio',
|
||||||
|
messages: 'messaggi',
|
||||||
|
Messages: 'Messaggi',
|
||||||
|
Message_Reported: 'Messaggio segnalato',
|
||||||
|
Microphone_Permission_Message: 'Rocket.Chat richiede l\'accesso al microfono per inviare messaggi audio.',
|
||||||
|
Microphone_Permission: 'Permesso microfono',
|
||||||
|
Mute: 'Silenzia',
|
||||||
|
muted: 'silenziato',
|
||||||
|
My_servers: 'I miei server',
|
||||||
|
N_people_reacted: '{{n}} persone hanno reagito',
|
||||||
|
N_users: '{{n}} utenti',
|
||||||
|
name: 'nome',
|
||||||
|
Name: 'Nome',
|
||||||
|
Never: 'Mai',
|
||||||
|
New_Message: 'Nuovo messaggio',
|
||||||
|
New_Password: 'Nuova password',
|
||||||
|
New_Server: 'Nuovo server',
|
||||||
|
Next: 'Successivo',
|
||||||
|
No_files: 'Nessun file',
|
||||||
|
No_limit: 'Nessun limite',
|
||||||
|
No_mentioned_messages: 'Nessun messaggio menzionato',
|
||||||
|
No_pinned_messages: 'Nessun messaggio attaccato',
|
||||||
|
No_results_found: 'Nessun risultato',
|
||||||
|
No_starred_messages: 'Nessun messaggio preferito',
|
||||||
|
No_thread_messages: 'Nessun messaggio thread',
|
||||||
|
No_announcement_provided: 'Nessun annuncio inserito.',
|
||||||
|
No_description_provided: 'Nessuna descrizione inserita.',
|
||||||
|
No_topic_provided: 'Nessun argomento inserito.',
|
||||||
|
No_Message: 'Nessun messaggio',
|
||||||
|
No_messages_yet: 'Non ci sono ancora messaggi',
|
||||||
|
No_Reactions: 'Nessuna reazione',
|
||||||
|
No_Read_Receipts: 'Nessuna conferma di lettura',
|
||||||
|
Not_logged: 'Non loggato',
|
||||||
|
Not_RC_Server: 'Questo non è un server di Rocket.Chat.\n{{contact}}',
|
||||||
|
Nothing: 'Niente',
|
||||||
|
Nothing_to_save: 'Niente da salvare!',
|
||||||
|
Notify_active_in_this_room: 'Notifica solo gli utenti attivi in questa stanza',
|
||||||
|
Notify_all_in_this_room: 'Notifica tutti gli utenti in questa stanza',
|
||||||
|
Notifications: 'Notifiche',
|
||||||
|
Notification_Duration: 'Durata notifiche',
|
||||||
|
Notification_Preferences: 'Impostazioni notifiche',
|
||||||
|
Offline: 'Offline',
|
||||||
|
Oops: 'Oops!',
|
||||||
|
Online: 'Online',
|
||||||
|
Only_authorized_users_can_write_new_messages: 'Solo gli utenti autorizzati possono scrivere nuovi messaggi',
|
||||||
|
Open_emoji_selector: 'Apri selettore emoji',
|
||||||
|
Open_Source_Communication: 'Comunicazione open-source',
|
||||||
|
Password: 'Password',
|
||||||
|
Permalink_copied_to_clipboard: 'Permalink copiato negli appunti!',
|
||||||
|
Pin: 'Attacca',
|
||||||
|
Pinned_Messages: 'Messaggi attaccati',
|
||||||
|
pinned: 'attaccato',
|
||||||
|
Pinned: 'Attaccati',
|
||||||
|
Please_enter_your_password: 'Per favore, inserisci la tua password',
|
||||||
|
Preferences: 'Impostazioni',
|
||||||
|
Preferences_saved: 'Impostazioni salvate!',
|
||||||
|
Privacy_Policy: ' Privacy Policy',
|
||||||
|
Private_Channel: 'Canale privato',
|
||||||
|
Private_Groups: 'Gruppi privati',
|
||||||
|
Private: 'Privato',
|
||||||
|
Processing: 'Elaborazione...',
|
||||||
|
Profile_saved_successfully: 'Profilo salvato correttamente!',
|
||||||
|
Profile: 'Profilo',
|
||||||
|
Public_Channel: 'Canale pubblico',
|
||||||
|
Public: 'Pubblico',
|
||||||
|
PUSH_NOTIFICATIONS: 'NOTIFICHE PUSH',
|
||||||
|
Push_Notifications_Alert_Info: 'Queste notifiche ti vengono recapitate quando l\'app non è aperta',
|
||||||
|
Quote: 'Cita',
|
||||||
|
Reactions_are_disabled: 'Le reazioni sono disabilitate',
|
||||||
|
Reactions_are_enabled: 'Le reazioni sono abilitate',
|
||||||
|
Reactions: 'Reazioni',
|
||||||
|
Read: 'Letto',
|
||||||
|
Read_Only_Channel: 'Canale in sola lettura',
|
||||||
|
Read_Only: 'Sola lettura',
|
||||||
|
Read_Receipt: 'Conferma di lettura',
|
||||||
|
Receive_Group_Mentions: 'Ricevi menzioni di gruppo',
|
||||||
|
Receive_Group_Mentions_Info: 'Ricevi menzioni @all e @here',
|
||||||
|
Register: 'Registrati',
|
||||||
|
Repeat_Password: 'Conferma password',
|
||||||
|
Replied_on: 'Risposto il:',
|
||||||
|
replies: 'risposte',
|
||||||
|
reply: 'risposta',
|
||||||
|
Reply: 'Rispondi',
|
||||||
|
Report: 'Segnala',
|
||||||
|
Receive_Notification: 'Ricevi notifiche',
|
||||||
|
Receive_notifications_from: 'Ricevi notifiche da {{name}}',
|
||||||
|
Resend: 'Invia di nuovo',
|
||||||
|
Reset_password: 'Ripristina password',
|
||||||
|
resetting_password: 'ripristinando password',
|
||||||
|
RESET: 'RIPRISTINA',
|
||||||
|
Roles: 'Ruoli',
|
||||||
|
Room_actions: 'Azioni stanza',
|
||||||
|
Room_changed_announcement: 'Annuncio stanza cambiato in: {{announcement}} da {{userBy}}',
|
||||||
|
Room_changed_description: 'Descrizione stanza cambiata in: {{description}} da {{userBy}}',
|
||||||
|
Room_changed_privacy: 'Tipo stanza cambiato in: {{type}} da {{userBy}}',
|
||||||
|
Room_changed_topic: 'Argomento stanza cambiato in: {{topic}} da {{userBy}}',
|
||||||
|
Room_Files: 'File stanza',
|
||||||
|
Room_Info_Edit: 'Modifica informazioni stanza',
|
||||||
|
Room_Info: 'Informazioni stanza',
|
||||||
|
Room_Members: 'Membri stanza',
|
||||||
|
Room_name_changed: 'Nome stanza cambiato in: {{name}} da {{userBy}}',
|
||||||
|
SAVE: 'SALVA',
|
||||||
|
Save_Changes: 'Salva cambiamenti',
|
||||||
|
Save: 'Salva',
|
||||||
|
saving_preferences: 'salvataggio impostazioni',
|
||||||
|
saving_profile: 'salvataggio profilo',
|
||||||
|
saving_settings: 'salvataggio impostazioni',
|
||||||
|
saved_to_gallery: 'Salvato in Galleria',
|
||||||
|
Search_Messages: 'Cerca messaggi',
|
||||||
|
Search: 'Cerca',
|
||||||
|
Search_by: 'Cerca per',
|
||||||
|
Search_global_users: 'Cerca utenti globali',
|
||||||
|
Search_global_users_description: 'Se attivi questa opzione, puoi cercare qualsiasi utente da altre aziende o server.',
|
||||||
|
Seconds: '{{second}} secondi',
|
||||||
|
Select_Avatar: 'Seleziona avatar',
|
||||||
|
Select_Server: 'Seleziona server',
|
||||||
|
Select_Users: 'Seleziona utenti',
|
||||||
|
Send: 'Invia',
|
||||||
|
Send_audio_message: 'Invia messaggio audio',
|
||||||
|
Send_crash_report: 'Invia crash report',
|
||||||
|
Send_message: 'Invia messaggio',
|
||||||
|
Send_to: 'Invia a...',
|
||||||
|
Sent_an_attachment: 'Inviato un allegato',
|
||||||
|
Server: 'Server',
|
||||||
|
Servers: 'Servers',
|
||||||
|
Server_version: 'Versione server: {{version}}',
|
||||||
|
Set_username_subtitle: 'Il nome utente viene utilizzato per permettere ad altri di menzionarti nei messaggi',
|
||||||
|
Settings: 'Impostazioni',
|
||||||
|
Settings_succesfully_changed: 'Impostazioni modificate correttamente!',
|
||||||
|
Share: 'Condividi',
|
||||||
|
Share_Link: 'Condividi link',
|
||||||
|
Share_this_app: 'Condividi questa app',
|
||||||
|
Show_Unread_Counter: 'Mostra contatore messaggi non letti',
|
||||||
|
Show_Unread_Counter_Info: 'Il contatore viene mostrato come un\'etichetta alla destra del canale, nella lista',
|
||||||
|
Sign_in_your_server: 'Accedi al tuo server',
|
||||||
|
Sign_Up: 'Registrati',
|
||||||
|
Some_field_is_invalid_or_empty: 'Un campo non è valido o è vuoto',
|
||||||
|
Sorting_by: 'Ordina per {{key}}',
|
||||||
|
Sound: 'Suono',
|
||||||
|
Star_room: 'Aggiungi stanza ai preferiti',
|
||||||
|
Star: 'Aggiungi ai preferiti',
|
||||||
|
Starred_Messages: 'Messaggi preferiti',
|
||||||
|
starred: 'preferiti',
|
||||||
|
Starred: 'Preferiti',
|
||||||
|
Start_of_conversation: 'Inizio della conversazione',
|
||||||
|
Started_discussion: 'Discussione iniziata:',
|
||||||
|
Started_call: 'Chiamata iniziata da {{userBy}}',
|
||||||
|
Submit: 'Invia',
|
||||||
|
Table: 'Tabella',
|
||||||
|
Take_a_photo: 'Scatta una foto',
|
||||||
|
Take_a_video: 'Registra un video',
|
||||||
|
tap_to_change_status: 'tocca per cambiare stato',
|
||||||
|
Tap_to_view_servers_list: 'Tocca per vedere la lista server',
|
||||||
|
Terms_of_Service: ' Termini di servizio ',
|
||||||
|
Theme: 'Tema',
|
||||||
|
The_URL_is_invalid: 'URL non valido o errore nello stabilimento di una connessione sicura.\n{{contact}}',
|
||||||
|
There_was_an_error_while_action: 'Si è verificato un errore nel {{action}}!',
|
||||||
|
This_room_is_blocked: 'Questa stanza è bloccata',
|
||||||
|
This_room_is_read_only: 'Questa stanza è in sola lettura',
|
||||||
|
Thread: 'Thread',
|
||||||
|
Threads: 'Threads',
|
||||||
|
Timezone: 'Fuso orario',
|
||||||
|
To: 'A',
|
||||||
|
topic: 'argomento',
|
||||||
|
Topic: 'Argomento',
|
||||||
|
Translate: 'Traduci',
|
||||||
|
Try_again: 'Riprova',
|
||||||
|
Two_Factor_Authentication: 'Autenticazione a due fattori',
|
||||||
|
Type_the_channel_name_here: 'Scrivi il nome del canale qui',
|
||||||
|
unarchive: 'rimuovi dall\'archivio',
|
||||||
|
UNARCHIVE: 'RIMUOVI DALL\'ARCHIVIO',
|
||||||
|
Unblock_user: 'Sblocca utente',
|
||||||
|
Unfavorite: 'Rimuovi dai preferiti',
|
||||||
|
Unfollowed_thread: 'Non segui più il thread',
|
||||||
|
Unmute: 'Attiva notifiche',
|
||||||
|
unmuted: 'notifiche attivate',
|
||||||
|
Unpin: 'Stacca',
|
||||||
|
unread_messages: 'non letti',
|
||||||
|
Unread: 'Non letto',
|
||||||
|
Unread_on_top: 'Non letti sopra',
|
||||||
|
Unstar: 'Rimuovi dai preferiti',
|
||||||
|
Updating: 'Aggiornamento...',
|
||||||
|
Uploading: 'Caricamento',
|
||||||
|
Upload_file_question_mark: 'Carica file?',
|
||||||
|
Users: 'Utenti',
|
||||||
|
User_added_by: 'Utente {{userAdded}} aggiunto da {{userBy}}',
|
||||||
|
User_Info: 'Informazioni utente',
|
||||||
|
User_has_been_key: 'Utente {{key}}!',
|
||||||
|
User_is_no_longer_role_by_: '{{user}} non è più {{role}} da {{userBy}}',
|
||||||
|
User_muted_by: 'Utente {{userMuted}} silenziato da {{userBy}}',
|
||||||
|
User_removed_by: 'Utente {{userRemoved}} rimosso da {{userBy}}',
|
||||||
|
User_sent_an_attachment: '{{user}} ha inviato un allegato',
|
||||||
|
User_unmuted_by: 'Utente {{userUnmuted}} de-silenziato da {{userBy}}',
|
||||||
|
User_was_set_role_by_: '{{user}} è stato impostato come {{role}} da {{userBy}}',
|
||||||
|
Username_is_empty: 'Username vuoto',
|
||||||
|
Username: 'Username',
|
||||||
|
Username_or_email: 'Username o email',
|
||||||
|
Validating: 'Validazione',
|
||||||
|
Video_call: 'Videochiamata',
|
||||||
|
View_Original: 'Mostra originale',
|
||||||
|
Voice_call: 'Chiamata vocale',
|
||||||
|
Websocket_disabled: 'Websocket è disabilitato per questo server.\n{{contact}}',
|
||||||
|
Welcome: 'Benvenuto',
|
||||||
|
Welcome_to_RocketChat: 'Benvenuto in Rocket.Chat',
|
||||||
|
Whats_your_2fa: 'Qual\'è il tuo codice 2FA?',
|
||||||
|
Without_Servers: 'Senza server',
|
||||||
|
Write_External_Permission_Message: 'Rocket.Chat ha bisogno dell\'accesso alla galleria per salvare le immagini.',
|
||||||
|
Write_External_Permission: 'Permesso galleria',
|
||||||
|
Yes_action_it: 'Sì, {{action}}!',
|
||||||
|
Yesterday: 'Ieri',
|
||||||
|
You_are_in_preview_mode: 'Sei in modalità anteprima',
|
||||||
|
You_are_offline: 'Sei offline',
|
||||||
|
You_can_search_using_RegExp_eg: 'Puoi usare espressioni regolari. es. `/^testo$/i`',
|
||||||
|
You_colon: 'Tu: ',
|
||||||
|
you_were_mentioned: 'sei stato menzionato',
|
||||||
|
you: 'tu',
|
||||||
|
You: 'Tu',
|
||||||
|
You_need_to_access_at_least_one_RocketChat_server_to_share_something: 'Devi accedere ad almeno un server Rocket.Chat prima di condividere qualcosa.',
|
||||||
|
Your_certificate: 'Il tuo certificato',
|
||||||
|
Your_invite_link_will_expire_after__usesLeft__uses: 'Il tuo link di invito scadrà dopo {{usesLeft}} utilizzi.',
|
||||||
|
Your_invite_link_will_expire_on__date__or_after__usesLeft__uses: 'Il tuo link di invito scadrà il {{date}} oppure dopo {{usesLeft}} utilizzi.',
|
||||||
|
Your_invite_link_will_expire_on__date__: 'Il tuo link di invito scadrà il {{date}}.',
|
||||||
|
Your_invite_link_will_never_expire: 'Il tuo link di invito non scadrà mai.',
|
||||||
|
Version_no: 'Versione: {{version}}',
|
||||||
|
You_will_not_be_able_to_recover_this_message: 'Non sarai in grado di ripristinare questo messaggio!',
|
||||||
|
Change_Language: 'Cambia lingua',
|
||||||
|
Crash_report_disclaimer: 'Non registreremo mai il contenuto delle tue chat. Il crash report contiene solo informazioni necessarie per l\'identificazione e la risoluzione dei problemi.',
|
||||||
|
Type_message: 'Scrivi messaggio',
|
||||||
|
Room_search: 'Ricerca stanze',
|
||||||
|
Room_selection: 'Selezione stanza 1...9',
|
||||||
|
Next_room: 'Prossima stanza',
|
||||||
|
Previous_room: 'Stanza precedente',
|
||||||
|
New_room: 'Nuova stanza',
|
||||||
|
Upload_room: 'Carica nella stanza',
|
||||||
|
Search_messages: 'Cerca messaggi',
|
||||||
|
Scroll_messages: 'Scroll messaggi',
|
||||||
|
Reply_latest: 'Rispondi all\'ultimo',
|
||||||
|
Server_selection: 'Selezione server',
|
||||||
|
Server_selection_numbers: 'Selezione server 1...9',
|
||||||
|
Add_server: 'Aggiungi server',
|
||||||
|
New_line: 'Nuova linea'
|
||||||
|
};
|
|
@ -0,0 +1,493 @@
|
||||||
|
export default {
|
||||||
|
'1_person_reacted': '1 persoon heeft gereageerd',
|
||||||
|
'1_user': '1 gebruiker',
|
||||||
|
'error-action-not-allowed': '{{actie}} is niet toegestaan',
|
||||||
|
'error-application-not-found': 'Applicatie niet gevonden',
|
||||||
|
'error-archived-duplicate-name': 'Er is een gearchiveerd kanaal met de naam {{room_name}}',
|
||||||
|
'error-avatar-invalid-url': 'Foutieve avatar URL: {{url}}',
|
||||||
|
'error-avatar-url-handling': 'Fout tijdens verwerken avatar instellingen vanaf een URL({{url}}) for {{username}}',
|
||||||
|
'error-cant-invite-for-direct-room': 'Kan gebruikers niet in directe kamers toevoegen',
|
||||||
|
'error-could-not-change-email': 'Kon email niet veranderen',
|
||||||
|
'error-could-not-change-name': 'Kon naam niet veranderen',
|
||||||
|
'error-could-not-change-username': 'Kon gebruikersnaam niet veranderen',
|
||||||
|
'error-delete-protected-role': 'Beveiligde rollen kunnen niet verwijderd worden.',
|
||||||
|
'error-department-not-found': 'Afdeling niet gevonden',
|
||||||
|
'error-direct-message-file-upload-not-allowed': 'Delen van bestanden niet toegestaan in directe berichten',
|
||||||
|
'error-duplicate-channel-name': 'Een kanaal met de naam {{channel_name}} bestaat',
|
||||||
|
'error-email-domain-blacklisted': 'Het email domein is blacklisted',
|
||||||
|
'error-email-send-failed': 'Fout tijdens verzenden van email: {{message}}',
|
||||||
|
'error-save-image': 'Fout tijdens opslaan afbeelding',
|
||||||
|
'error-field-unavailable': '{{field}} is alr in gebruik :(',
|
||||||
|
'error-file-too-large': 'Bestand is te groot',
|
||||||
|
'error-importer-not-defined': 'De importer is niet goed gedefinieerd, het mist de Import class.',
|
||||||
|
'error-input-is-not-a-valid-field': '{{input}} is geen geldig {{field}}',
|
||||||
|
'error-invalid-actionlink': 'Ongeldige action link',
|
||||||
|
'error-invalid-arguments': 'Ongeldige argumenten',
|
||||||
|
'error-invalid-asset': 'Ongeldig asset',
|
||||||
|
'error-invalid-channel': 'Ongeldig channel.',
|
||||||
|
'error-invalid-channel-start-with-chars': 'Ongeldig channel. Begin met @ of #',
|
||||||
|
'error-invalid-custom-field': 'Ongeldig custom veld',
|
||||||
|
'error-invalid-custom-field-name': 'Ongeldige custom veld naam. Gebruik alleen letters, cijfers, - of _.',
|
||||||
|
'error-invalid-date': 'Ongeldige datum opgegeven.',
|
||||||
|
'error-invalid-description': 'Ongeldige beschrijving',
|
||||||
|
'error-invalid-domain': 'Ongeldig domein',
|
||||||
|
'error-invalid-email': 'Ongeldige email {{emai}}',
|
||||||
|
'error-invalid-email-address': 'Ongeldig emailadres',
|
||||||
|
'error-invalid-file-height': 'Ongeldige file height',
|
||||||
|
'error-invalid-file-type': 'Ongeldig bestandstype',
|
||||||
|
'error-invalid-file-width': 'Ongeldige file width',
|
||||||
|
'error-invalid-from-address': 'Een ongeldig FROM adres is ingevuld.',
|
||||||
|
'error-invalid-integration': 'Ongeldige integration',
|
||||||
|
'error-invalid-message': 'Ongeldige message',
|
||||||
|
'error-invalid-method': 'Ongeldige method',
|
||||||
|
'error-invalid-name': 'Ongeldige naam',
|
||||||
|
'error-invalid-password': 'Ongeldig password',
|
||||||
|
'error-invalid-redirectUri': 'Ongeldige redirectUri',
|
||||||
|
'error-invalid-role': 'Ongeldige role',
|
||||||
|
'error-invalid-room': 'Ongeldige kamer',
|
||||||
|
'error-invalid-room-name': '{{room_name}} is geen geldige kamernaam',
|
||||||
|
'error-invalid-room-type': '{{type}} is geen geldig kamertype.',
|
||||||
|
'error-invalid-settings': 'Ongeldige instellingen ingevuld',
|
||||||
|
'error-invalid-subscription': 'Ongeldige subscription',
|
||||||
|
'error-invalid-token': 'Ongeldig token',
|
||||||
|
'error-invalid-triggerWords': 'Ongeldige triggerWords',
|
||||||
|
'error-invalid-urls': 'Ongeldige URLs',
|
||||||
|
'error-invalid-user': 'Ongeldige user',
|
||||||
|
'error-invalid-username': 'Ongeldige username',
|
||||||
|
'error-invalid-webhook-response': 'De webhook URL antwoorde met een andere status dan 200',
|
||||||
|
'error-message-deleting-blocked': 'Berichten verwijderen is geblokkeerd.',
|
||||||
|
'error-message-editing-blocked': 'Berichten aanpassen is geblokkeerd.',
|
||||||
|
'error-message-size-exceeded': 'Berichtgrootte is meer dan Message_MaxAllowedSize',
|
||||||
|
'error-missing-unsubscribe-link': 'De [unsubscribe] link moet gegeven worden.',
|
||||||
|
'error-no-tokens-for-this-user': 'Er zijn geen tokens voor deze user',
|
||||||
|
'error-not-allowed': 'Niet toegestaan',
|
||||||
|
'error-not-authorized': 'Niet gemachtigd',
|
||||||
|
'error-push-disabled': 'Push staat uit',
|
||||||
|
'error-remove-last-owner': 'Dit is de laatste eigenaar. Kies een nieuwe eigenaar voor je deze verwijderd.',
|
||||||
|
'error-role-in-use': 'Kan rol niet verwijderen omdat hij in gebruik is',
|
||||||
|
'error-role-name-required': 'Rol naam verplicht',
|
||||||
|
'error-the-field-is-required': 'Het veld {{field}} is verplicht.',
|
||||||
|
'error-too-many-requests': 'Error, te veel requests. Doe alsjeblieft rustig aan. Je moet {{seconds}} wachten voor je het opnieuw kan proberen.',
|
||||||
|
'error-user-is-not-activated': 'Gebruiker is niet geactiveerd',
|
||||||
|
'error-user-has-no-roles': 'Gebruiker heeft geen rollen',
|
||||||
|
'error-user-limit-exceeded': 'De hoeveelheid gebruikers die je probeert uit te nodigen voor #channel_name is meer dan het limiet wat de admin gekozen heeft',
|
||||||
|
'error-user-not-in-room': 'Gebruiker is niet in deze kamer',
|
||||||
|
'error-user-registration-custom-field': 'error-user-registration-custom-field',
|
||||||
|
'error-user-registration-disabled': 'Registratie van gebruikers staat uit',
|
||||||
|
'error-user-registration-secret': 'Registratie van gebruikers kan alleen via Secret URL',
|
||||||
|
'error-you-are-last-owner': 'Je bent de laatste eigenaar. Kies eerst een nieuwe voor je de kamer verlaat.',
|
||||||
|
Actions: 'Acties',
|
||||||
|
activity: 'activiteit',
|
||||||
|
Activity: 'Activiteit',
|
||||||
|
Add_Reaction: 'Voeg reactie toe',
|
||||||
|
Add_Server: 'Voeg server toe',
|
||||||
|
Add_users: 'Voeg gebruikers toe',
|
||||||
|
Admin_Panel: 'Admin Paneel',
|
||||||
|
Alert: 'Alert',
|
||||||
|
alert: 'alert',
|
||||||
|
alerts: 'alerts',
|
||||||
|
All_users_in_the_channel_can_write_new_messages: 'Alle gebruikers in het kanaal kunnen nieuwe berichten sturen',
|
||||||
|
All: 'Alle',
|
||||||
|
All_Messages: 'Alle Berichten',
|
||||||
|
Allow_Reactions: 'Sta reacties toe',
|
||||||
|
Alphabetical: 'Alfabetisch',
|
||||||
|
and_more: 'en meer',
|
||||||
|
and: 'en',
|
||||||
|
announcement: 'aankondiging',
|
||||||
|
Announcement: 'Aankondiging',
|
||||||
|
Apply_Your_Certificate: 'Gebruik je certificaat',
|
||||||
|
Applying_a_theme_will_change_how_the_app_looks: 'Een thema toepassen verandert de looks van de app.',
|
||||||
|
ARCHIVE: 'ARCHIVEER',
|
||||||
|
archive: 'archiveer',
|
||||||
|
are_typing: 'zijn aan het typen',
|
||||||
|
Are_you_sure_question_mark: 'Weet je het zeker?',
|
||||||
|
Are_you_sure_you_want_to_leave_the_room: 'Weet je zeker dat je de kamer {{room}} wil verlaten?',
|
||||||
|
Audio: 'Audio',
|
||||||
|
Authenticating: 'Authenticating',
|
||||||
|
Automatic: 'Automatisch',
|
||||||
|
Auto_Translate: 'Auto-Vertalen',
|
||||||
|
Avatar_changed_successfully: 'Avatar succesvol aangepast!',
|
||||||
|
Avatar_Url: 'Avatar URL',
|
||||||
|
Away: 'Weg',
|
||||||
|
Back: 'Terug',
|
||||||
|
Black: 'Zwart',
|
||||||
|
Block_user: 'Blokkeer gebruiker',
|
||||||
|
Broadcast_channel_Description: 'Alleen toegestane gebruikers kunnen nieuwe berichten sturen, maar iedereen kan reageren',
|
||||||
|
Broadcast_Channel: 'Broadcast Kanaal',
|
||||||
|
Busy: 'Bezig',
|
||||||
|
By_proceeding_you_are_agreeing: 'Door verder te gaan ga je akkoord met onze',
|
||||||
|
Cancel_editing: 'Stop bewerken',
|
||||||
|
Cancel_recording: 'Stop opnemen',
|
||||||
|
Cancel: 'Stop',
|
||||||
|
changing_avatar: 'avatar aan het veranderen',
|
||||||
|
creating_channel: 'kanaal aan het maken',
|
||||||
|
creating_invite: 'uitnodiging maken',
|
||||||
|
Channel_Name: 'Kanaal Name',
|
||||||
|
Channels: 'Kanalen',
|
||||||
|
Chats: 'Chats',
|
||||||
|
Call_already_ended: 'Gesprek al beeïndigd!',
|
||||||
|
Click_to_join: 'Klik om lid te worden!',
|
||||||
|
Close: 'Sluiten',
|
||||||
|
Close_emoji_selector: 'Sluit emoji selector',
|
||||||
|
Choose: 'Kies',
|
||||||
|
Choose_from_library: 'Kies uit bibliotheek',
|
||||||
|
Choose_file: 'Kies bestand',
|
||||||
|
Code: 'Code',
|
||||||
|
Collaborative: 'Samenwerkend',
|
||||||
|
Confirm: 'Bevestig',
|
||||||
|
Connect: 'Verbind',
|
||||||
|
Connect_to_a_server: 'Verbind met een server',
|
||||||
|
Connected: 'Verbonden',
|
||||||
|
connecting_server: 'Verbonden met een server',
|
||||||
|
Connecting: 'Aan het verbinden...',
|
||||||
|
Contact_us: 'Contact opnemen',
|
||||||
|
Contact_your_server_admin: 'Neem contact op met je server admin.',
|
||||||
|
Continue_with: 'Ga verder met',
|
||||||
|
Copied_to_clipboard: 'Gekopïeerd naar klembord!',
|
||||||
|
Copy: 'Kopïeer',
|
||||||
|
Permalink: 'Permalink',
|
||||||
|
Certificate_password: 'Certificate Password',
|
||||||
|
Whats_the_password_for_your_certificate: 'Wat is het wachtwoord voor je certificate?',
|
||||||
|
Create_account: 'Maak een account',
|
||||||
|
Create_Channel: 'Maak een kanaal',
|
||||||
|
Created_snippet: 'Snippet gemaakt',
|
||||||
|
Create_a_new_workspace: 'Een nieuwe workspace maken',
|
||||||
|
Create: 'Maken',
|
||||||
|
Dark: 'Donker',
|
||||||
|
Dark_level: 'Donker niveau',
|
||||||
|
Default: 'Standaard',
|
||||||
|
Delete_Room_Warning: 'Een kamer verwijderen verwijdert alle berichten erin. Dit kan niet ongedaan gemaakt worden.',
|
||||||
|
delete: 'delete',
|
||||||
|
Delete: 'Delete',
|
||||||
|
DELETE: 'DELETE',
|
||||||
|
description: 'beschrijving',
|
||||||
|
Description: 'Beschrijving',
|
||||||
|
DESKTOP_OPTIONS: 'DESKTOP OPTIES',
|
||||||
|
Directory: 'Map',
|
||||||
|
Direct_Messages: 'Directe berichten',
|
||||||
|
Disable_notifications: 'Zet notificaties uit',
|
||||||
|
Discussions: 'Discussies',
|
||||||
|
Dont_Have_An_Account: 'Heb je geen account?',
|
||||||
|
Do_you_have_a_certificate: 'Heb je een certificate?',
|
||||||
|
Do_you_really_want_to_key_this_room_question_mark: 'Wil je deze kamer echt {{key}}?',
|
||||||
|
edit: 'bewerk',
|
||||||
|
edited: 'bewerkt',
|
||||||
|
Edit: 'Bewerk',
|
||||||
|
Edit_Invite: 'Bewerk uitnodiging',
|
||||||
|
Email_or_password_field_is_empty: 'Email of wachtwoord veld is leeg',
|
||||||
|
Email: 'Email',
|
||||||
|
EMAIL: 'EMAIL',
|
||||||
|
email: 'e-mail',
|
||||||
|
Enable_Auto_Translate: 'Zet Auto-Translate aan',
|
||||||
|
Enable_markdown: 'Zet markdown aan',
|
||||||
|
Enable_notifications: 'Zet notifications aan',
|
||||||
|
Everyone_can_access_this_channel: 'Iedereen kan bij dit kanaal',
|
||||||
|
erasing_room: 'kamer legen',
|
||||||
|
Error_uploading: 'Error tijdens uploaden',
|
||||||
|
Expiration_Days: 'Vervalt in (Dagen)',
|
||||||
|
Favorite: 'Favoriet',
|
||||||
|
Favorites: 'Favorieten',
|
||||||
|
Files: 'Bestanden',
|
||||||
|
File_description: 'Bestandsbeschrijving',
|
||||||
|
File_name: 'Bestandsnaam',
|
||||||
|
Finish_recording: 'Beëindig opname',
|
||||||
|
Following_thread: 'Volg thread',
|
||||||
|
For_your_security_you_must_enter_your_current_password_to_continue: 'Voor je veiligheid moet je je wachtwoord invullen om door te gaan',
|
||||||
|
Forgot_my_password: 'Wachtwoord vergeten',
|
||||||
|
Forgot_password_If_this_email_is_registered: 'Als dit email adres bij ons bekend is, sturen we je instructies op om je wachtwoord te resetten. Als je geen email krijgt, probeer het dan nogmaals.',
|
||||||
|
Forgot_password: 'Wachtwoord vergeten',
|
||||||
|
Forgot_Password: 'Wachtwoord Vergeten',
|
||||||
|
Full_table: 'Klik om de hele tabel te zien',
|
||||||
|
Generate_New_Link: 'Genereer Nieuwe Link',
|
||||||
|
Group_by_favorites: 'Sorteer op favorieten',
|
||||||
|
Group_by_type: 'Sorteer op type',
|
||||||
|
Hide: 'Verberg',
|
||||||
|
Has_joined_the_channel: 'Is bij het kanaal gekomen',
|
||||||
|
Has_joined_the_conversation: 'Neemt deel aan het gesprek',
|
||||||
|
Has_left_the_channel: 'Heeft het kanaal verlaten',
|
||||||
|
IN_APP_AND_DESKTOP: 'IN-APP EN DESKTOP',
|
||||||
|
In_App_and_Desktop_Alert_info: 'Laat een banner bovenaan het scherm zien als de app open is en geeft een notificatie op de desktop',
|
||||||
|
Invisible: 'Onzichtbaar',
|
||||||
|
Invite: 'Nodig uit',
|
||||||
|
is_a_valid_RocketChat_instance: 'is een geldige Rocket.Chat instantie',
|
||||||
|
is_not_a_valid_RocketChat_instance: 'is geen geldige Rocket.Chat instantie',
|
||||||
|
is_typing: 'is aan het typen',
|
||||||
|
Invalid_or_expired_invite_token: 'Ongeldig of verlopen uitnodigingstoken',
|
||||||
|
Invalid_server_version: 'De server die je probeert te bereiken gebruikt een versie die niet meer door de app ondersteunt wordt: {{currentVersion}}.\n\nMinimale versienummer {{minVersion}}',
|
||||||
|
Invite_Link: 'Uitnodigingslink',
|
||||||
|
Invite_users: 'Nodig gebruikers uit',
|
||||||
|
Join_the_community: 'Word lid van de community',
|
||||||
|
Join: 'Word lid',
|
||||||
|
Just_invited_people_can_access_this_channel: 'Alleen genodigden kunnen bij dit kanaal',
|
||||||
|
Language: 'Taal',
|
||||||
|
last_message: 'laatste bericht',
|
||||||
|
Leave_channel: 'Verlaat kanaal',
|
||||||
|
leaving_room: 'ruimte verlaten',
|
||||||
|
leave: 'verlaten',
|
||||||
|
Legal: 'Legaal',
|
||||||
|
Light: 'Light',
|
||||||
|
License: 'License',
|
||||||
|
Livechat: 'Livechat',
|
||||||
|
Login: 'Login',
|
||||||
|
Login_error: 'Je inloggegevens zijn fout! Probeer het opnieuw.',
|
||||||
|
Login_with: 'Login met',
|
||||||
|
Logout: 'Logout',
|
||||||
|
Max_number_of_uses: 'Maximaal aantal gebruiksmogelijkheden ',
|
||||||
|
members: 'leden',
|
||||||
|
Members: 'Leden',
|
||||||
|
Mentioned_Messages: 'Vermelde Berichten',
|
||||||
|
mentioned: 'vermeld',
|
||||||
|
Mentions: 'Vermeldingen',
|
||||||
|
Message_accessibility: 'Bericht van {{user}} om {{time}}: {{message}}',
|
||||||
|
Message_actions: 'Berichtacties',
|
||||||
|
Message_pinned: 'Bericht vastgezet',
|
||||||
|
Message_removed: 'Bericht verwijderd',
|
||||||
|
message: 'bericht',
|
||||||
|
messages: 'berichten',
|
||||||
|
Messages: 'Berichten',
|
||||||
|
Message_Reported: 'Bericht gerapporteerd',
|
||||||
|
Microphone_Permission_Message: 'Rocket Chat heeft toegang tot je microfoon nodig voor geluidsberichten.',
|
||||||
|
Microphone_Permission: 'Microfoon toestemming',
|
||||||
|
Mute: 'Dempen',
|
||||||
|
muted: 'gedempt',
|
||||||
|
My_servers: 'Mijn servers',
|
||||||
|
N_people_reacted: '{{n}} mensen reageerden',
|
||||||
|
N_users: '{{n}} gebruikers',
|
||||||
|
name: 'naam',
|
||||||
|
Name: 'Naam',
|
||||||
|
Never: 'Nooit',
|
||||||
|
New_Message: 'Nieuw Bericht',
|
||||||
|
New_Password: 'Nieuw Wachtwoord',
|
||||||
|
New_Server: 'Nieuwe Server',
|
||||||
|
Next: 'Volgende',
|
||||||
|
No_files: 'Geen bestanden',
|
||||||
|
No_limit: 'Geen limiet',
|
||||||
|
No_mentioned_messages: 'Geen vermelde berichten',
|
||||||
|
No_pinned_messages: 'Geen vastgezette berichten',
|
||||||
|
No_results_found: 'Geen resultaten gevonden',
|
||||||
|
No_starred_messages: 'Geen berichten met ster gemarkeerd',
|
||||||
|
No_thread_messages: 'Geen thread berichten',
|
||||||
|
No_announcement_provided: 'Geen announcement opgegeven.',
|
||||||
|
No_description_provided: 'Geen beschrijving opgegeven.',
|
||||||
|
No_topic_provided: 'Geen onderwerp opgegeven.',
|
||||||
|
No_Message: 'Geen bericht',
|
||||||
|
No_messages_yet: 'Nog geen berichten',
|
||||||
|
No_Reactions: 'Geen reacties',
|
||||||
|
No_Read_Receipts: 'Geen leesbevestiging',
|
||||||
|
Not_logged: 'Niet gelogged',
|
||||||
|
Not_RC_Server: 'Dit is geen Rocket.Chat server.\n{{contact}}',
|
||||||
|
Nothing: 'Niets',
|
||||||
|
Nothing_to_save: 'Niets om op te slaan!',
|
||||||
|
Notify_active_in_this_room: 'Bericht de actieve gebruikers in deze kamer',
|
||||||
|
Notify_all_in_this_room: 'Bericht iedereen in deze kamer',
|
||||||
|
Notifications: 'Notificaties',
|
||||||
|
Notification_Duration: 'Notificatie Duur',
|
||||||
|
Notification_Preferences: 'Notificatievoorkeuren',
|
||||||
|
Offline: 'Offline',
|
||||||
|
Oops: 'Oeps!',
|
||||||
|
Online: 'Online',
|
||||||
|
Only_authorized_users_can_write_new_messages: 'Alleen gebruikers met toestemming mogen nieuwe berichten maken',
|
||||||
|
Open_emoji_selector: 'Open de emoji selector',
|
||||||
|
Open_Source_Communication: 'Open de Source Communication',
|
||||||
|
Password: 'Wachtwoord',
|
||||||
|
Permalink_copied_to_clipboard: 'Permalink gekopiëerd naar klembord!',
|
||||||
|
Pin: 'Vastzetten',
|
||||||
|
Pinned_Messages: 'Vastgezette berichten',
|
||||||
|
pinned: 'vastgezet',
|
||||||
|
Pinned: 'Vastgezet',
|
||||||
|
Please_enter_your_password: 'Vul je wachtwoord in',
|
||||||
|
Preferences: 'Voorkeuren',
|
||||||
|
Preferences_saved: 'Voorkeuren opgeslagen!',
|
||||||
|
Privacy_Policy: ' Privacy Policy',
|
||||||
|
Private_Channel: 'Prive Kanaal',
|
||||||
|
Private_Groups: 'Prive Groepen',
|
||||||
|
Private: 'Prive',
|
||||||
|
Processing: 'Verwerken...',
|
||||||
|
Profile_saved_successfully: 'Profiel succesvol opgeslagen!',
|
||||||
|
Profile: 'Profiel',
|
||||||
|
Public_Channel: 'Publiek kanaal',
|
||||||
|
Public: 'Publiek',
|
||||||
|
PUSH_NOTIFICATIONS: 'PUSHNOTIFICATIES',
|
||||||
|
Push_Notifications_Alert_Info: 'Deze notificaties krijg je als de app niet geopend is',
|
||||||
|
Quote: 'Quote',
|
||||||
|
Reactions_are_disabled: 'Reacties zijn uitgeschakeld',
|
||||||
|
Reactions_are_enabled: 'Reacties zijn ingeschakeld',
|
||||||
|
Reactions: 'Reacties',
|
||||||
|
Read: 'Lezen',
|
||||||
|
Read_Only_Channel: 'Alleen-lezen Kanaal',
|
||||||
|
Read_Only: 'Alleen Lezen',
|
||||||
|
Read_Receipt: 'Leesbevestiging',
|
||||||
|
Receive_Group_Mentions: 'Ontvang Groepsvermeldingen',
|
||||||
|
Receive_Group_Mentions_Info: 'Ontvang @all en @here vermeldingen',
|
||||||
|
Register: 'Aanmelden',
|
||||||
|
Repeat_Password: 'Wachtwoord herhalen',
|
||||||
|
Replied_on: 'Gereageerd op:',
|
||||||
|
replies: 'reacties',
|
||||||
|
reply: 'reactie',
|
||||||
|
Reply: 'Reacties',
|
||||||
|
Report: 'Rapporteren',
|
||||||
|
Receive_Notification: 'Ontvang notificatie',
|
||||||
|
Receive_notifications_from: 'Ontvang notificaties van {{name}}',
|
||||||
|
Resend: 'Opnieuw verzenden',
|
||||||
|
Reset_password: 'Wachtwoord reset',
|
||||||
|
resetting_password: 'wachtwoord aan het resetten',
|
||||||
|
RESET: 'RESET',
|
||||||
|
Review_app_title: 'Vind je dit een TOP app?',
|
||||||
|
Review_app_desc: 'Geef ons 5 sterren op {{store}}',
|
||||||
|
Review_app_yes: 'Doe ik!',
|
||||||
|
Review_app_no: 'Nee',
|
||||||
|
Review_app_later: 'Misschien later',
|
||||||
|
Review_app_unable_store: 'Kon {{store}} niet openen',
|
||||||
|
Review_this_app: 'Review deze app',
|
||||||
|
Roles: 'Rollen',
|
||||||
|
Room_actions: 'Kamer acties',
|
||||||
|
Room_changed_announcement: 'Kamer announcement veranderd naar: {{announcement}} door {{userBy}}',
|
||||||
|
Room_changed_description: 'Kamer beschrijving veranderd naar: {{description}} door {{userBy}}',
|
||||||
|
Room_changed_privacy: 'Kamer type veranderd naar: {{type}} door {{userBy}}',
|
||||||
|
Room_changed_topic: 'Kamer onderwerp veranderd naar: {{topic}} door {{userBy}}',
|
||||||
|
Room_Files: 'Kamer Bestanden',
|
||||||
|
Room_Info_Edit: 'Kamer Info Aanpassen',
|
||||||
|
Room_Info: 'Kamer Info',
|
||||||
|
Room_Members: 'Kamer Leden',
|
||||||
|
Room_name_changed: 'Kamer naam veranderd naar: {{name}} door {{userBy}}',
|
||||||
|
SAVE: 'OPSLAAN',
|
||||||
|
Save_Changes: 'Sla wijzigingen op',
|
||||||
|
Save: 'Opslaan',
|
||||||
|
saving_preferences: 'voorkeuren opslaan',
|
||||||
|
saving_profile: 'profiel opslaan',
|
||||||
|
saving_settings: 'instellingen opslaan',
|
||||||
|
saved_to_gallery: 'Aan galerij toegevoegd',
|
||||||
|
Search_Messages: 'Zoek Berichten',
|
||||||
|
Search: 'Zoek',
|
||||||
|
Search_by: 'Zoek op',
|
||||||
|
Search_global_users: 'Zoek voor algemene gebruikers',
|
||||||
|
Search_global_users_description: 'Als je dit aan zet, kan je gebruikers van andere bedrijven en servers zoeken.',
|
||||||
|
Seconds: '{{second}} seconden',
|
||||||
|
Select_Avatar: 'Kies Avatar',
|
||||||
|
Select_Server: 'Kies Server',
|
||||||
|
Select_Users: 'Kies Gebruikers',
|
||||||
|
Send: 'Verstuur',
|
||||||
|
Send_audio_message: 'Verstuur geluidsbericht',
|
||||||
|
Send_crash_report: 'Verstuur crash report',
|
||||||
|
Send_message: 'Verstuur bericht',
|
||||||
|
Send_to: 'Verstuur naar...',
|
||||||
|
Sent_an_attachment: 'Verstuur een bijlage',
|
||||||
|
Server: 'Server',
|
||||||
|
Servers: 'Servers',
|
||||||
|
Server_version: 'Server versie: {{version}}',
|
||||||
|
Set_username_subtitle: 'De gebruikersnaam wordt gebruikt om anderen jou te vermelden in berichten',
|
||||||
|
Settings: 'Instellingen',
|
||||||
|
Settings_succesfully_changed: 'Instellingen succesvol veranderd!',
|
||||||
|
Share: 'Delen',
|
||||||
|
Share_Link: 'Deel Link',
|
||||||
|
Share_this_app: 'Deel deze app',
|
||||||
|
Show_Unread_Counter: 'Laat Ongelezen Teller Zien',
|
||||||
|
Show_Unread_Counter_Info: 'De Ongelezen Tller is een badge aan de rechterkant van het kanaal in de lijst',
|
||||||
|
Sign_in_your_server: 'Log in bij je server',
|
||||||
|
Sign_Up: 'Inschrijven',
|
||||||
|
Some_field_is_invalid_or_empty: 'Een veld is ongeldig of leeg',
|
||||||
|
Sorting_by: 'Sorteren op {{key}}',
|
||||||
|
Sound: 'Geluid',
|
||||||
|
Star_room: 'Sterrenkamer',
|
||||||
|
Star: 'Ster',
|
||||||
|
Starred_Messages: 'Berichten met ster gemarkeerd',
|
||||||
|
starred: 'met ster gemarkeerd',
|
||||||
|
Starred: 'Met ster gemarkeerd',
|
||||||
|
Start_of_conversation: 'Begin van een gesprek',
|
||||||
|
Started_discussion: 'Begin van een discussie:',
|
||||||
|
Started_call: 'Gesprek gestart door {{userBy}}',
|
||||||
|
Submit: 'Verstuur',
|
||||||
|
Table: 'Tabel',
|
||||||
|
Take_a_photo: 'Neem een foto',
|
||||||
|
Take_a_video: 'Neem een video',
|
||||||
|
tap_to_change_status: 'tik om je status te veranderen',
|
||||||
|
Tap_to_view_servers_list: 'Tik om een server lijst te weergeven',
|
||||||
|
Terms_of_Service: ' Servicevoorwaarden ',
|
||||||
|
Theme: 'Thema',
|
||||||
|
The_URL_is_invalid: 'Ongeldige URL of niet mogelijk een veilige verbinding op te zetten.\n{{contact}}',
|
||||||
|
There_was_an_error_while_action: 'Er was eer fout tijdens {{action}}!',
|
||||||
|
This_room_is_blocked: 'Deze kamer is geblokkeerd',
|
||||||
|
This_room_is_read_only: 'Deze kamer is alleen-lezen',
|
||||||
|
Thread: 'Thread',
|
||||||
|
Threads: 'Threads',
|
||||||
|
Timezone: 'Tijdzone',
|
||||||
|
To: 'Naar',
|
||||||
|
topic: 'onderwerp',
|
||||||
|
Topic: 'Onderwerp',
|
||||||
|
Translate: 'Vertalen',
|
||||||
|
Try_again: 'Probeer opnieuw',
|
||||||
|
Two_Factor_Authentication: 'Tweee-factor Authenticatie',
|
||||||
|
Type_the_channel_name_here: 'Typ hier de kanaalnaam',
|
||||||
|
unarchive: 'dearchiveren',
|
||||||
|
UNARCHIVE: 'DEARCHIVEREN',
|
||||||
|
Unblock_user: 'Gebruiker deblokkeren',
|
||||||
|
Unfavorite: 'Uit favorieten halen',
|
||||||
|
Unfollowed_thread: 'Thread ontvolgd',
|
||||||
|
Unmute: 'Dempen opheffen',
|
||||||
|
unmuted: 'ongedempt',
|
||||||
|
Unpin: 'Losmaken',
|
||||||
|
unread_messages: 'ongelezen',
|
||||||
|
Unread: 'Ongelezen',
|
||||||
|
Unread_on_top: 'Ongelezen bovenaan',
|
||||||
|
Unstar: 'Ster verwijderen',
|
||||||
|
Updating: 'Updaten...',
|
||||||
|
Uploading: 'Uploaden',
|
||||||
|
Upload_file_question_mark: 'Bestand uploaden?',
|
||||||
|
Users: 'Gebruikers',
|
||||||
|
User_added_by: 'Gebruiker {{userAdded}} toegevoegd door {{userBy}}',
|
||||||
|
User_Info: 'Gebruiker Info',
|
||||||
|
User_has_been_key: 'Gebruiker is {{key}}!',
|
||||||
|
User_is_no_longer_role_by_: '{{user}} is geen {{role}} meer door {{userBy}}',
|
||||||
|
User_muted_by: 'Gebruiker {{userMuted}} gedempt door {{userBy}}',
|
||||||
|
User_removed_by: 'Gebruiker {{userRemoved}} verwijderd door {{userBy}}',
|
||||||
|
User_sent_an_attachment: '{{user}} stuurde een bijlage',
|
||||||
|
User_unmuted_by: 'Dempen opgeheven voor {{userUnmuted}} door {{userBy}}',
|
||||||
|
User_was_set_role_by_: '{{user}} is nu {{role}} door {{userBy}}',
|
||||||
|
Username_is_empty: 'Gebruikersnaam is leeg',
|
||||||
|
Username: 'Gebruikersnaam',
|
||||||
|
Username_or_email: 'Gebruikersnaam of email',
|
||||||
|
Validating: 'Aan het valideren',
|
||||||
|
Video_call: 'Videogesprek',
|
||||||
|
View_Original: 'Bekijk origineel',
|
||||||
|
Voice_call: 'Audiogesprek',
|
||||||
|
Websocket_disabled: 'Websocket staat uit voor deze server.\n{{contact}}',
|
||||||
|
Welcome: 'Welkom',
|
||||||
|
Welcome_to_RocketChat: 'Welkom bij Rocket.Chat',
|
||||||
|
Whats_your_2fa: 'Wat is je 2FA code?',
|
||||||
|
Without_Servers: 'Zonder Servers',
|
||||||
|
Write_External_Permission_Message: 'Rocket Chat moet bij je galerij kunnen om afbeeldingen op te slaan.',
|
||||||
|
Write_External_Permission: 'Galerij Toestemming',
|
||||||
|
Yes_action_it: 'Ja, {{action}} het!',
|
||||||
|
Yesterday: 'Gisteren',
|
||||||
|
You_are_in_preview_mode: 'Je bent in preview mode',
|
||||||
|
You_are_offline: 'Je bent offline',
|
||||||
|
You_can_search_using_RegExp_eg: 'Je kan RegExp. gebruiken, bijv. `/^text$/i`',
|
||||||
|
You_colon: 'Jij: ',
|
||||||
|
you_were_mentioned: 'je bent vermeld',
|
||||||
|
you: 'jij',
|
||||||
|
You: 'Jij',
|
||||||
|
You_need_to_access_at_least_one_RocketChat_server_to_share_something: 'Je moet minimaal toegang hebben tot 1 Rocket.Chat server om iets te delen.',
|
||||||
|
Your_certificate: 'Jouw Certificaat',
|
||||||
|
Your_invite_link_will_expire_after__usesLeft__uses: 'Je uitnodigingslink wordt ongeldig over {{usesLeft}} keer.',
|
||||||
|
Your_invite_link_will_expire_on__date__or_after__usesLeft__uses: 'Je uitnodigingslink wordt ongeldig op {{date}} of na{{usesLeft}} keer.',
|
||||||
|
Your_invite_link_will_expire_on__date__: 'Je uitnodigingslink wordt ongeldig op {{date}}.',
|
||||||
|
Your_invite_link_will_never_expire: 'Je uitnodigingslink wordt nooit ongeldig.',
|
||||||
|
Version_no: 'Versie: {{version}}',
|
||||||
|
You_will_not_be_able_to_recover_this_message: 'Je kan dit bericht niet meer terugkrijgen!',
|
||||||
|
Change_Language: 'Verander taal',
|
||||||
|
Crash_report_disclaimer: 'We kijken nooit naar de content van je chats. Het crashrapport bevat alleen relevante informatie voor ons om problemen te isoleren en op te lossen.',
|
||||||
|
Type_message: 'Type bericht',
|
||||||
|
Room_search: 'Kamers zoeken',
|
||||||
|
Room_selection: 'Kamerselectie 1...9',
|
||||||
|
Next_room: 'Volgende kamer',
|
||||||
|
Previous_room: 'Vorige kamer',
|
||||||
|
New_room: 'Nieuwe Kamer',
|
||||||
|
Upload_room: 'Upload naar kamer',
|
||||||
|
Search_messages: 'Doorzoek messages',
|
||||||
|
Scroll_messages: 'Scroll door messages',
|
||||||
|
Reply_latest: 'Beantwoord de laatste',
|
||||||
|
Server_selection: 'Server selectie',
|
||||||
|
Server_selection_numbers: 'Server selectie 1...9',
|
||||||
|
Add_server: 'Voeg Server Toe',
|
||||||
|
New_line: 'Nieuwe Regel'
|
||||||
|
};
|
|
@ -302,6 +302,13 @@ export default {
|
||||||
Reset_password: 'Resetar senha',
|
Reset_password: 'Resetar senha',
|
||||||
resetting_password: 'redefinindo senha',
|
resetting_password: 'redefinindo senha',
|
||||||
RESET: 'RESETAR',
|
RESET: 'RESETAR',
|
||||||
|
Review_app_title: 'Você está gostando do app?',
|
||||||
|
Review_app_desc: 'Nos dê 5 estrelas na {{store}}',
|
||||||
|
Review_app_yes: 'Claro!',
|
||||||
|
Review_app_no: 'Não',
|
||||||
|
Review_app_later: 'Talvez depois',
|
||||||
|
Review_app_unable_store: 'Não foi possível abrir {{store}}',
|
||||||
|
Review_this_app: 'Avaliar esse app',
|
||||||
Roles: 'Papéis',
|
Roles: 'Papéis',
|
||||||
Room_actions: 'Ações',
|
Room_actions: 'Ações',
|
||||||
Room_changed_announcement: 'O anúncio da sala foi alterado para: {{announcement}} por {{userBy}}',
|
Room_changed_announcement: 'O anúncio da sala foi alterado para: {{announcement}} por {{userBy}}',
|
||||||
|
@ -339,6 +346,7 @@ export default {
|
||||||
Settings_succesfully_changed: 'Configurações salvas com sucesso!',
|
Settings_succesfully_changed: 'Configurações salvas com sucesso!',
|
||||||
Share: 'Compartilhar',
|
Share: 'Compartilhar',
|
||||||
Share_Link: 'Share Link',
|
Share_Link: 'Share Link',
|
||||||
|
Show_more: 'Mostrar mais..',
|
||||||
Sign_in_your_server: 'Entrar no seu servidor',
|
Sign_in_your_server: 'Entrar no seu servidor',
|
||||||
Sign_Up: 'Registrar',
|
Sign_Up: 'Registrar',
|
||||||
Some_field_is_invalid_or_empty: 'Algum campo está inválido ou vazio',
|
Some_field_is_invalid_or_empty: 'Algum campo está inválido ou vazio',
|
||||||
|
@ -396,6 +404,8 @@ export default {
|
||||||
Username_is_empty: 'Usuário está vazio',
|
Username_is_empty: 'Usuário está vazio',
|
||||||
Username: 'Usuário',
|
Username: 'Usuário',
|
||||||
Username_or_email: 'Usuário ou email',
|
Username_or_email: 'Usuário ou email',
|
||||||
|
Verify_email_title: 'Registrado com sucesso!',
|
||||||
|
Verify_email_desc: 'Nós lhe enviamos um e-mail para confirmar o seu registro. Se você não receber um e-mail em breve, por favor retorne e tente novamente.',
|
||||||
Video_call: 'Chamada de vídeo',
|
Video_call: 'Chamada de vídeo',
|
||||||
Voice_call: 'Chamada de voz',
|
Voice_call: 'Chamada de voz',
|
||||||
Websocket_disabled: 'Websocket está desativado para esse servidor.\n{{contact}}',
|
Websocket_disabled: 'Websocket está desativado para esse servidor.\n{{contact}}',
|
||||||
|
@ -433,5 +443,8 @@ export default {
|
||||||
Server_selection: 'Seleção de servidor',
|
Server_selection: 'Seleção de servidor',
|
||||||
Server_selection_numbers: 'Selecionar servidor 1...9',
|
Server_selection_numbers: 'Selecionar servidor 1...9',
|
||||||
Add_server: 'Adicionar servidor',
|
Add_server: 'Adicionar servidor',
|
||||||
New_line: 'Nova linha'
|
New_line: 'Nova linha',
|
||||||
|
You_will_be_logged_out_of_this_application: 'Você sairá deste aplicativo.',
|
||||||
|
Clear: 'Limpar',
|
||||||
|
This_will_clear_all_your_offline_data: 'Isto limpará todos os seus dados offline.'
|
||||||
};
|
};
|
||||||
|
|
55
app/index.js
55
app/index.js
|
@ -1,5 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View, Linking, BackHandler } from 'react-native';
|
import {
|
||||||
|
View, Linking, BackHandler, ScrollView
|
||||||
|
} from 'react-native';
|
||||||
import { createAppContainer, createSwitchNavigator } from 'react-navigation';
|
import { createAppContainer, createSwitchNavigator } from 'react-navigation';
|
||||||
import { createStackNavigator } from 'react-navigation-stack';
|
import { createStackNavigator } from 'react-navigation-stack';
|
||||||
import { createDrawerNavigator } from 'react-navigation-drawer';
|
import { createDrawerNavigator } from 'react-navigation-drawer';
|
||||||
|
@ -34,13 +36,16 @@ import { ThemeContext } from './theme';
|
||||||
import RocketChat, { THEME_PREFERENCES_KEY } from './lib/rocketchat';
|
import RocketChat, { THEME_PREFERENCES_KEY } from './lib/rocketchat';
|
||||||
import { MIN_WIDTH_SPLIT_LAYOUT } from './constants/tablet';
|
import { MIN_WIDTH_SPLIT_LAYOUT } from './constants/tablet';
|
||||||
import {
|
import {
|
||||||
isTablet, isSplited, isIOS, setWidth, supportSystemTheme
|
isTablet, isSplited, isIOS, setWidth, supportSystemTheme, isAndroid
|
||||||
} from './utils/deviceInfo';
|
} from './utils/deviceInfo';
|
||||||
import { KEY_COMMAND } from './commands';
|
import { KEY_COMMAND } from './commands';
|
||||||
import Tablet, { initTabletNav } from './tablet';
|
import Tablet, { initTabletNav } from './tablet';
|
||||||
import sharedStyles from './views/Styles';
|
import sharedStyles from './views/Styles';
|
||||||
import { SplitContext } from './split';
|
import { SplitContext } from './split';
|
||||||
|
|
||||||
|
import RoomsListView from './views/RoomsListView';
|
||||||
|
import RoomView from './views/RoomView';
|
||||||
|
|
||||||
if (isIOS) {
|
if (isIOS) {
|
||||||
const RNScreens = require('react-native-screens');
|
const RNScreens = require('react-native-screens');
|
||||||
RNScreens.useScreens();
|
RNScreens.useScreens();
|
||||||
|
@ -109,9 +114,7 @@ const OutsideStackModal = createStackNavigator({
|
||||||
});
|
});
|
||||||
|
|
||||||
const RoomRoutes = {
|
const RoomRoutes = {
|
||||||
RoomView: {
|
RoomView,
|
||||||
getScreen: () => require('./views/RoomView').default
|
|
||||||
},
|
|
||||||
ThreadMessagesView: {
|
ThreadMessagesView: {
|
||||||
getScreen: () => require('./views/ThreadMessagesView').default
|
getScreen: () => require('./views/ThreadMessagesView').default
|
||||||
},
|
},
|
||||||
|
@ -125,9 +128,7 @@ const RoomRoutes = {
|
||||||
|
|
||||||
// Inside
|
// Inside
|
||||||
const ChatsStack = createStackNavigator({
|
const ChatsStack = createStackNavigator({
|
||||||
RoomsListView: {
|
RoomsListView,
|
||||||
getScreen: () => require('./views/RoomsListView').default
|
|
||||||
},
|
|
||||||
RoomActionsView: {
|
RoomActionsView: {
|
||||||
getScreen: () => require('./views/RoomActionsView').default
|
getScreen: () => require('./views/RoomActionsView').default
|
||||||
},
|
},
|
||||||
|
@ -275,10 +276,21 @@ const AttachmentStack = createStackNavigator({
|
||||||
cardStyle
|
cardStyle
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const ModalBlockStack = createStackNavigator({
|
||||||
|
ModalBlockView: {
|
||||||
|
getScreen: () => require('./views/ModalBlockView').default
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
mode: 'modal',
|
||||||
|
defaultNavigationOptions: defaultHeader,
|
||||||
|
cardStyle
|
||||||
|
});
|
||||||
|
|
||||||
const InsideStackModal = createStackNavigator({
|
const InsideStackModal = createStackNavigator({
|
||||||
Main: ChatsDrawer,
|
Main: ChatsDrawer,
|
||||||
NewMessageStack,
|
NewMessageStack,
|
||||||
AttachmentStack,
|
AttachmentStack,
|
||||||
|
ModalBlockStack,
|
||||||
JitsiMeetView: {
|
JitsiMeetView: {
|
||||||
getScreen: () => require('./views/JitsiMeetView').default
|
getScreen: () => require('./views/JitsiMeetView').default
|
||||||
}
|
}
|
||||||
|
@ -422,6 +434,7 @@ const ModalSwitch = createSwitchNavigator({
|
||||||
SidebarStack,
|
SidebarStack,
|
||||||
RoomActionsStack,
|
RoomActionsStack,
|
||||||
SettingsStack,
|
SettingsStack,
|
||||||
|
ModalBlockStack,
|
||||||
AuthLoading: () => null
|
AuthLoading: () => null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -453,6 +466,9 @@ class CustomModalStack extends React.Component {
|
||||||
closeModal();
|
closeModal();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (state && state.routes[state.index] && state.routes[state.index].routes.length > 1) {
|
||||||
|
navigation.goBack();
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -464,6 +480,24 @@ class CustomModalStack extends React.Component {
|
||||||
const pageSheetViews = ['AttachmentView'];
|
const pageSheetViews = ['AttachmentView'];
|
||||||
const pageSheet = pageSheetViews.includes(getActiveRouteName(navigation.state));
|
const pageSheet = pageSheetViews.includes(getActiveRouteName(navigation.state));
|
||||||
|
|
||||||
|
const androidProps = isAndroid && {
|
||||||
|
style: { marginBottom: 0 }
|
||||||
|
};
|
||||||
|
|
||||||
|
let content = (
|
||||||
|
<View style={[sharedStyles.modal, pageSheet ? sharedStyles.modalPageSheet : sharedStyles.modalFormSheet]}>
|
||||||
|
<ModalSwitch navigation={navigation} screenProps={{ ...screenProps, closeModal: this.closeModal }} />
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isAndroid) {
|
||||||
|
content = (
|
||||||
|
<ScrollView overScrollMode='never'>
|
||||||
|
{content}
|
||||||
|
</ScrollView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
useNativeDriver
|
useNativeDriver
|
||||||
|
@ -472,10 +506,9 @@ class CustomModalStack extends React.Component {
|
||||||
onBackdropPress={closeModal}
|
onBackdropPress={closeModal}
|
||||||
hideModalContentWhileAnimating
|
hideModalContentWhileAnimating
|
||||||
avoidKeyboard
|
avoidKeyboard
|
||||||
|
{...androidProps}
|
||||||
>
|
>
|
||||||
<View style={[sharedStyles.modal, pageSheet ? sharedStyles.modalPageSheet : sharedStyles.modalFormSheet]}>
|
{content}
|
||||||
<ModalSwitch navigation={navigation} screenProps={screenProps} />
|
|
||||||
</View>
|
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,12 @@ function setTopLevelNavigator(navigatorRef) {
|
||||||
_navigator = navigatorRef;
|
_navigator = navigatorRef;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function back() {
|
||||||
|
_navigator.dispatch(
|
||||||
|
NavigationActions.back()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function navigate(routeName, params) {
|
function navigate(routeName, params) {
|
||||||
_navigator.dispatch(
|
_navigator.dispatch(
|
||||||
NavigationActions.navigate({
|
NavigationActions.navigate({
|
||||||
|
@ -16,6 +22,7 @@ function navigate(routeName, params) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
back,
|
||||||
navigate,
|
navigate,
|
||||||
setTopLevelNavigator
|
setTopLevelNavigator
|
||||||
};
|
};
|
||||||
|
|
|
@ -60,7 +60,7 @@ class DB {
|
||||||
}
|
}
|
||||||
|
|
||||||
setShareDB(database = '') {
|
setShareDB(database = '') {
|
||||||
const path = database.replace(/(^\w+:|^)\/\//, '').replace(/\//, '.');
|
const path = database.replace(/(^\w+:|^)\/\//, '').replace(/\//g, '.');
|
||||||
const dbName = `${ appGroupPath }${ path }.db`;
|
const dbName = `${ appGroupPath }${ path }.db`;
|
||||||
|
|
||||||
const adapter = new SQLiteAdapter({
|
const adapter = new SQLiteAdapter({
|
||||||
|
@ -83,7 +83,7 @@ class DB {
|
||||||
}
|
}
|
||||||
|
|
||||||
setActiveDB(database = '') {
|
setActiveDB(database = '') {
|
||||||
const path = database.replace(/(^\w+:|^)\/\//, '').replace(/\//, '.');
|
const path = database.replace(/(^\w+:|^)\/\//, '').replace(/\//g, '.');
|
||||||
const dbName = `${ appGroupPath }${ path }.db`;
|
const dbName = `${ appGroupPath }${ path }.db`;
|
||||||
|
|
||||||
const adapter = new SQLiteAdapter({
|
const adapter = new SQLiteAdapter({
|
||||||
|
|
|
@ -73,4 +73,6 @@ export default class Message extends Model {
|
||||||
@json('translations', sanitizer) translations;
|
@json('translations', sanitizer) translations;
|
||||||
|
|
||||||
@field('tmsg') tmsg;
|
@field('tmsg') tmsg;
|
||||||
|
|
||||||
|
@json('blocks', sanitizer) blocks;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,4 +11,6 @@ export default class SlashCommand extends Model {
|
||||||
@field('client_only') clientOnly;
|
@field('client_only') clientOnly;
|
||||||
|
|
||||||
@field('provides_preview') providesPreview;
|
@field('provides_preview') providesPreview;
|
||||||
|
|
||||||
|
@field('app_id') appId;
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,23 @@ export default schemaMigrations({
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
toVersion: 4,
|
||||||
|
steps: [
|
||||||
|
addColumns({
|
||||||
|
table: 'messages',
|
||||||
|
columns: [
|
||||||
|
{ name: 'blocks', type: 'string', isOptional: true }
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
addColumns({
|
||||||
|
table: 'slash_commands',
|
||||||
|
columns: [
|
||||||
|
{ name: 'app_id', type: 'string', isOptional: true }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { appSchema, tableSchema } from '@nozbe/watermelondb';
|
import { appSchema, tableSchema } from '@nozbe/watermelondb';
|
||||||
|
|
||||||
export default appSchema({
|
export default appSchema({
|
||||||
version: 3,
|
version: 4,
|
||||||
tables: [
|
tables: [
|
||||||
tableSchema({
|
tableSchema({
|
||||||
name: 'subscriptions',
|
name: 'subscriptions',
|
||||||
|
@ -84,7 +84,8 @@ export default appSchema({
|
||||||
{ name: 'unread', type: 'boolean', isOptional: true },
|
{ name: 'unread', type: 'boolean', isOptional: true },
|
||||||
{ name: 'auto_translate', type: 'boolean', isOptional: true },
|
{ name: 'auto_translate', type: 'boolean', isOptional: true },
|
||||||
{ name: 'translations', type: 'string', isOptional: true },
|
{ name: 'translations', type: 'string', isOptional: true },
|
||||||
{ name: 'tmsg', type: 'string', isOptional: true }
|
{ name: 'tmsg', type: 'string', isOptional: true },
|
||||||
|
{ name: 'blocks', type: 'string', isOptional: true }
|
||||||
]
|
]
|
||||||
}),
|
}),
|
||||||
tableSchema({
|
tableSchema({
|
||||||
|
@ -217,7 +218,8 @@ export default appSchema({
|
||||||
{ name: 'params', type: 'string', isOptional: true },
|
{ name: 'params', type: 'string', isOptional: true },
|
||||||
{ name: 'description', type: 'string', isOptional: true },
|
{ name: 'description', type: 'string', isOptional: true },
|
||||||
{ name: 'client_only', type: 'boolean', isOptional: true },
|
{ name: 'client_only', type: 'boolean', isOptional: true },
|
||||||
{ name: 'provides_preview', type: 'boolean', isOptional: true }
|
{ name: 'provides_preview', type: 'boolean', isOptional: true },
|
||||||
|
{ name: 'app_id', type: 'string', isOptional: true }
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
|
|
|
@ -0,0 +1,160 @@
|
||||||
|
import random from '../../utils/random';
|
||||||
|
import EventEmitter from '../../utils/events';
|
||||||
|
import Navigation from '../Navigation';
|
||||||
|
|
||||||
|
const ACTION_TYPES = {
|
||||||
|
ACTION: 'blockAction',
|
||||||
|
SUBMIT: 'viewSubmit',
|
||||||
|
CLOSED: 'viewClosed'
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MODAL_ACTIONS = {
|
||||||
|
MODAL: 'modal',
|
||||||
|
OPEN: 'modal.open',
|
||||||
|
CLOSE: 'modal.close',
|
||||||
|
UPDATE: 'modal.update',
|
||||||
|
ERRORS: 'errors'
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CONTAINER_TYPES = {
|
||||||
|
VIEW: 'view',
|
||||||
|
MESSAGE: 'message'
|
||||||
|
};
|
||||||
|
|
||||||
|
const triggersId = new Map();
|
||||||
|
|
||||||
|
const invalidateTriggerId = (id) => {
|
||||||
|
const appId = triggersId.get(id);
|
||||||
|
triggersId.delete(id);
|
||||||
|
return appId;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const generateTriggerId = (appId) => {
|
||||||
|
const triggerId = random(17);
|
||||||
|
triggersId.set(triggerId, appId);
|
||||||
|
|
||||||
|
return triggerId;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const handlePayloadUserInteraction = (type, { triggerId, ...data }) => {
|
||||||
|
if (!triggersId.has(triggerId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const appId = invalidateTriggerId(triggerId);
|
||||||
|
if (!appId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { view } = data;
|
||||||
|
let { viewId } = data;
|
||||||
|
|
||||||
|
if (view && view.id) {
|
||||||
|
viewId = view.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!viewId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([MODAL_ACTIONS.ERRORS].includes(type)) {
|
||||||
|
EventEmitter.emit(viewId, {
|
||||||
|
type,
|
||||||
|
triggerId,
|
||||||
|
viewId,
|
||||||
|
appId,
|
||||||
|
...data
|
||||||
|
});
|
||||||
|
return MODAL_ACTIONS.ERRORS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([MODAL_ACTIONS.UPDATE].includes(type)) {
|
||||||
|
EventEmitter.emit(viewId, {
|
||||||
|
type,
|
||||||
|
triggerId,
|
||||||
|
viewId,
|
||||||
|
appId,
|
||||||
|
...data
|
||||||
|
});
|
||||||
|
return MODAL_ACTIONS.UPDATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if ([MODAL_ACTIONS.OPEN].includes(type) || [MODAL_ACTIONS.MODAL].includes(type)) {
|
||||||
|
Navigation.navigate('ModalBlockView', {
|
||||||
|
data: {
|
||||||
|
triggerId,
|
||||||
|
viewId,
|
||||||
|
appId,
|
||||||
|
...data
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return MODAL_ACTIONS.OPEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
return MODAL_ACTIONS.CLOSE;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function triggerAction({
|
||||||
|
type, actionId, appId, rid, mid, viewId, container, ...rest
|
||||||
|
}) {
|
||||||
|
return new Promise(async(resolve, reject) => {
|
||||||
|
const triggerId = generateTriggerId(appId);
|
||||||
|
|
||||||
|
const payload = rest.payload || rest;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { userId, authToken } = this.sdk.currentLogin;
|
||||||
|
const { host } = this.sdk.client;
|
||||||
|
|
||||||
|
// we need to use fetch because this.sdk.post add /v1 to url
|
||||||
|
const result = await fetch(`${ host }/api/apps/ui.interaction/${ appId }/`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-Auth-Token': authToken,
|
||||||
|
'X-User-Id': userId
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
type,
|
||||||
|
actionId,
|
||||||
|
payload,
|
||||||
|
container,
|
||||||
|
mid,
|
||||||
|
rid,
|
||||||
|
triggerId,
|
||||||
|
viewId
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { type: interactionType, ...data } = await result.json();
|
||||||
|
return resolve(handlePayloadUserInteraction(interactionType, data));
|
||||||
|
} catch (e) {
|
||||||
|
// modal.close has no body, so result.json will fail
|
||||||
|
// but it returns ok status
|
||||||
|
if (result.ok) {
|
||||||
|
return resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
return reject();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function triggerBlockAction(options) {
|
||||||
|
return triggerAction.call(this, { type: ACTION_TYPES.ACTION, ...options });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function triggerSubmitView({ viewId, ...options }) {
|
||||||
|
const result = await triggerAction.call(this, { type: ACTION_TYPES.SUBMIT, viewId, ...options });
|
||||||
|
if (!result || MODAL_ACTIONS.CLOSE === result) {
|
||||||
|
Navigation.back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function triggerCancel({ view, ...options }) {
|
||||||
|
return triggerAction.call(this, { type: ACTION_TYPES.CLOSED, view, ...options });
|
||||||
|
}
|
|
@ -17,10 +17,25 @@ const jitsiBaseUrl = ({
|
||||||
return `${ urlProtocol }${ urlDomain }${ prefix }${ uniqueIdentifier }`;
|
return `${ urlProtocol }${ urlDomain }${ prefix }${ uniqueIdentifier }`;
|
||||||
};
|
};
|
||||||
|
|
||||||
function callJitsi(rid, onlyAudio = false) {
|
async function callJitsi(rid, onlyAudio = false) {
|
||||||
|
let accessToken;
|
||||||
|
let queryString = '';
|
||||||
const { settings } = reduxStore.getState();
|
const { settings } = reduxStore.getState();
|
||||||
|
const { Jitsi_Enabled_TokenAuth } = settings;
|
||||||
|
|
||||||
Navigation.navigate('JitsiMeetView', { url: `${ jitsiBaseUrl(settings) }${ rid }`, onlyAudio, rid });
|
if (Jitsi_Enabled_TokenAuth) {
|
||||||
|
try {
|
||||||
|
accessToken = await this.sdk.methodCall('jitsi:generateAccessToken', rid);
|
||||||
|
} catch (e) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (accessToken) {
|
||||||
|
queryString = `?jwt=${ accessToken }`;
|
||||||
|
}
|
||||||
|
|
||||||
|
Navigation.navigate('JitsiMeetView', { url: `${ jitsiBaseUrl(settings) }${ rid }${ queryString }`, onlyAudio, rid });
|
||||||
}
|
}
|
||||||
|
|
||||||
export default callJitsi;
|
export default callJitsi;
|
||||||
|
|
|
@ -28,6 +28,8 @@ export default function() {
|
||||||
// filter slash commands
|
// filter slash commands
|
||||||
let slashCommandsToCreate = commands.filter(i1 => !allSlashCommandsRecords.find(i2 => i1.command === i2.id));
|
let slashCommandsToCreate = commands.filter(i1 => !allSlashCommandsRecords.find(i2 => i1.command === i2.id));
|
||||||
let slashCommandsToUpdate = allSlashCommandsRecords.filter(i1 => commands.find(i2 => i1.id === i2.command));
|
let slashCommandsToUpdate = allSlashCommandsRecords.filter(i1 => commands.find(i2 => i1.id === i2.command));
|
||||||
|
let slashCommandsToDelete = allSlashCommandsRecords
|
||||||
|
.filter(i1 => !slashCommandsToCreate.find(i2 => i2.command === i1.id) && !slashCommandsToUpdate.find(i2 => i2.id === i1.id));
|
||||||
|
|
||||||
// Create
|
// Create
|
||||||
slashCommandsToCreate = slashCommandsToCreate.map(command => slashCommandsCollection.prepareCreate(protectedFunction((s) => {
|
slashCommandsToCreate = slashCommandsToCreate.map(command => slashCommandsCollection.prepareCreate(protectedFunction((s) => {
|
||||||
|
@ -43,9 +45,13 @@ export default function() {
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Delete
|
||||||
|
slashCommandsToDelete = slashCommandsToDelete.map(command => command.prepareDestroyPermanently());
|
||||||
|
|
||||||
const allRecords = [
|
const allRecords = [
|
||||||
...slashCommandsToCreate,
|
...slashCommandsToCreate,
|
||||||
...slashCommandsToUpdate
|
...slashCommandsToUpdate,
|
||||||
|
...slashCommandsToDelete
|
||||||
];
|
];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -28,13 +28,9 @@ export async function cancelUpload(item) {
|
||||||
export function sendFileMessage(rid, fileInfo, tmid, server, user) {
|
export function sendFileMessage(rid, fileInfo, tmid, server, user) {
|
||||||
return new Promise(async(resolve, reject) => {
|
return new Promise(async(resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
const serversDB = database.servers;
|
|
||||||
const serversCollection = serversDB.collections.get('servers');
|
|
||||||
const serverInfo = await serversCollection.find(server);
|
|
||||||
const { id: Site_Url } = serverInfo;
|
|
||||||
const { id, token } = user;
|
const { id, token } = user;
|
||||||
|
|
||||||
const uploadUrl = `${ Site_Url }/api/v1/rooms.upload/${ rid }`;
|
const uploadUrl = `${ server }/api/v1/rooms.upload/${ rid }`;
|
||||||
|
|
||||||
const xhr = new XMLHttpRequest();
|
const xhr = new XMLHttpRequest();
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
|
|
|
@ -9,26 +9,66 @@ import database from '../../database';
|
||||||
import reduxStore from '../../createStore';
|
import reduxStore from '../../createStore';
|
||||||
import { addUserTyping, removeUserTyping, clearUserTyping } from '../../../actions/usersTyping';
|
import { addUserTyping, removeUserTyping, clearUserTyping } from '../../../actions/usersTyping';
|
||||||
import debounce from '../../../utils/debounce';
|
import debounce from '../../../utils/debounce';
|
||||||
|
import RocketChat from '../../rocketchat';
|
||||||
|
|
||||||
const unsubscribe = (subscriptions = []) => Promise.all(subscriptions.map(sub => sub.unsubscribe));
|
export default class RoomSubscription {
|
||||||
const removeListener = listener => listener.stop();
|
constructor(rid) {
|
||||||
|
this.rid = rid;
|
||||||
|
this.isAlive = true;
|
||||||
|
}
|
||||||
|
|
||||||
let promises;
|
subscribe = async() => {
|
||||||
let connectedListener;
|
console.log(`[RCRN] Subscribing to room ${ this.rid }`);
|
||||||
let disconnectedListener;
|
if (this.promises) {
|
||||||
let notifyRoomListener;
|
await this.unsubscribe();
|
||||||
let messageReceivedListener;
|
}
|
||||||
|
this.promises = RocketChat.subscribeRoom(this.rid);
|
||||||
|
|
||||||
export default function subscribeRoom({ rid }) {
|
this.connectedListener = RocketChat.onStreamData('connected', this.handleConnection);
|
||||||
console.log(`[RCRN] Subscribed to room ${ rid }`);
|
this.disconnectedListener = RocketChat.onStreamData('close', this.handleConnection);
|
||||||
|
this.notifyRoomListener = RocketChat.onStreamData('stream-notify-room', this.handleNotifyRoomReceived);
|
||||||
|
this.messageReceivedListener = RocketChat.onStreamData('stream-room-messages', this.handleMessageReceived);
|
||||||
|
if (!this.isAlive) {
|
||||||
|
this.unsubscribe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handleConnection = () => {
|
unsubscribe = async() => {
|
||||||
this.loadMissedMessages({ rid }).catch(e => console.log(e));
|
console.log(`[RCRN] Unsubscribing from room ${ this.rid }`);
|
||||||
|
this.isAlive = false;
|
||||||
|
if (this.promises) {
|
||||||
|
try {
|
||||||
|
const subscriptions = await this.promises || [];
|
||||||
|
subscriptions.forEach(sub => sub.unsubscribe().catch(() => console.log('unsubscribeRoom')));
|
||||||
|
} catch (e) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.removeListener(this.connectedListener);
|
||||||
|
this.removeListener(this.disconnectedListener);
|
||||||
|
this.removeListener(this.notifyRoomListener);
|
||||||
|
this.removeListener(this.messageReceivedListener);
|
||||||
|
reduxStore.dispatch(clearUserTyping());
|
||||||
|
}
|
||||||
|
|
||||||
|
removeListener = async(promise) => {
|
||||||
|
if (promise) {
|
||||||
|
try {
|
||||||
|
const listener = await promise;
|
||||||
|
listener.stop();
|
||||||
|
} catch (e) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleNotifyRoomReceived = protectedFunction((ddpMessage) => {
|
handleConnection = () => {
|
||||||
|
RocketChat.loadMissedMessages({ rid: this.rid }).catch(e => console.log(e));
|
||||||
|
};
|
||||||
|
|
||||||
|
handleNotifyRoomReceived = protectedFunction((ddpMessage) => {
|
||||||
const [_rid, ev] = ddpMessage.fields.eventName.split('/');
|
const [_rid, ev] = ddpMessage.fields.eventName.split('/');
|
||||||
if (rid !== _rid) {
|
if (this.rid !== _rid) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (ev === 'typing') {
|
if (ev === 'typing') {
|
||||||
|
@ -87,14 +127,14 @@ export default function subscribeRoom({ rid }) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const read = debounce((lastOpen) => {
|
read = debounce((lastOpen) => {
|
||||||
this.readMessages(rid, lastOpen);
|
RocketChat.readMessages(this.rid, lastOpen);
|
||||||
}, 300);
|
}, 300);
|
||||||
|
|
||||||
const handleMessageReceived = protectedFunction((ddpMessage) => {
|
handleMessageReceived = protectedFunction((ddpMessage) => {
|
||||||
const message = buildMessage(EJSON.fromJSONValue(ddpMessage.fields.args[0]));
|
const message = buildMessage(EJSON.fromJSONValue(ddpMessage.fields.args[0]));
|
||||||
const lastOpen = new Date();
|
const lastOpen = new Date();
|
||||||
if (rid !== message.rid) {
|
if (this.rid !== message.rid) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
InteractionManager.runAfterInteractions(async() => {
|
InteractionManager.runAfterInteractions(async() => {
|
||||||
|
@ -126,7 +166,7 @@ export default function subscribeRoom({ rid }) {
|
||||||
batch.push(
|
batch.push(
|
||||||
msgCollection.prepareCreate(protectedFunction((m) => {
|
msgCollection.prepareCreate(protectedFunction((m) => {
|
||||||
m._raw = sanitizedRaw({ id: message._id }, msgCollection.schema);
|
m._raw = sanitizedRaw({ id: message._id }, msgCollection.schema);
|
||||||
m.subscription.id = rid;
|
m.subscription.id = this.rid;
|
||||||
Object.assign(m, message);
|
Object.assign(m, message);
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
@ -150,7 +190,7 @@ export default function subscribeRoom({ rid }) {
|
||||||
batch.push(
|
batch.push(
|
||||||
threadsCollection.prepareCreate(protectedFunction((t) => {
|
threadsCollection.prepareCreate(protectedFunction((t) => {
|
||||||
t._raw = sanitizedRaw({ id: message._id }, threadsCollection.schema);
|
t._raw = sanitizedRaw({ id: message._id }, threadsCollection.schema);
|
||||||
t.subscription.id = rid;
|
t.subscription.id = this.rid;
|
||||||
Object.assign(t, message);
|
Object.assign(t, message);
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
@ -178,7 +218,7 @@ export default function subscribeRoom({ rid }) {
|
||||||
threadMessagesCollection.prepareCreate(protectedFunction((tm) => {
|
threadMessagesCollection.prepareCreate(protectedFunction((tm) => {
|
||||||
tm._raw = sanitizedRaw({ id: message._id }, threadMessagesCollection.schema);
|
tm._raw = sanitizedRaw({ id: message._id }, threadMessagesCollection.schema);
|
||||||
Object.assign(tm, message);
|
Object.assign(tm, message);
|
||||||
tm.subscription.id = rid;
|
tm.subscription.id = this.rid;
|
||||||
tm.rid = message.tmid;
|
tm.rid = message.tmid;
|
||||||
delete tm.tmid;
|
delete tm.tmid;
|
||||||
}))
|
}))
|
||||||
|
@ -186,7 +226,7 @@ export default function subscribeRoom({ rid }) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
read(lastOpen);
|
this.read(lastOpen);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await db.action(async() => {
|
await db.action(async() => {
|
||||||
|
@ -197,49 +237,4 @@ export default function subscribeRoom({ rid }) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const stop = async() => {
|
|
||||||
let params;
|
|
||||||
if (promises) {
|
|
||||||
try {
|
|
||||||
params = await promises;
|
|
||||||
await unsubscribe(params);
|
|
||||||
} catch (error) {
|
|
||||||
// Do nothing
|
|
||||||
}
|
|
||||||
promises = false;
|
|
||||||
}
|
|
||||||
if (connectedListener) {
|
|
||||||
params = await connectedListener;
|
|
||||||
removeListener(params);
|
|
||||||
connectedListener = false;
|
|
||||||
}
|
|
||||||
if (disconnectedListener) {
|
|
||||||
params = await disconnectedListener;
|
|
||||||
removeListener(params);
|
|
||||||
disconnectedListener = false;
|
|
||||||
}
|
|
||||||
if (notifyRoomListener) {
|
|
||||||
params = await notifyRoomListener;
|
|
||||||
removeListener(params);
|
|
||||||
notifyRoomListener = false;
|
|
||||||
}
|
|
||||||
if (messageReceivedListener) {
|
|
||||||
params = await messageReceivedListener;
|
|
||||||
removeListener(params);
|
|
||||||
messageReceivedListener = false;
|
|
||||||
}
|
|
||||||
reduxStore.dispatch(clearUserTyping());
|
|
||||||
};
|
|
||||||
|
|
||||||
connectedListener = this.sdk.onStreamData('connected', handleConnection);
|
|
||||||
disconnectedListener = this.sdk.onStreamData('close', handleConnection);
|
|
||||||
notifyRoomListener = this.sdk.onStreamData('stream-notify-room', handleNotifyRoomReceived);
|
|
||||||
messageReceivedListener = this.sdk.onStreamData('stream-room-messages', handleMessageReceived);
|
|
||||||
|
|
||||||
promises = this.sdk.subscribeRoom(rid);
|
|
||||||
|
|
||||||
return {
|
|
||||||
stop: () => stop()
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
||||||
|
import { InteractionManager } from 'react-native';
|
||||||
|
|
||||||
import database from '../../database';
|
import database from '../../database';
|
||||||
import { merge } from '../helpers/mergeSubscriptionsRooms';
|
import { merge } from '../helpers/mergeSubscriptionsRooms';
|
||||||
|
@ -9,6 +10,9 @@ import random from '../../../utils/random';
|
||||||
import store from '../../createStore';
|
import store from '../../createStore';
|
||||||
import { roomsRequest } from '../../../actions/rooms';
|
import { roomsRequest } from '../../../actions/rooms';
|
||||||
import { notificationReceived } from '../../../actions/notification';
|
import { notificationReceived } from '../../../actions/notification';
|
||||||
|
import { handlePayloadUserInteraction } from '../actions';
|
||||||
|
import buildMessage from '../helpers/buildMessage';
|
||||||
|
import RocketChat from '../../rocketchat';
|
||||||
|
|
||||||
const removeListener = listener => listener.stop();
|
const removeListener = listener => listener.stop();
|
||||||
|
|
||||||
|
@ -16,8 +20,12 @@ let connectedListener;
|
||||||
let disconnectedListener;
|
let disconnectedListener;
|
||||||
let streamListener;
|
let streamListener;
|
||||||
let subServer;
|
let subServer;
|
||||||
|
let subQueue = {};
|
||||||
|
let subTimer = null;
|
||||||
|
let roomQueue = {};
|
||||||
|
let roomTimer = null;
|
||||||
|
const WINDOW_TIME = 500;
|
||||||
|
|
||||||
// TODO: batch execution
|
|
||||||
const createOrUpdateSubscription = async(subscription, room) => {
|
const createOrUpdateSubscription = async(subscription, room) => {
|
||||||
try {
|
try {
|
||||||
const db = database.active;
|
const db = database.active;
|
||||||
|
@ -128,32 +136,32 @@ const createOrUpdateSubscription = async(subscription, room) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (tmp.lastMessage) {
|
if (tmp.lastMessage) {
|
||||||
// const lastMessage = buildMessage(tmp.lastMessage);
|
const lastMessage = buildMessage(tmp.lastMessage);
|
||||||
// const messagesCollection = db.collections.get('messages');
|
const messagesCollection = db.collections.get('messages');
|
||||||
// let messageRecord;
|
let messageRecord;
|
||||||
// try {
|
try {
|
||||||
// messageRecord = await messagesCollection.find(lastMessage._id);
|
messageRecord = await messagesCollection.find(lastMessage._id);
|
||||||
// } catch (error) {
|
} catch (error) {
|
||||||
// // Do nothing
|
// Do nothing
|
||||||
// }
|
}
|
||||||
|
|
||||||
// if (messageRecord) {
|
if (messageRecord) {
|
||||||
// batch.push(
|
batch.push(
|
||||||
// messageRecord.prepareUpdate(() => {
|
messageRecord.prepareUpdate(() => {
|
||||||
// Object.assign(messageRecord, lastMessage);
|
Object.assign(messageRecord, lastMessage);
|
||||||
// })
|
})
|
||||||
// );
|
);
|
||||||
// } else {
|
} else {
|
||||||
// batch.push(
|
batch.push(
|
||||||
// messagesCollection.prepareCreate((m) => {
|
messagesCollection.prepareCreate((m) => {
|
||||||
// m._raw = sanitizedRaw({ id: lastMessage._id }, messagesCollection.schema);
|
m._raw = sanitizedRaw({ id: lastMessage._id }, messagesCollection.schema);
|
||||||
// m.subscription.id = lastMessage.rid;
|
m.subscription.id = lastMessage.rid;
|
||||||
// return Object.assign(m, lastMessage);
|
return Object.assign(m, lastMessage);
|
||||||
// })
|
})
|
||||||
// );
|
);
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
await db.batch(...batch);
|
await db.batch(...batch);
|
||||||
});
|
});
|
||||||
|
@ -162,6 +170,38 @@ const createOrUpdateSubscription = async(subscription, room) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const debouncedUpdateSub = (subscription) => {
|
||||||
|
if (!subTimer) {
|
||||||
|
subTimer = setTimeout(() => {
|
||||||
|
const subBatch = subQueue;
|
||||||
|
subQueue = {};
|
||||||
|
subTimer = null;
|
||||||
|
Object.keys(subBatch).forEach((key) => {
|
||||||
|
InteractionManager.runAfterInteractions(() => {
|
||||||
|
createOrUpdateSubscription(subBatch[key]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, WINDOW_TIME);
|
||||||
|
}
|
||||||
|
subQueue[subscription.rid] = subscription;
|
||||||
|
};
|
||||||
|
|
||||||
|
const debouncedUpdateRoom = (room) => {
|
||||||
|
if (!roomTimer) {
|
||||||
|
roomTimer = setTimeout(() => {
|
||||||
|
const roomBatch = roomQueue;
|
||||||
|
roomQueue = {};
|
||||||
|
roomTimer = null;
|
||||||
|
Object.keys(roomBatch).forEach((key) => {
|
||||||
|
InteractionManager.runAfterInteractions(() => {
|
||||||
|
createOrUpdateSubscription(null, roomBatch[key]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, WINDOW_TIME);
|
||||||
|
}
|
||||||
|
roomQueue[room._id] = room;
|
||||||
|
};
|
||||||
|
|
||||||
export default function subscribeRooms() {
|
export default function subscribeRooms() {
|
||||||
const handleConnection = () => {
|
const handleConnection = () => {
|
||||||
store.dispatch(roomsRequest());
|
store.dispatch(roomsRequest());
|
||||||
|
@ -202,12 +242,12 @@ export default function subscribeRooms() {
|
||||||
log(e);
|
log(e);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await createOrUpdateSubscription(data);
|
debouncedUpdateSub(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (/rooms/.test(ev)) {
|
if (/rooms/.test(ev)) {
|
||||||
if (type === 'updated' || type === 'inserted') {
|
if (type === 'updated' || type === 'inserted') {
|
||||||
await createOrUpdateSubscription(null, data);
|
debouncedUpdateRoom(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (/message/.test(ev)) {
|
if (/message/.test(ev)) {
|
||||||
|
@ -217,6 +257,7 @@ export default function subscribeRooms() {
|
||||||
_id,
|
_id,
|
||||||
rid: args.rid,
|
rid: args.rid,
|
||||||
msg: args.msg,
|
msg: args.msg,
|
||||||
|
blocks: args.blocks,
|
||||||
ts: new Date(),
|
ts: new Date(),
|
||||||
_updatedAt: new Date(),
|
_updatedAt: new Date(),
|
||||||
status: messagesStatus.SENT,
|
status: messagesStatus.SENT,
|
||||||
|
@ -240,8 +281,21 @@ export default function subscribeRooms() {
|
||||||
}
|
}
|
||||||
if (/notification/.test(ev)) {
|
if (/notification/.test(ev)) {
|
||||||
const [notification] = ddpMessage.fields.args;
|
const [notification] = ddpMessage.fields.args;
|
||||||
|
try {
|
||||||
|
const { payload: { rid } } = notification;
|
||||||
|
const subCollection = db.collections.get('subscriptions');
|
||||||
|
const sub = await subCollection.find(rid);
|
||||||
|
notification.title = RocketChat.getRoomTitle(sub);
|
||||||
|
notification.avatar = RocketChat.getRoomAvatar(sub);
|
||||||
|
} catch (e) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
store.dispatch(notificationReceived(notification));
|
store.dispatch(notificationReceived(notification));
|
||||||
}
|
}
|
||||||
|
if (/uiInteraction/.test(ev)) {
|
||||||
|
const { type: eventType, ...args } = type;
|
||||||
|
handlePayloadUserInteraction(eventType, args);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const stop = () => {
|
const stop = () => {
|
||||||
|
@ -257,6 +311,16 @@ export default function subscribeRooms() {
|
||||||
streamListener.then(removeListener);
|
streamListener.then(removeListener);
|
||||||
streamListener = false;
|
streamListener = false;
|
||||||
}
|
}
|
||||||
|
subQueue = {};
|
||||||
|
roomQueue = {};
|
||||||
|
if (subTimer) {
|
||||||
|
clearTimeout(subTimer);
|
||||||
|
subTimer = false;
|
||||||
|
}
|
||||||
|
if (roomTimer) {
|
||||||
|
clearTimeout(roomTimer);
|
||||||
|
roomTimer = false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
connectedListener = this.sdk.onStreamData('connected', handleConnection);
|
connectedListener = this.sdk.onStreamData('connected', handleConnection);
|
||||||
|
|
|
@ -21,7 +21,6 @@ import {
|
||||||
} from '../actions/share';
|
} from '../actions/share';
|
||||||
|
|
||||||
import subscribeRooms from './methods/subscriptions/rooms';
|
import subscribeRooms from './methods/subscriptions/rooms';
|
||||||
import subscribeRoom from './methods/subscriptions/room';
|
|
||||||
|
|
||||||
import protectedFunction from './methods/helpers/protectedFunction';
|
import protectedFunction from './methods/helpers/protectedFunction';
|
||||||
import readMessages from './methods/readMessages';
|
import readMessages from './methods/readMessages';
|
||||||
|
@ -33,6 +32,7 @@ import { getCustomEmojis, setCustomEmojis } from './methods/getCustomEmojis';
|
||||||
import getSlashCommands from './methods/getSlashCommands';
|
import getSlashCommands from './methods/getSlashCommands';
|
||||||
import getRoles from './methods/getRoles';
|
import getRoles from './methods/getRoles';
|
||||||
import canOpenRoom from './methods/canOpenRoom';
|
import canOpenRoom from './methods/canOpenRoom';
|
||||||
|
import triggerBlockAction, { triggerSubmitView, triggerCancel } from './methods/actions';
|
||||||
|
|
||||||
import loadMessagesForRoom from './methods/loadMessagesForRoom';
|
import loadMessagesForRoom from './methods/loadMessagesForRoom';
|
||||||
import loadMissedMessages from './methods/loadMissedMessages';
|
import loadMissedMessages from './methods/loadMissedMessages';
|
||||||
|
@ -73,7 +73,6 @@ const RocketChat = {
|
||||||
log(e);
|
log(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
subscribeRoom,
|
|
||||||
canOpenRoom,
|
canOpenRoom,
|
||||||
createChannel({
|
createChannel({
|
||||||
name, users, type, readOnly, broadcast
|
name, users, type, readOnly, broadcast
|
||||||
|
@ -455,6 +454,27 @@ const RocketChat = {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async clearCache({ server }) {
|
||||||
|
try {
|
||||||
|
const serversDB = database.servers;
|
||||||
|
await serversDB.action(async() => {
|
||||||
|
const serverCollection = serversDB.collections.get('servers');
|
||||||
|
const serverRecord = await serverCollection.find(server);
|
||||||
|
await serverRecord.update((s) => {
|
||||||
|
s.roomsUpdatedAt = null;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const db = database.active;
|
||||||
|
await db.action(() => db.unsafeResetDatabase());
|
||||||
|
} catch (e) {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
},
|
||||||
registerPushToken() {
|
registerPushToken() {
|
||||||
return new Promise(async(resolve) => {
|
return new Promise(async(resolve) => {
|
||||||
const token = getDeviceToken();
|
const token = getDeviceToken();
|
||||||
|
@ -545,18 +565,28 @@ const RocketChat = {
|
||||||
RocketChat.spotlight(searchText, usernames, { users: filterUsers, rooms: filterRooms }),
|
RocketChat.spotlight(searchText, usernames, { users: filterUsers, rooms: filterRooms }),
|
||||||
new Promise((resolve, reject) => this.oldPromise = reject)
|
new Promise((resolve, reject) => this.oldPromise = reject)
|
||||||
]);
|
]);
|
||||||
|
if (filterUsers) {
|
||||||
data = data.concat(users.map(user => ({
|
data = data.concat(users.map(user => ({
|
||||||
...user,
|
...user,
|
||||||
rid: user.username,
|
rid: user.username,
|
||||||
name: user.username,
|
name: user.username,
|
||||||
t: 'd',
|
t: 'd',
|
||||||
search: true
|
search: true
|
||||||
})), rooms.map(room => ({
|
})));
|
||||||
|
}
|
||||||
|
if (filterRooms) {
|
||||||
|
rooms.forEach((room) => {
|
||||||
|
// Check if it exists on local database
|
||||||
|
const index = data.findIndex(item => item.rid === room._id);
|
||||||
|
if (index === -1) {
|
||||||
|
data.push({
|
||||||
rid: room._id,
|
rid: room._id,
|
||||||
...room,
|
...room,
|
||||||
search: true
|
search: true
|
||||||
})));
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
delete this.oldPromise;
|
delete this.oldPromise;
|
||||||
return data;
|
return data;
|
||||||
|
@ -584,6 +614,9 @@ const RocketChat = {
|
||||||
}
|
}
|
||||||
return this.sdk.post('channels.join', { roomId });
|
return this.sdk.post('channels.join', { roomId });
|
||||||
},
|
},
|
||||||
|
triggerBlockAction,
|
||||||
|
triggerSubmitView,
|
||||||
|
triggerCancel,
|
||||||
sendFileMessage,
|
sendFileMessage,
|
||||||
cancelUpload,
|
cancelUpload,
|
||||||
isUploadActive,
|
isUploadActive,
|
||||||
|
@ -669,6 +702,9 @@ const RocketChat = {
|
||||||
subscribe(...args) {
|
subscribe(...args) {
|
||||||
return this.sdk.subscribe(...args);
|
return this.sdk.subscribe(...args);
|
||||||
},
|
},
|
||||||
|
subscribeRoom(...args) {
|
||||||
|
return this.sdk.subscribeRoom(...args);
|
||||||
|
},
|
||||||
unsubscribe(subscription) {
|
unsubscribe(subscription) {
|
||||||
return this.sdk.unsubscribe(subscription);
|
return this.sdk.unsubscribe(subscription);
|
||||||
},
|
},
|
||||||
|
@ -927,7 +963,7 @@ const RocketChat = {
|
||||||
},
|
},
|
||||||
roomTypeToApiType(t) {
|
roomTypeToApiType(t) {
|
||||||
const types = {
|
const types = {
|
||||||
c: 'channels', d: 'im', p: 'groups'
|
c: 'channels', d: 'im', p: 'groups', l: 'channels'
|
||||||
};
|
};
|
||||||
return types[t];
|
return types[t];
|
||||||
},
|
},
|
||||||
|
@ -983,10 +1019,10 @@ const RocketChat = {
|
||||||
rid, updatedSince
|
rid, updatedSince
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
runSlashCommand(command, roomId, params) {
|
runSlashCommand(command, roomId, params, triggerId, tmid) {
|
||||||
// RC 0.60.2
|
// RC 0.60.2
|
||||||
return this.sdk.post('commands.run', {
|
return this.sdk.post('commands.run', {
|
||||||
command, roomId, params
|
command, roomId, params, triggerId, tmid
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
getCommandPreview(command, roomId, params) {
|
getCommandPreview(command, roomId, params) {
|
||||||
|
@ -995,10 +1031,10 @@ const RocketChat = {
|
||||||
command, roomId, params
|
command, roomId, params
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
executeCommandPreview(command, params, roomId, previewItem) {
|
executeCommandPreview(command, params, roomId, previewItem, triggerId, tmid) {
|
||||||
// RC 0.65.0
|
// RC 0.65.0
|
||||||
return this.sdk.post('commands.preview', {
|
return this.sdk.post('commands.preview', {
|
||||||
command, params, roomId, previewItem
|
command, params, roomId, previewItem, triggerId, tmid
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
_setUser(ddpMessage) {
|
_setUser(ddpMessage) {
|
||||||
|
@ -1105,6 +1141,9 @@ const RocketChat = {
|
||||||
const { UI_Use_Real_Name: useRealName } = reduxStore.getState().settings;
|
const { UI_Use_Real_Name: useRealName } = reduxStore.getState().settings;
|
||||||
return ((room.prid || useRealName) && room.fname) || room.name;
|
return ((room.prid || useRealName) && room.fname) || room.name;
|
||||||
},
|
},
|
||||||
|
getRoomAvatar(room) {
|
||||||
|
return room.prid ? room.fname : room.name;
|
||||||
|
},
|
||||||
|
|
||||||
findOrCreateInvite({ rid, days, maxUses }) {
|
findOrCreateInvite({ rid, days, maxUses }) {
|
||||||
// RC 2.4.0
|
// RC 2.4.0
|
||||||
|
|
|
@ -16,6 +16,7 @@ import { removeNotification as removeNotificationAction } from '../../actions/no
|
||||||
import sharedStyles from '../../views/Styles';
|
import sharedStyles from '../../views/Styles';
|
||||||
import { ROW_HEIGHT } from '../../presentation/RoomItem';
|
import { ROW_HEIGHT } from '../../presentation/RoomItem';
|
||||||
import { withTheme } from '../../theme';
|
import { withTheme } from '../../theme';
|
||||||
|
import { getUserSelector } from '../../selectors/login';
|
||||||
|
|
||||||
const AVATAR_SIZE = 48;
|
const AVATAR_SIZE = 48;
|
||||||
const ANIMATION_DURATION = 300;
|
const ANIMATION_DURATION = 300;
|
||||||
|
@ -72,8 +73,7 @@ class NotificationBadge extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
navigation: PropTypes.object,
|
navigation: PropTypes.object,
|
||||||
baseUrl: PropTypes.string,
|
baseUrl: PropTypes.string,
|
||||||
token: PropTypes.string,
|
user: PropTypes.object,
|
||||||
userId: PropTypes.string,
|
|
||||||
notification: PropTypes.object,
|
notification: PropTypes.object,
|
||||||
window: PropTypes.object,
|
window: PropTypes.object,
|
||||||
removeNotification: PropTypes.func,
|
removeNotification: PropTypes.func,
|
||||||
|
@ -158,26 +158,31 @@ class NotificationBadge extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
goToRoom = async() => {
|
goToRoom = async() => {
|
||||||
const { notification: { payload }, navigation, baseUrl } = this.props;
|
const { notification, navigation, baseUrl } = this.props;
|
||||||
|
const { payload } = notification;
|
||||||
const { rid, type, prid } = payload;
|
const { rid, type, prid } = payload;
|
||||||
if (!rid) {
|
if (!rid) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const name = type === 'd' ? payload.sender.username : payload.name;
|
const name = type === 'd' ? payload.sender.username : payload.name;
|
||||||
|
// if sub is not on local database, title will be null, so we use payload from notification
|
||||||
|
const { title = name } = notification;
|
||||||
await navigation.navigate('RoomsListView');
|
await navigation.navigate('RoomsListView');
|
||||||
navigation.navigate('RoomView', {
|
navigation.navigate('RoomView', {
|
||||||
rid, name, t: type, prid, baseUrl
|
rid, name: title, t: type, prid, baseUrl
|
||||||
});
|
});
|
||||||
this.hide();
|
this.hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
baseUrl, token, userId, notification, window, theme
|
baseUrl, user: { id: userId, token }, notification, window, theme
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { message, payload } = notification;
|
const { message, payload } = notification;
|
||||||
const { type } = payload;
|
const { type } = payload;
|
||||||
const name = type === 'd' ? payload.sender.username : payload.name;
|
const name = type === 'd' ? payload.sender.username : payload.name;
|
||||||
|
// if sub is not on local database, title and avatar will be null, so we use payload from notification
|
||||||
|
const { title = name, avatar = name } = notification;
|
||||||
|
|
||||||
let top = 0;
|
let top = 0;
|
||||||
if (isIOS) {
|
if (isIOS) {
|
||||||
|
@ -211,9 +216,9 @@ class NotificationBadge extends React.Component {
|
||||||
background={Touchable.SelectableBackgroundBorderless()}
|
background={Touchable.SelectableBackgroundBorderless()}
|
||||||
>
|
>
|
||||||
<>
|
<>
|
||||||
<Avatar text={name} size={AVATAR_SIZE} type={type} baseUrl={baseUrl} style={styles.avatar} userId={userId} token={token} />
|
<Avatar text={avatar} size={AVATAR_SIZE} type={type} baseUrl={baseUrl} style={styles.avatar} userId={userId} token={token} />
|
||||||
<View style={styles.inner}>
|
<View style={styles.inner}>
|
||||||
<Text style={[styles.roomName, { color: themes[theme].titleText }]} numberOfLines={1}>{name}</Text>
|
<Text style={[styles.roomName, { color: themes[theme].titleText }]} numberOfLines={1}>{title}</Text>
|
||||||
<Text style={[styles.message, { color: themes[theme].titleText }]} numberOfLines={1}>{message}</Text>
|
<Text style={[styles.message, { color: themes[theme].titleText }]} numberOfLines={1}>{message}</Text>
|
||||||
</View>
|
</View>
|
||||||
</>
|
</>
|
||||||
|
@ -227,9 +232,8 @@ class NotificationBadge extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
userId: state.login.user && state.login.user.id,
|
user: getUserSelector(state),
|
||||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
|
baseUrl: state.server.server,
|
||||||
token: state.login.user && state.login.user.token,
|
|
||||||
notification: state.notification
|
notification: state.notification
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -14,9 +14,12 @@ export const onNotification = (notification) => {
|
||||||
} = EJSON.parse(data.ejson);
|
} = EJSON.parse(data.ejson);
|
||||||
|
|
||||||
const types = {
|
const types = {
|
||||||
c: 'channel', d: 'direct', p: 'group'
|
c: 'channel', d: 'direct', p: 'group', l: 'channels'
|
||||||
};
|
};
|
||||||
const roomName = type === 'd' ? sender.username : name;
|
let roomName = type === 'd' ? sender.username : name;
|
||||||
|
if (type === 'l') {
|
||||||
|
roomName = sender.name;
|
||||||
|
}
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
host,
|
host,
|
||||||
|
|
|
@ -2,6 +2,7 @@ import * as types from '../actions/actionsTypes';
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
isFetching: false,
|
isFetching: false,
|
||||||
|
refreshing: false,
|
||||||
failure: false,
|
failure: false,
|
||||||
errorMessage: {},
|
errorMessage: {},
|
||||||
searchText: '',
|
searchText: '',
|
||||||
|
@ -23,15 +24,23 @@ export default function login(state = initialState, action) {
|
||||||
case types.ROOMS.SUCCESS:
|
case types.ROOMS.SUCCESS:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
isFetching: false
|
isFetching: false,
|
||||||
|
refreshing: false
|
||||||
};
|
};
|
||||||
case types.ROOMS.FAILURE:
|
case types.ROOMS.FAILURE:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
isFetching: false,
|
isFetching: false,
|
||||||
|
refreshing: false,
|
||||||
failure: true,
|
failure: true,
|
||||||
errorMessage: action.err
|
errorMessage: action.err
|
||||||
};
|
};
|
||||||
|
case types.ROOMS.REFRESH:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
isFetching: true,
|
||||||
|
refreshing: true
|
||||||
|
};
|
||||||
case types.ROOMS.SET_SEARCH:
|
case types.ROOMS.SET_SEARCH:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
|
|
@ -13,7 +13,7 @@ import EventEmitter from '../utils/events';
|
||||||
import { appStart } from '../actions';
|
import { appStart } from '../actions';
|
||||||
|
|
||||||
const roomTypes = {
|
const roomTypes = {
|
||||||
channel: 'c', direct: 'd', group: 'p'
|
channel: 'c', direct: 'd', group: 'p', channels: 'l'
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleInviteLink = function* handleInviteLink({ params, requireLogin = false }) {
|
const handleInviteLink = function* handleInviteLink({ params, requireLogin = false }) {
|
||||||
|
|
|
@ -121,6 +121,8 @@ const start = function* start({ root }) {
|
||||||
yield Navigation.navigate('SetUsernameView');
|
yield Navigation.navigate('SetUsernameView');
|
||||||
} else if (root === 'outside') {
|
} else if (root === 'outside') {
|
||||||
yield Navigation.navigate('OutsideStack');
|
yield Navigation.navigate('OutsideStack');
|
||||||
|
} else if (root === 'loading') {
|
||||||
|
yield Navigation.navigate('AuthLoading');
|
||||||
}
|
}
|
||||||
RNBootSplash.hide();
|
RNBootSplash.hide();
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import {
|
import {
|
||||||
put, call, takeLatest, select, take, fork, cancel
|
put, call, takeLatest, select, take, fork, cancel, race, delay
|
||||||
} from 'redux-saga/effects';
|
} from 'redux-saga/effects';
|
||||||
import RNUserDefaults from 'rn-user-defaults';
|
import RNUserDefaults from 'rn-user-defaults';
|
||||||
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
||||||
|
@ -19,8 +19,8 @@ import log from '../utils/log';
|
||||||
import I18n from '../i18n';
|
import I18n from '../i18n';
|
||||||
import database from '../lib/database';
|
import database from '../lib/database';
|
||||||
import EventEmitter from '../utils/events';
|
import EventEmitter from '../utils/events';
|
||||||
import Navigation from '../lib/Navigation';
|
|
||||||
import { inviteLinksRequest } from '../actions/inviteLinks';
|
import { inviteLinksRequest } from '../actions/inviteLinks';
|
||||||
|
import { showErrorAlert } from '../utils/info';
|
||||||
|
|
||||||
const getServer = state => state.server.server;
|
const getServer = state => state.server.server;
|
||||||
const loginWithPasswordCall = args => RocketChat.loginWithPassword(args);
|
const loginWithPasswordCall = args => RocketChat.loginWithPassword(args);
|
||||||
|
@ -36,11 +36,11 @@ const handleLoginRequest = function* handleLoginRequest({ credentials, logoutOnE
|
||||||
result = yield call(loginWithPasswordCall, credentials);
|
result = yield call(loginWithPasswordCall, credentials);
|
||||||
}
|
}
|
||||||
return yield put(loginSuccess(result));
|
return yield put(loginSuccess(result));
|
||||||
} catch (error) {
|
} catch (e) {
|
||||||
if (logoutOnError) {
|
if (logoutOnError && (e.data && e.data.message && /you've been logged out by the server/i.test(e.data.message))) {
|
||||||
yield put(logout());
|
yield put(logout(true));
|
||||||
} else {
|
} else {
|
||||||
yield put(loginFailure(error));
|
yield put(loginFailure(e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -142,12 +142,19 @@ const handleLoginSuccess = function* handleLoginSuccess({ user }) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLogout = function* handleLogout() {
|
const handleLogout = function* handleLogout({ forcedByServer }) {
|
||||||
Navigation.navigate('AuthLoading');
|
yield put(appStart('loading'));
|
||||||
const server = yield select(getServer);
|
const server = yield select(getServer);
|
||||||
if (server) {
|
if (server) {
|
||||||
try {
|
try {
|
||||||
yield call(logoutCall, { server });
|
yield call(logoutCall, { server });
|
||||||
|
|
||||||
|
// if the user was logged out by the server
|
||||||
|
if (forcedByServer) {
|
||||||
|
yield put(appStart('outside'));
|
||||||
|
showErrorAlert(I18n.t('Logged_out_by_server'), I18n.t('Oops'));
|
||||||
|
EventEmitter.emit('NewServer', { server });
|
||||||
|
} else {
|
||||||
const serversDB = database.servers;
|
const serversDB = database.servers;
|
||||||
// all servers
|
// all servers
|
||||||
const serversCollection = serversDB.collections.get('servers');
|
const serversCollection = serversDB.collections.get('servers');
|
||||||
|
@ -163,6 +170,7 @@ const handleLogout = function* handleLogout() {
|
||||||
}
|
}
|
||||||
// if there's no servers, go outside
|
// if there's no servers, go outside
|
||||||
yield put(appStart('outside'));
|
yield put(appStart('outside'));
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
yield put(appStart('outside'));
|
yield put(appStart('outside'));
|
||||||
log(e);
|
log(e);
|
||||||
|
@ -185,7 +193,11 @@ const root = function* root() {
|
||||||
while (true) {
|
while (true) {
|
||||||
const params = yield take(types.LOGIN.SUCCESS);
|
const params = yield take(types.LOGIN.SUCCESS);
|
||||||
const loginSuccessTask = yield fork(handleLoginSuccess, params);
|
const loginSuccessTask = yield fork(handleLoginSuccess, params);
|
||||||
yield take(types.SERVER.SELECT_REQUEST);
|
// yield take(types.SERVER.SELECT_REQUEST);
|
||||||
|
yield race({
|
||||||
|
selectRequest: take(types.SERVER.SELECT_REQUEST),
|
||||||
|
timeout: delay(2000)
|
||||||
|
});
|
||||||
yield cancel(loginSuccessTask);
|
yield cancel(loginSuccessTask);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,11 +6,13 @@ import { Q } from '@nozbe/watermelondb';
|
||||||
|
|
||||||
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
||||||
import * as types from '../actions/actionsTypes';
|
import * as types from '../actions/actionsTypes';
|
||||||
import { roomsSuccess, roomsFailure } from '../actions/rooms';
|
import { roomsSuccess, roomsFailure, roomsRefresh } from '../actions/rooms';
|
||||||
import database from '../lib/database';
|
import database from '../lib/database';
|
||||||
import log from '../utils/log';
|
import log from '../utils/log';
|
||||||
import mergeSubscriptionsRooms from '../lib/methods/helpers/mergeSubscriptionsRooms';
|
import mergeSubscriptionsRooms from '../lib/methods/helpers/mergeSubscriptionsRooms';
|
||||||
import RocketChat from '../lib/rocketchat';
|
import RocketChat from '../lib/rocketchat';
|
||||||
|
import buildMessage from '../lib/methods/helpers/buildMessage';
|
||||||
|
import protectedFunction from '../lib/methods/helpers/protectedFunction';
|
||||||
|
|
||||||
const updateRooms = function* updateRooms({ server, newRoomsUpdatedAt }) {
|
const updateRooms = function* updateRooms({ server, newRoomsUpdatedAt }) {
|
||||||
const serversDB = database.servers;
|
const serversDB = database.servers;
|
||||||
|
@ -24,20 +26,26 @@ const updateRooms = function* updateRooms({ server, newRoomsUpdatedAt }) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRoomsRequest = function* handleRoomsRequest() {
|
const handleRoomsRequest = function* handleRoomsRequest({ params }) {
|
||||||
try {
|
try {
|
||||||
const serversDB = database.servers;
|
const serversDB = database.servers;
|
||||||
yield RocketChat.subscribeRooms();
|
RocketChat.subscribeRooms();
|
||||||
const newRoomsUpdatedAt = new Date();
|
const newRoomsUpdatedAt = new Date();
|
||||||
|
let roomsUpdatedAt;
|
||||||
const server = yield select(state => state.server.server);
|
const server = yield select(state => state.server.server);
|
||||||
|
if (params.allData) {
|
||||||
|
yield put(roomsRefresh());
|
||||||
|
} else {
|
||||||
const serversCollection = serversDB.collections.get('servers');
|
const serversCollection = serversDB.collections.get('servers');
|
||||||
const serverRecord = yield serversCollection.find(server);
|
const serverRecord = yield serversCollection.find(server);
|
||||||
const { roomsUpdatedAt } = serverRecord;
|
({ roomsUpdatedAt } = serverRecord);
|
||||||
|
}
|
||||||
const [subscriptionsResult, roomsResult] = yield RocketChat.getRooms(roomsUpdatedAt);
|
const [subscriptionsResult, roomsResult] = yield RocketChat.getRooms(roomsUpdatedAt);
|
||||||
const { subscriptions } = mergeSubscriptionsRooms(subscriptionsResult, roomsResult);
|
const { subscriptions } = mergeSubscriptionsRooms(subscriptionsResult, roomsResult);
|
||||||
|
|
||||||
const db = database.active;
|
const db = database.active;
|
||||||
const subCollection = db.collections.get('subscriptions');
|
const subCollection = db.collections.get('subscriptions');
|
||||||
|
const messagesCollection = db.collections.get('messages');
|
||||||
|
|
||||||
if (subscriptions.length) {
|
if (subscriptions.length) {
|
||||||
const subsIds = subscriptions.map(sub => sub.rid);
|
const subsIds = subscriptions.map(sub => sub.rid);
|
||||||
|
@ -46,6 +54,14 @@ const handleRoomsRequest = function* handleRoomsRequest() {
|
||||||
const subsToCreate = subscriptions.filter(i1 => !existingSubs.find(i2 => i1._id === i2._id));
|
const subsToCreate = subscriptions.filter(i1 => !existingSubs.find(i2 => i1._id === i2._id));
|
||||||
// TODO: subsToDelete?
|
// TODO: subsToDelete?
|
||||||
|
|
||||||
|
const lastMessages = subscriptions
|
||||||
|
.map(sub => sub.lastMessage && buildMessage(sub.lastMessage))
|
||||||
|
.filter(lm => lm);
|
||||||
|
const lastMessagesIds = lastMessages.map(lm => lm._id);
|
||||||
|
const existingMessages = yield messagesCollection.query(Q.where('id', Q.oneOf(lastMessagesIds))).fetch();
|
||||||
|
const messagesToUpdate = existingMessages.filter(i1 => lastMessages.find(i2 => i1.id === i2._id));
|
||||||
|
const messagesToCreate = lastMessages.filter(i1 => !existingMessages.find(i2 => i1._id === i2.id));
|
||||||
|
|
||||||
const allRecords = [
|
const allRecords = [
|
||||||
...subsToCreate.map(subscription => subCollection.prepareCreate((s) => {
|
...subsToCreate.map(subscription => subCollection.prepareCreate((s) => {
|
||||||
s._raw = sanitizedRaw({ id: subscription.rid }, subCollection.schema);
|
s._raw = sanitizedRaw({ id: subscription.rid }, subCollection.schema);
|
||||||
|
@ -56,6 +72,17 @@ const handleRoomsRequest = function* handleRoomsRequest() {
|
||||||
return subscription.prepareUpdate(() => {
|
return subscription.prepareUpdate(() => {
|
||||||
Object.assign(subscription, newSub);
|
Object.assign(subscription, newSub);
|
||||||
});
|
});
|
||||||
|
}),
|
||||||
|
...messagesToCreate.map(message => messagesCollection.prepareCreate(protectedFunction((m) => {
|
||||||
|
m._raw = sanitizedRaw({ id: message._id }, messagesCollection.schema);
|
||||||
|
m.subscription.id = message.rid;
|
||||||
|
return Object.assign(m, message);
|
||||||
|
}))),
|
||||||
|
...messagesToUpdate.map((message) => {
|
||||||
|
const newMessage = lastMessages.find(m => m._id === message.id);
|
||||||
|
return message.prepareUpdate(protectedFunction(() => {
|
||||||
|
Object.assign(message, newMessage);
|
||||||
|
}));
|
||||||
})
|
})
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
|
||||||
|
const getUser = state => state.login.user || {};
|
||||||
|
|
||||||
|
export const getUserSelector = createSelector(
|
||||||
|
[getUser],
|
||||||
|
user => user
|
||||||
|
);
|
|
@ -112,6 +112,11 @@ export const initTabletNav = (setState) => {
|
||||||
KeyCommands.deleteKeyCommands([...defaultCommands, ...keyCommands]);
|
KeyCommands.deleteKeyCommands([...defaultCommands, ...keyCommands]);
|
||||||
setState({ inside: false, showModal: false });
|
setState({ inside: false, showModal: false });
|
||||||
}
|
}
|
||||||
|
if (routeName === 'ModalBlockView') {
|
||||||
|
modalRef.dispatch(NavigationActions.navigate({ routeName, params }));
|
||||||
|
setState({ showModal: true });
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (routeName === 'RoomView') {
|
if (routeName === 'RoomView') {
|
||||||
const resetAction = StackActions.reset({
|
const resetAction = StackActions.reset({
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import hoistNonReactStatics from 'hoist-non-react-statics';
|
import hoistNonReactStatics from 'hoist-non-react-statics';
|
||||||
|
|
||||||
export const ThemeContext = React.createContext(null);
|
export const ThemeContext = React.createContext({ theme: 'light' });
|
||||||
|
|
||||||
export function withTheme(Component) {
|
export function withTheme(Component) {
|
||||||
const ThemedComponent = props => (
|
const ThemedComponent = props => (
|
||||||
|
|
|
@ -1,3 +1,23 @@
|
||||||
import { Alert } from 'react-native';
|
import { Alert } from 'react-native';
|
||||||
|
import I18n from '../i18n';
|
||||||
|
|
||||||
export const showErrorAlert = (message, title, onPress = () => {}) => Alert.alert(title, message, [{ text: 'OK', onPress }], { cancelable: true });
|
export const showErrorAlert = (message, title, onPress = () => {}) => Alert.alert(title, message, [{ text: 'OK', onPress }], { cancelable: true });
|
||||||
|
|
||||||
|
export const showConfirmationAlert = ({ message, callToAction, onPress }) => (
|
||||||
|
Alert.alert(
|
||||||
|
I18n.t('Are_you_sure_question_mark'),
|
||||||
|
message,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
text: I18n.t('Cancel'),
|
||||||
|
style: 'cancel'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: callToAction,
|
||||||
|
style: 'destructive',
|
||||||
|
onPress
|
||||||
|
}
|
||||||
|
],
|
||||||
|
{ cancelable: false }
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
|
@ -0,0 +1,96 @@
|
||||||
|
import { Alert, Linking, AsyncStorage } from 'react-native';
|
||||||
|
|
||||||
|
import { isIOS } from './deviceInfo';
|
||||||
|
import I18n from '../i18n';
|
||||||
|
import { showErrorAlert } from './info';
|
||||||
|
import { STORE_REVIEW_LINK } from '../constants/links';
|
||||||
|
|
||||||
|
const store = isIOS ? 'App Store' : 'Play Store';
|
||||||
|
|
||||||
|
const reviewKey = 'reviewKey';
|
||||||
|
const reviewDelay = 2000;
|
||||||
|
const numberOfDays = 7;
|
||||||
|
const numberOfPositiveEvent = 5;
|
||||||
|
|
||||||
|
const daysBetween = (date1, date2) => {
|
||||||
|
const one_day = 1000 * 60 * 60 * 24;
|
||||||
|
const date1_ms = date1.getTime();
|
||||||
|
const date2_ms = date2.getTime();
|
||||||
|
const difference_ms = date2_ms - date1_ms;
|
||||||
|
return Math.round(difference_ms / one_day);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onCancelPress = () => {
|
||||||
|
try {
|
||||||
|
const data = JSON.stringify({ doneReview: true });
|
||||||
|
return AsyncStorage.setItem(reviewKey, data);
|
||||||
|
} catch (e) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const onReviewPress = async() => {
|
||||||
|
await onCancelPress();
|
||||||
|
try {
|
||||||
|
const supported = await Linking.canOpenURL(STORE_REVIEW_LINK);
|
||||||
|
if (supported) {
|
||||||
|
Linking.openURL(STORE_REVIEW_LINK);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
showErrorAlert(I18n.t('Review_app_unable_store', { store }));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onAskMeLaterPress = () => {
|
||||||
|
try {
|
||||||
|
const data = JSON.stringify({ lastReview: new Date().getTime() });
|
||||||
|
return AsyncStorage.setItem(reviewKey, data);
|
||||||
|
} catch (e) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onReviewButton = { text: I18n.t('Review_app_yes'), onPress: onReviewPress };
|
||||||
|
const onAskMeLaterButton = { text: I18n.t('Review_app_later'), onPress: onAskMeLaterPress };
|
||||||
|
const onCancelButton = { text: I18n.t('Review_app_no'), onPress: onCancelPress };
|
||||||
|
|
||||||
|
const askReview = () => Alert.alert(
|
||||||
|
I18n.t('Review_app_title'),
|
||||||
|
I18n.t('Review_app_desc', { store }),
|
||||||
|
isIOS
|
||||||
|
? [onReviewButton, onAskMeLaterButton, onCancelButton]
|
||||||
|
: [onAskMeLaterButton, onCancelButton, onReviewButton],
|
||||||
|
{
|
||||||
|
cancelable: true,
|
||||||
|
onDismiss: onAskMeLaterPress
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const tryReview = async() => {
|
||||||
|
const data = await AsyncStorage.getItem(reviewKey) || '{}';
|
||||||
|
const reviewData = JSON.parse(data);
|
||||||
|
const { lastReview = 0, doneReview = false } = reviewData;
|
||||||
|
const lastReviewDate = new Date(lastReview);
|
||||||
|
|
||||||
|
// if ask me later was pressed earlier, we can ask for review only after {{numberOfDays}} days
|
||||||
|
// if there's no review and it wasn't dismissed by the user
|
||||||
|
if (daysBetween(lastReviewDate, new Date()) >= numberOfDays && !doneReview) {
|
||||||
|
setTimeout(askReview, reviewDelay);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class ReviewApp {
|
||||||
|
positiveEventCount = 0;
|
||||||
|
|
||||||
|
pushPositiveEvent = () => {
|
||||||
|
if (this.positiveEventCount >= numberOfPositiveEvent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.positiveEventCount += 1;
|
||||||
|
if (this.positiveEventCount === numberOfPositiveEvent) {
|
||||||
|
tryReview();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Review = new ReviewApp();
|
|
@ -11,6 +11,7 @@ import styles from '../Styles';
|
||||||
import { themedHeader } from '../../utils/navigation';
|
import { themedHeader } from '../../utils/navigation';
|
||||||
import { withTheme } from '../../theme';
|
import { withTheme } from '../../theme';
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
|
import { getUserSelector } from '../../selectors/login';
|
||||||
|
|
||||||
class AdminPanelView extends React.Component {
|
class AdminPanelView extends React.Component {
|
||||||
static navigationOptions = ({ navigation, screenProps }) => ({
|
static navigationOptions = ({ navigation, screenProps }) => ({
|
||||||
|
@ -21,12 +22,12 @@ class AdminPanelView extends React.Component {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
baseUrl: PropTypes.string,
|
baseUrl: PropTypes.string,
|
||||||
authToken: PropTypes.string,
|
token: PropTypes.string,
|
||||||
theme: PropTypes.string
|
theme: PropTypes.string
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { baseUrl, authToken, theme } = this.props;
|
const { baseUrl, token, theme } = this.props;
|
||||||
if (!baseUrl) {
|
if (!baseUrl) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -35,7 +36,7 @@ class AdminPanelView extends React.Component {
|
||||||
<StatusBar theme={theme} />
|
<StatusBar theme={theme} />
|
||||||
<WebView
|
<WebView
|
||||||
source={{ uri: `${ baseUrl }/admin/info?layout=embedded` }}
|
source={{ uri: `${ baseUrl }/admin/info?layout=embedded` }}
|
||||||
injectedJavaScript={`Meteor.loginWithToken('${ authToken }', function() { })`}
|
injectedJavaScript={`Meteor.loginWithToken('${ token }', function() { })`}
|
||||||
/>
|
/>
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
);
|
);
|
||||||
|
@ -43,8 +44,8 @@ class AdminPanelView extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
|
baseUrl: state.server.server,
|
||||||
authToken: state.login.user && state.login.user.token
|
token: getUserSelector(state).token
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps)(withTheme(AdminPanelView));
|
export default connect(mapStateToProps)(withTheme(AdminPanelView));
|
||||||
|
|
|
@ -19,6 +19,7 @@ import { formatAttachmentUrl } from '../lib/utils';
|
||||||
import RCActivityIndicator from '../containers/ActivityIndicator';
|
import RCActivityIndicator from '../containers/ActivityIndicator';
|
||||||
import { SaveButton, CloseModalButton } from '../containers/HeaderButton';
|
import { SaveButton, CloseModalButton } from '../containers/HeaderButton';
|
||||||
import { isAndroid } from '../utils/deviceInfo';
|
import { isAndroid } from '../utils/deviceInfo';
|
||||||
|
import { getUserSelector } from '../selectors/login';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
|
@ -142,11 +143,8 @@ class AttachmentView extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
|
baseUrl: state.server.server,
|
||||||
user: {
|
user: getUserSelector(state)
|
||||||
id: state.login.user && state.login.user.id,
|
|
||||||
token: state.login.user && state.login.user.token
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps)(withTheme(AttachmentView));
|
export default connect(mapStateToProps)(withTheme(AttachmentView));
|
||||||
|
|
|
@ -22,6 +22,8 @@ import StatusBar from '../containers/StatusBar';
|
||||||
import { SWITCH_TRACK_COLOR, themes } from '../constants/colors';
|
import { SWITCH_TRACK_COLOR, themes } from '../constants/colors';
|
||||||
import { withTheme } from '../theme';
|
import { withTheme } from '../theme';
|
||||||
import { themedHeader } from '../utils/navigation';
|
import { themedHeader } from '../utils/navigation';
|
||||||
|
import { Review } from '../utils/review';
|
||||||
|
import { getUserSelector } from '../selectors/login';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
|
@ -201,6 +203,8 @@ class CreateChannelView extends React.Component {
|
||||||
create({
|
create({
|
||||||
name: channelName, users, type, readOnly, broadcast
|
name: channelName, users, type, readOnly, broadcast
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Review.pushPositiveEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
removeUser = (user) => {
|
removeUser = (user) => {
|
||||||
|
@ -362,16 +366,13 @@ class CreateChannelView extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
|
baseUrl: state.server.server,
|
||||||
error: state.createChannel.error,
|
error: state.createChannel.error,
|
||||||
failure: state.createChannel.failure,
|
failure: state.createChannel.failure,
|
||||||
isFetching: state.createChannel.isFetching,
|
isFetching: state.createChannel.isFetching,
|
||||||
result: state.createChannel.result,
|
result: state.createChannel.result,
|
||||||
users: state.selectedUsers.users,
|
users: state.selectedUsers.users,
|
||||||
user: {
|
user: getUserSelector(state)
|
||||||
id: state.login.user && state.login.user.id,
|
|
||||||
token: state.login.user && state.login.user.token
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
|
|
@ -23,6 +23,7 @@ import { withTheme } from '../../theme';
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import { themedHeader } from '../../utils/navigation';
|
import { themedHeader } from '../../utils/navigation';
|
||||||
|
import { getUserSelector } from '../../selectors/login';
|
||||||
|
|
||||||
class DirectoryView extends React.Component {
|
class DirectoryView extends React.Component {
|
||||||
static navigationOptions = ({ navigation, screenProps }) => {
|
static navigationOptions = ({ navigation, screenProps }) => {
|
||||||
|
@ -253,11 +254,8 @@ class DirectoryView extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
|
baseUrl: state.server.server,
|
||||||
user: {
|
user: getUserSelector(state),
|
||||||
id: state.login.user && state.login.user.id,
|
|
||||||
token: state.login.user && state.login.user.token
|
|
||||||
},
|
|
||||||
isFederationEnabled: state.settings.FEDERATION_Enabled
|
isFederationEnabled: state.settings.FEDERATION_Enabled
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,7 @@ class InviteUsersView extends React.Component {
|
||||||
|
|
||||||
share = () => {
|
share = () => {
|
||||||
const { invite } = this.props;
|
const { invite } = this.props;
|
||||||
if (!invite) {
|
if (!invite || !invite.url) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Share.share({ message: invite.url });
|
Share.share({ message: invite.url });
|
||||||
|
|
|
@ -2,14 +2,27 @@ import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import JitsiMeet, { JitsiMeetView as RNJitsiMeetView } from 'react-native-jitsi-meet';
|
import JitsiMeet, { JitsiMeetView as RNJitsiMeetView } from 'react-native-jitsi-meet';
|
||||||
import BackgroundTimer from 'react-native-background-timer';
|
import BackgroundTimer from 'react-native-background-timer';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import RocketChat from '../lib/rocketchat';
|
import RocketChat from '../lib/rocketchat';
|
||||||
|
import { getUserSelector } from '../selectors/login';
|
||||||
|
|
||||||
import sharedStyles from './Styles';
|
import sharedStyles from './Styles';
|
||||||
|
|
||||||
|
const formatUrl = (url, baseUrl, uriSize, avatarAuthURLFragment) => (
|
||||||
|
`${ baseUrl }/avatar/${ url }?format=png&width=${ uriSize }&height=${ uriSize }${ avatarAuthURLFragment }`
|
||||||
|
);
|
||||||
|
|
||||||
class JitsiMeetView extends React.Component {
|
class JitsiMeetView extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
navigation: PropTypes.object
|
navigation: PropTypes.object,
|
||||||
|
baseUrl: PropTypes.string,
|
||||||
|
user: PropTypes.shape({
|
||||||
|
id: PropTypes.string,
|
||||||
|
username: PropTypes.string,
|
||||||
|
name: PropTypes.string,
|
||||||
|
token: PropTypes.string
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -21,14 +34,25 @@ class JitsiMeetView extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const { navigation } = this.props;
|
const { navigation, user, baseUrl } = this.props;
|
||||||
|
const {
|
||||||
|
name: displayName, id: userId, token, username
|
||||||
|
} = user;
|
||||||
|
|
||||||
|
const avatarAuthURLFragment = `&rc_token=${ token }&rc_uid=${ userId }`;
|
||||||
|
const avatar = formatUrl(username, baseUrl, 100, avatarAuthURLFragment);
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
const userInfo = {
|
||||||
|
displayName,
|
||||||
|
avatar
|
||||||
|
};
|
||||||
const url = navigation.getParam('url');
|
const url = navigation.getParam('url');
|
||||||
const onlyAudio = navigation.getParam('onlyAudio', false);
|
const onlyAudio = navigation.getParam('onlyAudio', false);
|
||||||
if (onlyAudio) {
|
if (onlyAudio) {
|
||||||
JitsiMeet.audioCall(url);
|
JitsiMeet.audioCall(url, userInfo);
|
||||||
} else {
|
} else {
|
||||||
JitsiMeet.call(url);
|
JitsiMeet.call(url, userInfo);
|
||||||
}
|
}
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
@ -71,4 +95,9 @@ class JitsiMeetView extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default JitsiMeetView;
|
const mapStateToProps = state => ({
|
||||||
|
user: getUserSelector(state),
|
||||||
|
baseUrl: state.server.server
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(JitsiMeetView);
|
||||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { FlatList } from 'react-native';
|
import { FlatList } from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { SafeAreaView, NavigationActions } from 'react-navigation';
|
import { SafeAreaView } from 'react-navigation';
|
||||||
|
|
||||||
import RocketChat from '../../lib/rocketchat';
|
import RocketChat from '../../lib/rocketchat';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
|
@ -18,6 +18,9 @@ import Separator from '../../containers/Separator';
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
import { withTheme } from '../../theme';
|
import { withTheme } from '../../theme';
|
||||||
import { themedHeader } from '../../utils/navigation';
|
import { themedHeader } from '../../utils/navigation';
|
||||||
|
import { appStart as appStartAction } from '../../actions';
|
||||||
|
import { getUserSelector } from '../../selectors/login';
|
||||||
|
import database from '../../lib/database';
|
||||||
|
|
||||||
const LANGUAGES = [
|
const LANGUAGES = [
|
||||||
{
|
{
|
||||||
|
@ -29,6 +32,9 @@ const LANGUAGES = [
|
||||||
}, {
|
}, {
|
||||||
label: 'English',
|
label: 'English',
|
||||||
value: 'en'
|
value: 'en'
|
||||||
|
}, {
|
||||||
|
label: 'Español (ES)',
|
||||||
|
value: 'es-ES'
|
||||||
}, {
|
}, {
|
||||||
label: 'Français',
|
label: 'Français',
|
||||||
value: 'fr'
|
value: 'fr'
|
||||||
|
@ -41,6 +47,12 @@ const LANGUAGES = [
|
||||||
}, {
|
}, {
|
||||||
label: 'Russian',
|
label: 'Russian',
|
||||||
value: 'ru'
|
value: 'ru'
|
||||||
|
}, {
|
||||||
|
label: 'Nederlands',
|
||||||
|
value: 'nl'
|
||||||
|
}, {
|
||||||
|
label: 'Italiano',
|
||||||
|
value: 'it'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -51,23 +63,23 @@ class LanguageView extends React.Component {
|
||||||
})
|
})
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
userLanguage: PropTypes.string,
|
user: PropTypes.object,
|
||||||
navigation: PropTypes.object,
|
|
||||||
setUser: PropTypes.func,
|
setUser: PropTypes.func,
|
||||||
|
appStart: PropTypes.func,
|
||||||
theme: PropTypes.string
|
theme: PropTypes.string
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
language: props.userLanguage ? props.userLanguage : 'en',
|
language: props.user ? props.user.language : 'en',
|
||||||
saving: false
|
saving: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps, nextState) {
|
shouldComponentUpdate(nextProps, nextState) {
|
||||||
const { language, saving } = this.state;
|
const { language, saving } = this.state;
|
||||||
const { userLanguage, theme } = this.props;
|
const { user, theme } = this.props;
|
||||||
if (nextProps.theme !== theme) {
|
if (nextProps.theme !== theme) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -77,15 +89,15 @@ class LanguageView extends React.Component {
|
||||||
if (nextState.saving !== saving) {
|
if (nextState.saving !== saving) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (nextProps.userLanguage !== userLanguage) {
|
if (nextProps.user.language !== user.language) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
formIsChanged = (language) => {
|
formIsChanged = (language) => {
|
||||||
const { userLanguage } = this.props;
|
const { user } = this.props;
|
||||||
return (userLanguage !== language);
|
return (user.language !== language);
|
||||||
}
|
}
|
||||||
|
|
||||||
submit = async(language) => {
|
submit = async(language) => {
|
||||||
|
@ -95,12 +107,12 @@ class LanguageView extends React.Component {
|
||||||
|
|
||||||
this.setState({ saving: true });
|
this.setState({ saving: true });
|
||||||
|
|
||||||
const { userLanguage, setUser, navigation } = this.props;
|
const { user, setUser, appStart } = this.props;
|
||||||
|
|
||||||
const params = {};
|
const params = {};
|
||||||
|
|
||||||
// language
|
// language
|
||||||
if (userLanguage !== language) {
|
if (user.language !== language) {
|
||||||
params.language = language;
|
params.language = language;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,18 +120,27 @@ class LanguageView extends React.Component {
|
||||||
await RocketChat.saveUserPreferences(params);
|
await RocketChat.saveUserPreferences(params);
|
||||||
setUser({ language: params.language });
|
setUser({ language: params.language });
|
||||||
|
|
||||||
this.setState({ saving: false });
|
const serversDB = database.servers;
|
||||||
setTimeout(() => {
|
const usersCollection = serversDB.collections.get('users');
|
||||||
navigation.reset([NavigationActions.navigate({ routeName: 'SettingsView' })], 0);
|
await serversDB.action(async() => {
|
||||||
navigation.navigate('RoomsListView');
|
try {
|
||||||
}, 300);
|
const userRecord = await usersCollection.find(user.id);
|
||||||
|
await userRecord.update((record) => {
|
||||||
|
record.language = params.language;
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await appStart('loading');
|
||||||
|
await appStart('inside');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.setState({ saving: false });
|
|
||||||
setTimeout(() => {
|
|
||||||
showErrorAlert(I18n.t('There_was_an_error_while_action', { action: I18n.t('saving_preferences') }));
|
showErrorAlert(I18n.t('There_was_an_error_while_action', { action: I18n.t('saving_preferences') }));
|
||||||
log(e);
|
log(e);
|
||||||
}, 300);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.setState({ saving: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
renderSeparator = () => {
|
renderSeparator = () => {
|
||||||
|
@ -179,11 +200,12 @@ class LanguageView extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
userLanguage: state.login.user && state.login.user.language
|
user: getUserSelector(state)
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
const mapDispatchToProps = dispatch => ({
|
||||||
setUser: params => dispatch(setUserAction(params))
|
setUser: params => dispatch(setUserAction(params)),
|
||||||
|
appStart: params => dispatch(appStartAction(params))
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(LanguageView));
|
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(LanguageView));
|
||||||
|
|
|
@ -17,13 +17,14 @@ import { themes } from '../../constants/colors';
|
||||||
import { withTheme } from '../../theme';
|
import { withTheme } from '../../theme';
|
||||||
import { withSplit } from '../../split';
|
import { withSplit } from '../../split';
|
||||||
import { themedHeader } from '../../utils/navigation';
|
import { themedHeader } from '../../utils/navigation';
|
||||||
|
import { getUserSelector } from '../../selectors/login';
|
||||||
|
|
||||||
const ACTION_INDEX = 0;
|
const ACTION_INDEX = 0;
|
||||||
const CANCEL_INDEX = 1;
|
const CANCEL_INDEX = 1;
|
||||||
|
|
||||||
class MessagesView extends React.Component {
|
class MessagesView extends React.Component {
|
||||||
static navigationOptions = ({ navigation, screenProps }) => ({
|
static navigationOptions = ({ navigation, screenProps }) => ({
|
||||||
title: navigation.state.params.name,
|
title: I18n.t(navigation.state.params.name),
|
||||||
...themedHeader(screenProps.theme)
|
...themedHeader(screenProps.theme)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -73,6 +74,14 @@ class MessagesView extends React.Component {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
navToRoomInfo = (navParam) => {
|
||||||
|
const { navigation, user } = this.props;
|
||||||
|
if (navParam.rid === user.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
navigation.navigate('RoomInfoView', navParam);
|
||||||
|
}
|
||||||
|
|
||||||
defineMessagesViewContent = (name) => {
|
defineMessagesViewContent = (name) => {
|
||||||
const { messages } = this.state;
|
const { messages } = this.state;
|
||||||
const { user, baseUrl, theme } = this.props;
|
const { user, baseUrl, theme } = this.props;
|
||||||
|
@ -87,7 +96,8 @@ class MessagesView extends React.Component {
|
||||||
isHeader: true,
|
isHeader: true,
|
||||||
attachments: item.attachments || [],
|
attachments: item.attachments || [],
|
||||||
showAttachment: this.showAttachment,
|
showAttachment: this.showAttachment,
|
||||||
getCustomEmoji: this.getCustomEmoji
|
getCustomEmoji: this.getCustomEmoji,
|
||||||
|
navToRoomInfo: this.navToRoomInfo
|
||||||
});
|
});
|
||||||
|
|
||||||
return ({
|
return ({
|
||||||
|
@ -306,12 +316,8 @@ class MessagesView extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
|
baseUrl: state.server.server,
|
||||||
user: {
|
user: getUserSelector(state),
|
||||||
id: state.login.user && state.login.user.id,
|
|
||||||
username: state.login.user && state.login.user.username,
|
|
||||||
token: state.login.user && state.login.user.token
|
|
||||||
},
|
|
||||||
customEmojis: state.customEmojis
|
customEmojis: state.customEmojis
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,278 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { StyleSheet, View } from 'react-native';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import isEqual from 'lodash/isEqual';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
|
||||||
|
|
||||||
|
import { withTheme } from '../theme';
|
||||||
|
import { themedHeader } from '../utils/navigation';
|
||||||
|
import EventEmitter from '../utils/events';
|
||||||
|
import { themes } from '../constants/colors';
|
||||||
|
import { CustomHeaderButtons, Item } from '../containers/HeaderButton';
|
||||||
|
import { modalBlockWithContext } from '../containers/UIKit/MessageBlock';
|
||||||
|
import RocketChat from '../lib/rocketchat';
|
||||||
|
import ActivityIndicator from '../containers/ActivityIndicator';
|
||||||
|
import { MODAL_ACTIONS, CONTAINER_TYPES } from '../lib/methods/actions';
|
||||||
|
|
||||||
|
import sharedStyles from './Styles';
|
||||||
|
import { textParser } from '../containers/UIKit/utils';
|
||||||
|
import Navigation from '../lib/Navigation';
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
paddingHorizontal: 16
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
paddingVertical: 16
|
||||||
|
},
|
||||||
|
submit: {
|
||||||
|
...sharedStyles.textSemibold,
|
||||||
|
fontSize: 16
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.fromEntries = Object.fromEntries || (arr => arr.reduce((acc, [k, v]) => ((acc[k] = v, acc)), {}));
|
||||||
|
const groupStateByBlockIdMap = (obj, [key, { blockId, value }]) => {
|
||||||
|
obj[blockId] = obj[blockId] || {};
|
||||||
|
obj[blockId][key] = value;
|
||||||
|
return obj;
|
||||||
|
};
|
||||||
|
const groupStateByBlockId = obj => Object.entries(obj).reduce(groupStateByBlockIdMap, {});
|
||||||
|
const filterInputFields = ({ element, elements = [] }) => {
|
||||||
|
if (element && element.initialValue) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (elements.length && elements.map(e => ({ element: e })).filter(filterInputFields).length) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const mapElementToState = ({ element, blockId, elements = [] }) => {
|
||||||
|
if (elements.length) {
|
||||||
|
return elements.map(e => ({ element: e, blockId })).filter(filterInputFields).map(mapElementToState);
|
||||||
|
}
|
||||||
|
return [element.actionId, { value: element.initialValue, blockId }];
|
||||||
|
};
|
||||||
|
const reduceState = (obj, el) => (Array.isArray(el[0]) ? { ...obj, ...Object.fromEntries(el) } : { ...obj, [el[0]]: el[1] });
|
||||||
|
|
||||||
|
class ModalBlockView extends React.Component {
|
||||||
|
static navigationOptions = ({ navigation, screenProps }) => {
|
||||||
|
const { theme, closeModal } = screenProps;
|
||||||
|
const data = navigation.getParam('data');
|
||||||
|
const cancel = navigation.getParam('cancel', () => {});
|
||||||
|
const submitting = navigation.getParam('submitting', false);
|
||||||
|
const { view } = data;
|
||||||
|
const { title, submit, close } = view;
|
||||||
|
return {
|
||||||
|
title: textParser([title]),
|
||||||
|
...themedHeader(theme),
|
||||||
|
headerLeft: (
|
||||||
|
<CustomHeaderButtons>
|
||||||
|
<Item
|
||||||
|
title={textParser([close.text])}
|
||||||
|
style={styles.submit}
|
||||||
|
onPress={!submitting && (() => cancel({ closeModal }))}
|
||||||
|
testID='close-modal-uikit'
|
||||||
|
/>
|
||||||
|
</CustomHeaderButtons>
|
||||||
|
),
|
||||||
|
headerRight: (
|
||||||
|
<CustomHeaderButtons>
|
||||||
|
<Item
|
||||||
|
title={textParser([submit.text])}
|
||||||
|
style={styles.submit}
|
||||||
|
onPress={!submitting && (navigation.getParam('submit', () => {}))}
|
||||||
|
testID='submit-modal-uikit'
|
||||||
|
/>
|
||||||
|
</CustomHeaderButtons>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
navigation: PropTypes.object,
|
||||||
|
theme: PropTypes.string,
|
||||||
|
language: PropTypes.string,
|
||||||
|
user: PropTypes.shape({
|
||||||
|
id: PropTypes.string,
|
||||||
|
token: PropTypes.string
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.submitting = false;
|
||||||
|
const { navigation } = props;
|
||||||
|
const data = navigation.getParam('data');
|
||||||
|
this.values = data.view.blocks.filter(filterInputFields).map(mapElementToState).reduce(reduceState, {});
|
||||||
|
this.state = {
|
||||||
|
data,
|
||||||
|
loading: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const { data } = this.state;
|
||||||
|
const { navigation } = this.props;
|
||||||
|
const { viewId } = data;
|
||||||
|
navigation.setParams({ submit: this.submit, cancel: this.cancel });
|
||||||
|
|
||||||
|
EventEmitter.addEventListener(viewId, this.handleUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldComponentUpdate(nextProps, nextState) {
|
||||||
|
if (!isEqual(nextProps, this.props)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!isEqual(nextState, this.state)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
const { navigation } = this.props;
|
||||||
|
const oldData = prevProps.navigation.getParam('data', {});
|
||||||
|
const newData = navigation.getParam('data', {});
|
||||||
|
if (!isEqual(oldData, newData)) {
|
||||||
|
navigation.push('ModalBlockView', { data: newData });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
const { data } = this.state;
|
||||||
|
const { viewId } = data;
|
||||||
|
EventEmitter.removeListener(viewId, this.handleUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleUpdate = ({ type, ...data }) => {
|
||||||
|
if ([MODAL_ACTIONS.ERRORS].includes(type)) {
|
||||||
|
const { errors } = data;
|
||||||
|
this.setState({ errors });
|
||||||
|
} else {
|
||||||
|
this.setState({ data });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
cancel = async({ closeModal }) => {
|
||||||
|
const { data } = this.state;
|
||||||
|
const { appId, viewId, view } = data;
|
||||||
|
|
||||||
|
// handle tablet case
|
||||||
|
if (closeModal) {
|
||||||
|
closeModal();
|
||||||
|
} else {
|
||||||
|
Navigation.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await RocketChat.triggerCancel({
|
||||||
|
appId,
|
||||||
|
viewId,
|
||||||
|
view: {
|
||||||
|
...view,
|
||||||
|
id: viewId,
|
||||||
|
state: groupStateByBlockId(this.values)
|
||||||
|
},
|
||||||
|
isCleared: true
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
submit = async() => {
|
||||||
|
const { data } = this.state;
|
||||||
|
const { navigation } = this.props;
|
||||||
|
navigation.setParams({ submitting: true });
|
||||||
|
|
||||||
|
const { appId, viewId } = data;
|
||||||
|
this.setState({ loading: true });
|
||||||
|
try {
|
||||||
|
await RocketChat.triggerSubmitView({
|
||||||
|
viewId,
|
||||||
|
appId,
|
||||||
|
payload: {
|
||||||
|
view: {
|
||||||
|
id: viewId,
|
||||||
|
state: groupStateByBlockId(this.values)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
navigation.setParams({ submitting: false });
|
||||||
|
this.setState({ loading: false });
|
||||||
|
};
|
||||||
|
|
||||||
|
action = async({ actionId, value, blockId }) => {
|
||||||
|
const { data } = this.state;
|
||||||
|
const { mid, appId, viewId } = data;
|
||||||
|
await RocketChat.triggerBlockAction({
|
||||||
|
container: {
|
||||||
|
type: CONTAINER_TYPES.VIEW,
|
||||||
|
id: viewId
|
||||||
|
},
|
||||||
|
actionId,
|
||||||
|
appId,
|
||||||
|
value,
|
||||||
|
blockId,
|
||||||
|
mid
|
||||||
|
});
|
||||||
|
this.changeState({ actionId, value, blockId });
|
||||||
|
}
|
||||||
|
|
||||||
|
changeState = ({ actionId, value, blockId = 'default' }) => {
|
||||||
|
this.values[actionId] = {
|
||||||
|
blockId,
|
||||||
|
value
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { data, loading, errors } = this.state;
|
||||||
|
const { theme, language } = this.props;
|
||||||
|
const { values } = this;
|
||||||
|
const { view } = data;
|
||||||
|
const { blocks } = view;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<KeyboardAwareScrollView
|
||||||
|
style={[
|
||||||
|
styles.container,
|
||||||
|
{ backgroundColor: themes[theme].auxiliaryBackground }
|
||||||
|
]}
|
||||||
|
keyboardShouldPersistTaps='always'
|
||||||
|
>
|
||||||
|
<View style={styles.content}>
|
||||||
|
{
|
||||||
|
React.createElement(
|
||||||
|
modalBlockWithContext({
|
||||||
|
action: this.action,
|
||||||
|
state: this.changeState,
|
||||||
|
...data
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
blocks,
|
||||||
|
errors,
|
||||||
|
language,
|
||||||
|
values
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</View>
|
||||||
|
{loading ? <ActivityIndicator absolute size='large' theme={theme} /> : null}
|
||||||
|
</KeyboardAwareScrollView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = state => ({
|
||||||
|
language: state.login.user && state.login.user.language
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(withTheme(ModalBlockView));
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue