Compare commits

...

60 Commits

Author SHA1 Message Date
Elias Nahum
4ac2d20f52 Bump app build number to 204 (#2955) 2019-07-05 14:33:49 -04:00
Elias Nahum
b837f763d7 Bump app version number to 1.20.2 (#2954) 2019-07-05 14:24:26 -04:00
Elias Nahum
b793568c8b Update device info ref (#2953) 2019-07-05 13:38:40 -04:00
Elias Nahum
8b16a670b6 MM-16392 Split view support & hide sidebar on tablets (#2898) 2019-07-05 12:38:03 -04:00
Elias Nahum
b711cfa085 Bump app build number to 201 (#2903) 2019-06-19 18:47:44 -04:00
Elias Nahum
046c87997f MM-16432 Fix missing posts when using since API (#2901) 2019-06-19 18:27:43 -04:00
Elias Nahum
0d22d62819 Fix crash on Android by Updating JSC (#2900) 2019-06-19 16:18:31 -04:00
Elias Nahum
9d5bff69c8 Bump app build number to 200 2019-06-18 18:57:08 -04:00
Elias Nahum
6f3886cc4e Bump app version number to 1.20.1 2019-06-18 18:56:45 -04:00
Elias Nahum
be50cf6e6c Update gradle to use android-jsc as instructed (#2893) 2019-06-18 18:37:53 -04:00
Miguel Alatzar
387d4094b7 Update react-native-device-info and fix status value: (#2892) 2019-06-17 20:23:27 -07:00
Elias Nahum
8817390b57 Bump app build number to 198 (#2884) 2019-06-13 17:57:17 -04:00
Sudheer
c8e4bc01ad MM-16228 Add compatibility for removal of ExperimentalEnablePostMetadata flag from config (#2883) 2019-06-13 16:29:54 -04:00
Elias Nahum
82b12eb44f MM-16280 Fix when post fail to send (#2880) 2019-06-12 16:22:13 -04:00
Elias Nahum
7d250f97a6 MM-16281 Fix regression on android push notification messages (#2879) 2019-06-12 15:10:29 -04:00
Elias Nahum
f72791316f translations PR 20190611 (#2874) 2019-06-11 15:31:51 -04:00
Elias Nahum
1a7c1eff66 Bump app build number to 197 (#2872) 2019-06-07 09:55:00 -04:00
Miguel Alatzar
e1e2d9aedb Allow excluding archived channels in getChannelsBySections (#2871) 2019-06-06 15:52:36 -07:00
Elias Nahum
a95fcb3847 MM-16061 Keep unread channels in place on Tablets (#2865) 2019-06-06 15:16:47 -04:00
Elias Nahum
ffe5a89a46 Show broken image for inline images without dimensions (#2868) 2019-06-06 15:15:09 -04:00
Elias Nahum
f76bd73c0b Fix image preview width on tablets (#2867) 2019-06-06 21:04:36 +08:00
Elias Nahum
a05d11635e Fix file upload not completing (#2866)
* Fix file upload not completing

* Update snapshots
2019-06-06 08:39:57 -04:00
Elias Nahum
ddbc89bb9a MM-16070 Fix channel sidebar state while filtering (#2864) 2019-06-05 19:34:17 -04:00
Elias Nahum
151a304857 Fix opengraph image on tablet to account for the sidebar (#2863) 2019-06-05 17:59:27 -04:00
Elias Nahum
2a443b35f5 MM-15643 Fix race condition to prompt for passcode and (#2862)
detect if passcode has been removed while the app in the background
2019-06-05 11:08:35 -04:00
Elias Nahum
2a8cf194cb MM-16013 make paperplain animation faster (#2860) 2019-06-05 11:07:27 -04:00
Elias Nahum
5a471fd8e0 translations PR 20190603 (#2859) 2019-06-05 11:05:51 -04:00
Dan Maas
eb49f5bf41 Update NOTICE.txt (#2856)
- add new dependencies @react-native-community/async-storage and @react-native-community-netinfo
- misc copyright metadata updates
2019-06-05 11:05:22 -04:00
Saturnino Abril
0b8eb7aac6 fix server version for Android telemetry (#2857) 2019-06-03 09:03:18 -07:00
Elias Nahum
682dbae03b MM-15959 disable android firebase analytics (#2854) 2019-06-03 09:49:03 -04:00
Elias Nahum
b4ffdbf4e8 Bump app build number to 196 (#2853) 2019-05-31 14:23:00 -04:00
Elias Nahum
f9d7f74e38 Update netInfo to check for internet connectivity (#2850)
* Update netInfo to check for internet connectivity instead of using apple.com as a reference

* Fix network indicator position on tablets
2019-05-31 13:34:42 -04:00
Saturnino Abril
d832976675 configure telemetry on build (#2852) 2019-06-01 01:17:54 +08:00
Elias Nahum
ad0c3a5ea2 MM-15912 Fix uploads stuck at 100% (#2847) 2019-05-30 11:57:45 -07:00
Harrison Healey
163f0b7fdf MM-15643 Better close app when checking for pin code (#2846) 2019-05-30 11:47:15 -07:00
Elias Nahum
cdfec92669 Bump app build number to 195 (#2843) 2019-05-28 16:35:58 -04:00
Harrison Healey
64fbff6e71 MM-15643 Send user to settings when passcode is required (#2836)
* MM-15643 Send user to settings when passcode is required

* Allow access to the managed config in the iOS extensions
2019-05-27 19:39:43 -04:00
Elias Nahum
fe2fb1d857 translations PR 20190527 (#2837) 2019-05-27 19:22:49 -04:00
Elias Nahum
2c7116bc7f Fix autocomplete showing behind the keyboard on iOS and not working on Android (#2830)
* Fix autocomplete showing behind the keyboard on iOS and not working on Android

* Unbundle config for Android

* Dismiss keyboard on post long press, fix scroll to bottom on new message and update tests

* Add a timeout before scrolling to give time to render the last post

* Fix crash on Android
2019-05-27 19:21:46 -04:00
Saturnino Abril
03c3b91c1c fix build for unsigned and ios-sim-x86_64 (#2834) 2019-05-27 19:13:11 -04:00
Miguel Alatzar
ce733903b6 Add unit test for PostBody's measurePost (#2833) 2019-05-25 11:59:32 -04:00
Miguel Alatzar
70218d2173 Allow updating of isLongPost after editing message (#2829) 2019-05-24 14:50:30 -07:00
Elias Nahum
e5b5fefa09 MM-15667 if post was removed exclude it from postsInChannel (#2831) 2019-05-24 15:38:33 -04:00
Miguel Alatzar
e1c5536da8 [MM 15552] Determine number of placeholder rows from height (#2827)
* Calculate maxRows in ChannelLoader component

* Update rn-placeholder

* Fix style check
2019-05-24 12:01:12 -04:00
Elias Nahum
05a1c7c3d8 update react-native-device-info crash fix (#2824) 2019-05-23 09:45:12 -07:00
Elias Nahum
966db803ff Allow drawer pan responder when switching teams (#2823) 2019-05-23 09:01:39 -07:00
Saturnino Abril
b53c62bac4 fix link on Android beta signup message (#2822) 2019-05-23 07:26:11 +08:00
Elias Nahum
58b1f2e701 Fix android release link (#2821) 2019-05-23 07:04:03 +08:00
Elias Nahum
d90b283664 move apk using the full path (#2820) 2019-05-22 16:46:47 -04:00
Elias Nahum
44ab3ffa2a Fix Build script apk location (#2819) 2019-05-22 14:55:37 -04:00
Elias Nahum
146af60a32 Bump Version & Build number (#2818)
* Bump app version number to 1.20.0

* Bump app build number to 194

* Update fastlane
2019-05-22 12:05:36 -04:00
Harrison Healey
ac29021c88 MM-15582 Don't show forgot password link without email/username login (#2811) 2019-05-22 11:55:55 -04:00
Elias Nahum
3e28f95213 translations PR 20190522 (#2817) 2019-05-22 11:44:07 -04:00
Miguel Alatzar
a8938dad39 Upgrade react-native-sentry (#2815) 2019-05-22 10:35:45 -04:00
Elias Nahum
0eda4f49a3 Fix code so it can be parsed by mmjstool (#2816) 2019-05-22 09:32:14 -04:00
Miguel Alatzar
8d4046d28c Update mattermost-redux 2019-05-21 13:08:09 -07:00
Miguel Alatzar
3a15eaa3dc Update mattermost-redux (#2813) 2019-05-21 13:05:37 -07:00
Miguel Alatzar
c617f96162 Allow flagging of read-only channel posts (#2801) 2019-05-21 09:12:24 -07:00
Harrison Healey
474d6a0ff2 MM-14802 Add autocorrect when editing a post (#2810) 2019-05-21 08:51:42 -07:00
Elias Nahum
8676d063f2 MM-9494 & MM-13888 Tapping execute actions & interactive keyboard dismissal (#2799)
* MM-9494 & MM-13888 Tapping with the keyboard opened executes the action & iOS iteractive keyboard

* Fix tests

* feedback review

* add new line at the end of file

* feedback review and added todo list

* Track interactive dismiss keyboard and set scrollview bounds natively

* Fix snapshots

* Fastlane default to current branch when no BRANCH_TO_BUILD is set

* Set NODE_OPTIONS in ios build script

* Rebind scrollview when channel gets first set of posts

* Keep scrolling momentum on keyboard close

* Update react-native-keyboard-tracking-view

* Fix ScrollView offset with keyboard-tracking

* Fix offset while dragging the keyboard

* Allow action on channel drawer on tablets

* Fix typo

Co-Authored-By: Saturnino Abril <saturnino.abril@gmail.com>

* Fix indentation
2019-05-20 12:04:18 -04:00
116 changed files with 3897 additions and 1949 deletions

View File

@@ -76,9 +76,12 @@ post-install:
@# Need to copy custom RNDocumentPicker.m that implements direct access to the document picker in iOS
@cp ./native_modules/RNDocumentPicker.m node_modules/react-native-document-picker/ios/RNDocumentPicker/RNDocumentPicker.m
# Need to copy custom RNCookieManagerIOS.m that fixes a crash when cookies does not have expiration date set
@# Need to copy custom RNCookieManagerIOS.m that fixes a crash when cookies does not have expiration date set
@cp ./native_modules/RNCookieManagerIOS.m node_modules/react-native-cookies/ios/RNCookieManagerIOS/RNCookieManagerIOS.m
@# Need to copy custom RNCNetInfo.m that checks for internet connectivity instead of reaching a host by default
@cp ./native_modules/RNCNetInfo.m node_modules/@react-native-community/netinfo/ios/RNCNetInfo.m
@rm -f node_modules/intl/.babelrc
@# Hack to get react-intl and its dependencies to work with react-native
@# Based off of https://github.com/este/este/blob/master/gulp/native-fix.js
@@ -199,7 +202,7 @@ unsigned-ios: stop pre-build check-style ## Build an unsigned version of the iOS
@cd ios/ && xcodebuild -workspace Mattermost.xcworkspace/ -scheme Mattermost -sdk iphoneos -configuration Release -parallelizeTargets -resultBundlePath ../build-ios/result -derivedDataPath ../build-ios/ CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO
@cd build-ios/ && mkdir -p Payload && cp -R Build/Products/Release-iphoneos/Mattermost.app Payload/ && zip -r Mattermost-unsigned.ipa Payload/
@mv build-ios/Mattermost-unsigned.ipa .
@cd fastlane && bundle exec fastlane upload_file_to_s3 filename:Mattermost-unsigned.ipa os_type:iOS
@cd fastlane && bundle exec fastlane upload_file_to_s3 file:Mattermost-unsigned.ipa os_type:iOS
@rm -rf build-ios/
$(call stop_packager)
@@ -212,7 +215,7 @@ ios-sim-x86_64: stop pre-build check-style ## Build an unsigned x86_64 version o
@cd build-ios/Build/Products/Release-iphonesimulator/ && zip -r Mattermost-simulator-x86_64.app.zip Mattermost.app/
@mv build-ios/Build/Products/Release-iphonesimulator/Mattermost-simulator-x86_64.app.zip .
@rm -rf build-ios/
@cd fastlane && bundle exec fastlane upload_file_to_s3 filename:Mattermost-simulator-x86_64.app.zip os_type:iOS
@cd fastlane && bundle exec fastlane upload_file_to_s3 file:Mattermost-simulator-x86_64.app.zip os_type:iOS
$(call stop_packager)
unsigned-android: stop pre-build check-style prepare-android-build ## Build an unsigned version of the Android app
@@ -220,7 +223,7 @@ unsigned-android: stop pre-build check-style prepare-android-build ## Build an u
@echo "Building unsigned Android app"
@cd fastlane && NODE_ENV=production bundle exec fastlane android unsigned
@mv android/app/build/outputs/apk/unsigned/app-unsigned-unsigned.apk ./Mattermost-unsigned.apk
@cd fastlane && bundle exec fastlane upload_file_to_s3 filename:Mattermost-unsigned.apk os_type:Android
@cd fastlane && bundle exec fastlane upload_file_to_s3 file:Mattermost-unsigned.apk os_type:Android
$(call stop_packager)
test: | pre-run check-style ## Runs tests

View File

@@ -43,6 +43,76 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
---
## @react-native-community/async-storage
This product contains 'async-storage' by Krzysztof Borowy.
Asynchronous, persistent, key-value storage system for React Native.
* HOMEPAGE:
* https://github.com/react-native-community/react-native-async-storage#readme
* LICENSE: MIT
MIT License
Copyright (c) 2015-present, Facebook, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
---
## @react-native-community/netinfo
This product contains 'netinfo' by Matt Oakes.
React Native Network Info API for iOS & Android
* HOMEPAGE:
* https://github.com/react-native-community/react-native-netinfo#readme
* LICENSE: MIT
MIT License
Copyright (c) 2015-present, Facebook, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
---
## analytics-react-native
This product contains 'analytics-react-native' by Javier Alvarez.
@@ -1595,6 +1665,41 @@ SOFTWARE.
---
## react-native-keyboard-tracking-view
This product contains a modified version of 'react-native-keyboard-tracking-view' by Artal Druk.
React Native UI component which tracks the keyboard
* HOMEPAGE:
* https://github.com/wix/react-native-keyboard-tracking-view
* LICENSE: MIT
The MIT License (MIT)
Copyright (c) 2016 Wix.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
---
## react-native-keychain
This product contains 'react-native-keychain' by Joel Arvidsson.
@@ -1686,7 +1791,7 @@ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH RE
## react-native-navigation
This product contains a modified version of 'react-native-navigation' by Daniel Zlotin.
This product contains a modified version of 'react-native-navigation' by Wix.com.
React Native Navigation - truly native navigation for iOS and Android
@@ -1757,7 +1862,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
This product contains 'react-native-passcode-status' by Mark Vayngrib.
check passcode status on device
check if device-level passcode is supported/enabled/disabled
* HOMEPAGE:
* https://github.com/tradle/react-native-passcode-status
@@ -2087,7 +2192,7 @@ SOFTWARE.
## react-native-webview
This product contains 'react-native-webview' by Jamon Holmgren.
This product contains a modified version of 'react-native-webview' by Jamon Holmgren.
React Native WebView component for iOS, Android, and Windows 10 (coming soon)
@@ -2486,7 +2591,7 @@ Display some placeholder stuff before rendering your text or media content in Re
* LICENSE: MIT
Copyright (c) 2004-2018 Marvin Frachet
Copyright (c) 2004-Today Marvin Frachet
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the

View File

@@ -114,16 +114,16 @@ android {
}
packagingOptions {
pickFirst 'lib/x86_64/libjsc.so'
pickFirst 'lib/arm64-v8a/libjsc.so'
pickFirst '**/libjsc.so'
pickFirst '**/libc++_shared.so'
}
defaultConfig {
applicationId "com.mattermost.rnbeta"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 193
versionName "1.19.0"
versionCode 204
versionName "1.20.2"
multiDexEnabled = true
ndk {
abiFilters 'armeabi-v7a','arm64-v8a','x86','x86_64'
@@ -193,9 +193,6 @@ repositories {
configurations.all {
resolutionStrategy {
eachDependency { DependencyResolveDetails details ->
if (details.requested.name == 'android-jsc') {
details.useTarget group: details.requested.group, name: 'android-jsc-intl', version: 'r241213'
}
if (details.requested.name == 'play-services-base') {
details.useTarget group: details.requested.group, name: details.requested.name, version: '15.0.1'
}
@@ -213,6 +210,9 @@ configurations.all {
}
dependencies {
// Make sure to put android-jsc at the top
implementation "org.webkit:android-jsc:r241213"
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
implementation 'com.android.support:design:28.0.0'

View File

@@ -26,7 +26,7 @@
],
"services": {
"analytics_service": {
"status": 1
"status": 2
},
"appinvite_service": {
"status": 1,
@@ -57,7 +57,7 @@
],
"services": {
"analytics_service": {
"status": 1
"status": 2
},
"appinvite_service": {
"status": 1,
@@ -88,7 +88,7 @@
],
"services": {
"analytics_service": {
"status": 1
"status": 2
},
"appinvite_service": {
"status": 1,
@@ -101,4 +101,4 @@
}
],
"configuration_version": "1"
}
}

View File

@@ -19,6 +19,7 @@
android:installLocation="auto"
android:networkSecurityConfig="@xml/network_security_config"
>
<meta-data android:name="firebase_analytics_collection_deactivated" android:value="true" />
<meta-data android:name="android.content.APP_RESTRICTIONS"
android:resource="@xml/app_restrictions" />
@@ -43,7 +44,8 @@
android:exported="false" />
<activity
android:name="com.reactnativenavigation.controllers.NavigationActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"/>
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
android:resizeableActivity="true"/>
<activity
android:name="com.mattermost.share.ShareActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"

View File

@@ -4,6 +4,8 @@ import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Person;
import android.app.Person.Builder;
import android.app.RemoteInput;
import android.content.Intent;
import android.content.Context;
@@ -122,6 +124,7 @@ public class CustomPushNotification extends PushNotification {
String senderName = getSenderName(data.getString("sender_name"), data.getString("channel_name"), data.getString("message"));
data.putLong("time", new Date().getTime());
data.putString("sender_name", senderName);
data.putString("sender_id", data.getString("sender_id"));
}
list.add(0, data);
channelIdToNotification.put(channelId, list);
@@ -184,9 +187,17 @@ public class CustomPushNotification extends PushNotification {
}
Bundle bundle = mNotificationProps.asBundle();
String version = bundle.getString("version");
String channelId = bundle.getString("channel_id");
String channelName = bundle.getString("channel_name");
String senderName = bundle.getString("sender_name");
String version = bundle.getString("version");
String senderId = bundle.getString("sender_id");
String postId = bundle.getString("post_id");
String badge = bundle.getString("badge");
String smallIcon = bundle.getString("smallIcon");
String largeIcon = bundle.getString("largeIcon");
int notificationId = channelId != null ? channelId.hashCode() : MESSAGE_NOTIFICATION_ID;
String title = null;
if (version != null && version.equals("v2")) {
@@ -200,13 +211,6 @@ public class CustomPushNotification extends PushNotification {
title = mContext.getPackageManager().getApplicationLabel(appInfo).toString();
}
String channelId = bundle.getString("channel_id");
String postId = bundle.getString("post_id");
String badge = bundle.getString("badge");
String smallIcon = bundle.getString("smallIcon");
String largeIcon = bundle.getString("largeIcon");
int notificationId = channelId != null ? channelId.hashCode() : MESSAGE_NOTIFICATION_ID;
Bundle b = bundle.getBundle("userInfo");
if (b == null) {
b = new Bundle();
@@ -244,7 +248,26 @@ public class CustomPushNotification extends PushNotification {
ApplicationBadgeHelper.instance.setApplicationIconBadgeNumber(mContext.getApplicationContext(), CustomPushNotification.badgeCount);
}
Notification.MessagingStyle messagingStyle = new Notification.MessagingStyle("You");
if (android.text.TextUtils.isEmpty(senderName)) {
senderName = getSenderName(senderName, channelName, bundle.getString("message"));
}
String personId = senderId;
if (!android.text.TextUtils.isEmpty(channelName)) {
personId = channelId;
}
Notification.MessagingStyle messagingStyle;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
messagingStyle = new Notification.MessagingStyle("");
} else {
Person sender = new Person.Builder()
.setKey(senderId)
.setName("")
.build();
messagingStyle = new Notification.MessagingStyle(sender);
}
if (title != null && (!title.startsWith("@") || channelName != senderName)) {
messagingStyle
.setConversationTitle(title);
@@ -263,10 +286,22 @@ public class CustomPushNotification extends PushNotification {
for (int i = listCount; i >= 0; i--) {
Bundle data = list.get(i);
String message = data.getString("message");
if (title == null) {
message = removeSenderFromMessage(senderName, channelName, message); // generic message
String previousPersonName = getSenderName(data.getString("sender_name"), channelName, message);
String previousPersonId = data.getString("sender_id");
if (title == null || !android.text.TextUtils.isEmpty(previousPersonName)) {
message = removeSenderFromMessage(previousPersonName, channelName, message);
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
messagingStyle.addMessage(message, data.getLong("time"), previousPersonName);
} else {
Person sender = new Person.Builder()
.setKey(previousPersonId)
.setName(previousPersonName)
.build();
messagingStyle.addMessage(message, data.getLong("time"), sender);
}
messagingStyle.addMessage(message, data.getLong("time"), "");
}
notification
@@ -382,8 +417,8 @@ public class CustomPushNotification extends PushNotification {
}
private String removeSenderFromMessage(String senderName, String channelName, String message) {
String sender = String.format("%s: ", getSenderName(senderName, channelName, message));
return message.replaceFirst(sender, "");
String sender = String.format("%s", getSenderName(senderName, channelName, message));
return message.replaceFirst(sender, "").replaceFirst(": ", "").trim();
}
private void notificationReceiptDelivery(String ackId, String type) {

View File

@@ -1,7 +1,9 @@
package com.mattermost.rnbeta;
import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import com.facebook.react.bridge.Arguments;
@@ -11,6 +13,7 @@ import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.WritableMap;
public class MattermostManagedModule extends ReactContextBaseJavaModule {
private static MattermostManagedModule instance;
@@ -62,4 +65,31 @@ public class MattermostManagedModule extends ReactContextBaseJavaModule {
promise.resolve(Arguments.createMap());
}
}
@ReactMethod
// Close the current activity and open the security settings.
public void goToSecuritySettings() {
getReactApplicationContext().startActivity(new Intent(android.provider.Settings.ACTION_SECURITY_SETTINGS));
getCurrentActivity().finish();
System.exit(0);
}
@ReactMethod
public void isRunningInSplitView(final Promise promise) {
WritableMap result = Arguments.createMap();
Activity current = getCurrentActivity();
if (current != null) {
result.putBoolean("isSplitView", current.isInMultiWindowMode());
} else {
result.putBoolean("isSplitView", false);
}
promise.resolve(result);
}
@ReactMethod
public void quitApp() {
getCurrentActivity().finish();
System.exit(0);
}
}

View File

@@ -394,6 +394,7 @@ export function handleSelectChannel(channelId, fromPushNotification = false) {
{
type: ViewTypes.SELECT_CHANNEL_WITH_MEMBER,
data: channelId,
channel,
member,
},
]));
@@ -411,7 +412,8 @@ export function handleSelectChannelByName(channelName, teamName) {
return async (dispatch, getState) => {
const state = getState();
const {teams: currentTeams, currentTeamId} = state.entities.teams;
const currentTeamName = currentTeams[currentTeamId]?.name;
const currentTeam = currentTeams[currentTeamId];
const currentTeamName = currentTeam?.name;
const {data: channel} = await dispatch(getChannelByNameAndTeamName(teamName || currentTeamName, channelName));
const currentChannelId = getCurrentChannelId(state);
if (channel && currentChannelId !== channel.id) {

View File

@@ -31,6 +31,7 @@ export default class App {
// Usage: app.js
this.shouldRelaunchWhenActive = false;
this.inBackgroundSince = null;
this.previousAppState = null;
// Usage: screen/entry.js
this.startAppFromPushNotification = false;
@@ -197,10 +198,11 @@ export default class App {
const username = `${deviceToken}, ${currentUserId}`;
const password = `${token},${url}`;
this.token = token;
this.url = url;
if (this.waitForRehydration) {
this.waitForRehydration = false;
this.token = token;
this.url = url;
}
// Only save to keychain if the url and token are set

View File

@@ -9,6 +9,8 @@ import {
View,
} from 'react-native';
import EventEmitter from 'mattermost-redux/utils/event_emitter';
import {DeviceTypes} from 'app/constants';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
@@ -20,7 +22,7 @@ import DateSuggestion from './date_suggestion';
export default class Autocomplete extends PureComponent {
static propTypes = {
cursorPosition: PropTypes.number.isRequired,
cursorPosition: PropTypes.number,
deviceHeight: PropTypes.number,
onChangeText: PropTypes.func.isRequired,
maxHeight: PropTypes.number,
@@ -29,6 +31,8 @@ export default class Autocomplete extends PureComponent {
theme: PropTypes.object.isRequired,
value: PropTypes.string,
enableDateSuggestion: PropTypes.bool.isRequired,
valueEvent: PropTypes.string,
cursorPositionEvent: PropTypes.string,
};
static defaultProps = {
@@ -37,18 +41,67 @@ export default class Autocomplete extends PureComponent {
enableDateSuggestion: false,
};
state = {
atMentionCount: 0,
channelMentionCount: 0,
emojiCount: 0,
commandCount: 0,
dateCount: 0,
keyboardOffset: 0,
};
static getDerivedStateFromProps(props, state) {
const nextState = {};
let updated = false;
if (props.cursorPosition !== state.cursorPosition && !props.cursorPositionEvent) {
nextState.cursorPosition = props.cursorPosition;
updated = true;
}
if (props.value !== state.value && !props.valueEvent) {
nextState.value = props.value;
updated = true;
}
return updated ? nextState : null;
}
constructor(props) {
super(props);
this.state = {
atMentionCount: 0,
channelMentionCount: 0,
cursorPosition: props.cursorPosition,
emojiCount: 0,
commandCount: 0,
dateCount: 0,
keyboardOffset: 0,
value: props.value,
};
}
componentDidMount() {
this.keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', this.keyboardDidShow);
this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', this.keyboardDidHide);
if (this.props.valueEvent) {
EventEmitter.on(this.props.valueEvent, this.handleValueChange);
}
if (this.props.cursorPositionEvent) {
EventEmitter.on(this.props.cursorPositionEvent, this.handleCursorPositionChange);
}
}
componentWillUnmount() {
this.keyboardDidShowListener.remove();
this.keyboardDidHideListener.remove();
if (this.props.valueEvent) {
EventEmitter.off(this.props.valueEvent, this.handleValueChange);
}
if (this.props.cursorPositionEvent) {
EventEmitter.off(this.props.cursorPositionEvent, this.handleCursorPositionChange);
}
}
onChangeText = (value) => {
this.props.onChangeText(value, true);
}
};
handleAtMentionCountChange = (atMentionCount) => {
this.setState({atMentionCount});
@@ -58,6 +111,10 @@ export default class Autocomplete extends PureComponent {
this.setState({channelMentionCount});
};
handleCursorPositionChange = (cursorPosition) => {
this.setState({cursorPosition});
};
handleEmojiCountChange = (emojiCount) => {
this.setState({emojiCount});
};
@@ -70,15 +127,9 @@ export default class Autocomplete extends PureComponent {
this.setState({dateCount});
};
componentWillMount() {
this.keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', this.keyboardDidShow);
this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', this.keyboardDidHide);
}
componentWillUnmount() {
this.keyboardDidShowListener.remove();
this.keyboardDidHideListener.remove();
}
handleValueChange = (value) => {
this.setState({value});
};
keyboardDidShow = (e) => {
const {height} = e.endCoordinates;
@@ -119,7 +170,7 @@ export default class Autocomplete extends PureComponent {
}
// We always need to render something, but we only draw the borders when we have results to show
const {atMentionCount, channelMentionCount, emojiCount, commandCount, dateCount} = this.state;
const {atMentionCount, channelMentionCount, emojiCount, commandCount, dateCount, cursorPosition, value} = this.state;
if (atMentionCount + channelMentionCount + emojiCount + commandCount + dateCount > 0) {
if (this.props.isSearch) {
wrapperStyle.push(style.bordersSearch);
@@ -134,34 +185,43 @@ export default class Autocomplete extends PureComponent {
<View style={wrapperStyle}>
<View style={containerStyle}>
<AtMention
maxListHeight={maxListHeight}
onResultCountChange={this.handleAtMentionCountChange}
{...this.props}
cursorPosition={cursorPosition}
maxListHeight={maxListHeight}
onChangeText={this.onChangeText}
onResultCountChange={this.handleAtMentionCountChange}
value={value || ''}
/>
<ChannelMention
maxListHeight={maxListHeight}
onResultCountChange={this.handleChannelMentionCountChange}
{...this.props}
cursorPosition={cursorPosition}
maxListHeight={maxListHeight}
onChangeText={this.onChangeText}
onResultCountChange={this.handleChannelMentionCountChange}
value={value || ''}
/>
<EmojiSuggestion
maxListHeight={maxListHeight}
onResultCountChange={this.handleEmojiCountChange}
{...this.props}
cursorPosition={cursorPosition}
maxListHeight={maxListHeight}
onChangeText={this.onChangeText}
onResultCountChange={this.handleEmojiCountChange}
value={value || ''}
/>
<SlashSuggestion
maxListHeight={maxListHeight}
onResultCountChange={this.handleCommandCountChange}
{...this.props}
maxListHeight={maxListHeight}
onChangeText={this.onChangeText}
onResultCountChange={this.handleCommandCountChange}
value={value || ''}
/>
{(this.props.isSearch && this.props.enableDateSuggestion) &&
<DateSuggestion
onResultCountChange={this.handleIsDateFilterChange}
{...this.props}
cursorPosition={cursorPosition}
onChangeText={this.onChangeText}
onResultCountChange={this.handleIsDateFilterChange}
value={value || ''}
/>
}
</View>

View File

@@ -17,4 +17,6 @@ function mapStateToProps(state) {
};
}
export const AUTOCOMPLETE_MAX_HEIGHT = 200;
export default connect(mapStateToProps, null, null, {forwardRef: true})(Autocomplete);

View File

@@ -0,0 +1,383 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ChannelLoader should match snapshot 1`] = `
<View
onLayout={[Function]}
style={
Array [
Object {
"flex": 1,
},
undefined,
Object {
"backgroundColor": "#ffffff",
},
]
}
>
<View
style={
Array [
Object {
"backgroundColor": "#ffffff",
"flex": 1,
"flexDirection": "row",
"marginVertical": 10,
"paddingLeft": 12,
"paddingRight": 20,
},
Object {
"backgroundColor": "#ffffff",
},
]
}
>
<ImageContent
animate="fade"
color="rgba(61,60,64,0.2)"
firstLineWidth="80%"
hasRadius={true}
lineNumber={3}
lineSpacing={5}
size={32}
textSize={14}
/>
</View>
<View
style={
Array [
Object {
"backgroundColor": "#ffffff",
"flex": 1,
"flexDirection": "row",
"marginVertical": 10,
"paddingLeft": 12,
"paddingRight": 20,
},
Object {
"backgroundColor": "#ffffff",
},
]
}
>
<ImageContent
animate="fade"
color="rgba(61,60,64,0.2)"
firstLineWidth="80%"
hasRadius={true}
lineNumber={3}
lineSpacing={5}
size={32}
textSize={14}
/>
</View>
<View
style={
Array [
Object {
"backgroundColor": "#ffffff",
"flex": 1,
"flexDirection": "row",
"marginVertical": 10,
"paddingLeft": 12,
"paddingRight": 20,
},
Object {
"backgroundColor": "#ffffff",
},
]
}
>
<ImageContent
animate="fade"
color="rgba(61,60,64,0.2)"
firstLineWidth="80%"
hasRadius={true}
lineNumber={3}
lineSpacing={5}
size={32}
textSize={14}
/>
</View>
<View
style={
Array [
Object {
"backgroundColor": "#ffffff",
"flex": 1,
"flexDirection": "row",
"marginVertical": 10,
"paddingLeft": 12,
"paddingRight": 20,
},
Object {
"backgroundColor": "#ffffff",
},
]
}
>
<ImageContent
animate="fade"
color="rgba(61,60,64,0.2)"
firstLineWidth="80%"
hasRadius={true}
lineNumber={3}
lineSpacing={5}
size={32}
textSize={14}
/>
</View>
<View
style={
Array [
Object {
"backgroundColor": "#ffffff",
"flex": 1,
"flexDirection": "row",
"marginVertical": 10,
"paddingLeft": 12,
"paddingRight": 20,
},
Object {
"backgroundColor": "#ffffff",
},
]
}
>
<ImageContent
animate="fade"
color="rgba(61,60,64,0.2)"
firstLineWidth="80%"
hasRadius={true}
lineNumber={3}
lineSpacing={5}
size={32}
textSize={14}
/>
</View>
<View
style={
Array [
Object {
"backgroundColor": "#ffffff",
"flex": 1,
"flexDirection": "row",
"marginVertical": 10,
"paddingLeft": 12,
"paddingRight": 20,
},
Object {
"backgroundColor": "#ffffff",
},
]
}
>
<ImageContent
animate="fade"
color="rgba(61,60,64,0.2)"
firstLineWidth="80%"
hasRadius={true}
lineNumber={3}
lineSpacing={5}
size={32}
textSize={14}
/>
</View>
<View
style={
Array [
Object {
"backgroundColor": "#ffffff",
"flex": 1,
"flexDirection": "row",
"marginVertical": 10,
"paddingLeft": 12,
"paddingRight": 20,
},
Object {
"backgroundColor": "#ffffff",
},
]
}
>
<ImageContent
animate="fade"
color="rgba(61,60,64,0.2)"
firstLineWidth="80%"
hasRadius={true}
lineNumber={3}
lineSpacing={5}
size={32}
textSize={14}
/>
</View>
<View
style={
Array [
Object {
"backgroundColor": "#ffffff",
"flex": 1,
"flexDirection": "row",
"marginVertical": 10,
"paddingLeft": 12,
"paddingRight": 20,
},
Object {
"backgroundColor": "#ffffff",
},
]
}
>
<ImageContent
animate="fade"
color="rgba(61,60,64,0.2)"
firstLineWidth="80%"
hasRadius={true}
lineNumber={3}
lineSpacing={5}
size={32}
textSize={14}
/>
</View>
<View
style={
Array [
Object {
"backgroundColor": "#ffffff",
"flex": 1,
"flexDirection": "row",
"marginVertical": 10,
"paddingLeft": 12,
"paddingRight": 20,
},
Object {
"backgroundColor": "#ffffff",
},
]
}
>
<ImageContent
animate="fade"
color="rgba(61,60,64,0.2)"
firstLineWidth="80%"
hasRadius={true}
lineNumber={3}
lineSpacing={5}
size={32}
textSize={14}
/>
</View>
<View
style={
Array [
Object {
"backgroundColor": "#ffffff",
"flex": 1,
"flexDirection": "row",
"marginVertical": 10,
"paddingLeft": 12,
"paddingRight": 20,
},
Object {
"backgroundColor": "#ffffff",
},
]
}
>
<ImageContent
animate="fade"
color="rgba(61,60,64,0.2)"
firstLineWidth="80%"
hasRadius={true}
lineNumber={3}
lineSpacing={5}
size={32}
textSize={14}
/>
</View>
<View
style={
Array [
Object {
"backgroundColor": "#ffffff",
"flex": 1,
"flexDirection": "row",
"marginVertical": 10,
"paddingLeft": 12,
"paddingRight": 20,
},
Object {
"backgroundColor": "#ffffff",
},
]
}
>
<ImageContent
animate="fade"
color="rgba(61,60,64,0.2)"
firstLineWidth="80%"
hasRadius={true}
lineNumber={3}
lineSpacing={5}
size={32}
textSize={14}
/>
</View>
<View
style={
Array [
Object {
"backgroundColor": "#ffffff",
"flex": 1,
"flexDirection": "row",
"marginVertical": 10,
"paddingLeft": 12,
"paddingRight": 20,
},
Object {
"backgroundColor": "#ffffff",
},
]
}
>
<ImageContent
animate="fade"
color="rgba(61,60,64,0.2)"
firstLineWidth="80%"
hasRadius={true}
lineNumber={3}
lineSpacing={5}
size={32}
textSize={14}
/>
</View>
<View
style={
Array [
Object {
"backgroundColor": "#ffffff",
"flex": 1,
"flexDirection": "row",
"marginVertical": 10,
"paddingLeft": 12,
"paddingRight": 20,
},
Object {
"backgroundColor": "#ffffff",
},
]
}
>
<ImageContent
animate="fade"
color="rgba(61,60,64,0.2)"
firstLineWidth="80%"
hasRadius={true}
lineNumber={3}
lineSpacing={5}
size={32}
textSize={14}
/>
</View>
</View>
`;

View File

@@ -4,17 +4,20 @@
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {
Platform,
View,
Dimensions,
} from 'react-native';
import {ImageContent} from 'rn-placeholder';
import EventEmitter from 'mattermost-redux/utils/event_emitter';
import {DeviceTypes} from 'app/constants';
import CustomPropTypes from 'app/constants/custom_prop_types';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
function calculateMaxRows(height) {
return Math.round(height / 100);
}
export default class ChannelLoader extends PureComponent {
static propTypes = {
actions: PropTypes.shape({
@@ -23,28 +26,35 @@ export default class ChannelLoader extends PureComponent {
}).isRequired,
backgroundColor: PropTypes.string,
channelIsLoading: PropTypes.bool.isRequired,
maxRows: PropTypes.number,
style: CustomPropTypes.Style,
theme: PropTypes.object.isRequired,
height: PropTypes.number,
};
static defaultProps = {
maxRows: 6,
};
constructor(props) {
super(props);
state = {
switch: false,
};
const height = props.height || Dimensions.get('window').height;
const maxRows = calculateMaxRows(height);
this.state = {
switch: false,
maxRows,
};
}
static getDerivedStateFromProps(nextProps, prevState) {
const state = {};
if (nextProps.height) {
state.maxRows = calculateMaxRows(nextProps.height);
}
if (!nextProps.channelIsLoading && prevState.switch) {
return {
switch: false,
channel: null,
};
state.switch = false;
state.channel = null;
}
return null;
return Object.keys(state) ? state : null;
}
componentDidMount() {
@@ -99,10 +109,15 @@ export default class ChannelLoader extends PureComponent {
}
};
handleLayout = (e) => {
const {height} = e.nativeEvent.layout;
const maxRows = calculateMaxRows(height);
this.setState({maxRows});
}
render() {
const {
channelIsLoading,
maxRows,
style: styleProp,
theme,
} = this.props;
@@ -113,15 +128,13 @@ export default class ChannelLoader extends PureComponent {
const style = getStyleSheet(theme);
const bg = this.props.backgroundColor || theme.centerChannelBg;
const containerStyle = [style.container];
if (DeviceTypes.IS_TABLET) {
containerStyle.push(style.tablet);
}
return (
<View style={[containerStyle, styleProp, {backgroundColor: bg}]}>
{Array(maxRows).fill().map((item, index) => this.buildSections({
<View
style={[style.container, styleProp, {backgroundColor: bg}]}
onLayout={this.handleLayout}
>
{Array(this.state.maxRows).fill().map((item, index) => this.buildSections({
key: index,
style,
bg,
@@ -137,16 +150,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
container: {
flex: 1,
},
tablet: {
...Platform.select({
android: {
paddingTop: 25,
},
ios: {
paddingTop: 30,
},
}),
},
section: {
backgroundColor: theme.centerChannelBg,
flexDirection: 'row',

View File

@@ -0,0 +1,29 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {shallow} from 'enzyme';
import Preferences from 'mattermost-redux/constants/preferences';
import ChannelLoader from './channel_loader';
jest.mock('rn-placeholder', () => ({
ImageContent: () => {},
}));
describe('ChannelLoader', () => {
const baseProps = {
channelIsLoading: true,
theme: Preferences.THEMES.default,
actions: {
handleSelectChannel: jest.fn(),
setChannelLoading: jest.fn(),
},
};
test('should match snapshot', () => {
const wrapper = shallow(<ChannelLoader {...baseProps}/>);
expect(wrapper.getElement()).toMatchSnapshot();
});
});

View File

@@ -24,7 +24,7 @@ exports[`CustomList should match snapshot with FlatList 1`] = `
horizontal={false}
initialNumToRender={15}
keyExtractor={[Function]}
keyboardDismissMode="interactive"
keyboardDismissMode="on-drag"
keyboardShouldPersistTaps="always"
maxToRenderPerBatch={16}
numColumns={1}
@@ -62,7 +62,7 @@ exports[`CustomList should match snapshot with SectionList 1`] = `
horizontal={false}
initialNumToRender={15}
keyExtractor={[Function]}
keyboardDismissMode="interactive"
keyboardDismissMode="on-drag"
keyboardShouldPersistTaps="always"
maxToRenderPerBatch={16}
onEndReachedThreshold={2}

View File

@@ -3,7 +3,7 @@
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {FlatList, Platform, SectionList, Text, View} from 'react-native';
import {FlatList, Keyboard, Platform, SectionList, Text, View} from 'react-native';
import {ListTypes} from 'app/constants';
import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme';
@@ -26,10 +26,7 @@ export default class CustomList extends PureComponent {
onLoadMore: PropTypes.func,
onRowPress: PropTypes.func,
onRowSelect: PropTypes.func,
renderItem: PropTypes.oneOfType([
PropTypes.func,
PropTypes.element,
]).isRequired,
renderItem: PropTypes.func.isRequired,
selectable: PropTypes.bool,
theme: PropTypes.object.isRequired,
shouldRenderSeparator: PropTypes.bool,
@@ -45,6 +42,14 @@ export default class CustomList extends PureComponent {
super(props);
this.contentOffsetY = 0;
this.keyboardDismissProp = Platform.select({
android: {
onScrollBeginDrag: Keyboard.dismiss,
},
ios: {
keyboardDismissMode: 'on-drag',
},
});
this.state = {};
}
@@ -98,12 +103,6 @@ export default class CustomList extends PureComponent {
props.onPress = onRowPress;
}
// Allow passing in a component like UserListRow or ChannelListRow
if (this.props.renderItem.prototype.isReactComponent) {
const RowComponent = this.props.renderItem;
return <RowComponent {...props}/>;
}
return this.props.renderItem(props);
};
@@ -117,7 +116,7 @@ export default class CustomList extends PureComponent {
data={data}
extraData={extraData}
keyboardShouldPersistTaps='always'
keyboardDismissMode='interactive'
{...this.keyboardDismissProp}
keyExtractor={this.keyExtractor}
initialNumToRender={INITIAL_BATCH_TO_RENDER}
ItemSeparatorComponent={this.renderSeparator}
@@ -169,7 +168,7 @@ export default class CustomList extends PureComponent {
contentContainerStyle={style.container}
extraData={loading}
keyboardShouldPersistTaps='always'
keyboardDismissMode='interactive'
{...this.keyboardDismissProp}
keyExtractor={this.keyExtractor}
initialNumToRender={INITIAL_BATCH_TO_RENDER}
ItemSeparatorComponent={this.renderSeparator}

View File

@@ -53,42 +53,39 @@ export default class EditChannelInfo extends PureComponent {
editing: false,
};
constructor(props) {
super(props);
this.nameInput = React.createRef();
this.urlInput = React.createRef();
this.purposeInput = React.createRef();
this.headerInput = React.createRef();
this.lastText = React.createRef();
this.scroll = React.createRef();
}
blur = () => {
if (this.nameInput) {
this.nameInput.blur();
if (this.nameInput?.current) {
this.nameInput.current.blur();
}
// TODO: uncomment below once the channel URL field is added
// if (this.urlInput) {
// this.urlInput.blur();
// if (this.urlInput?.current) {
// this.urlInput.current.blur();
// }
if (this.purposeInput) {
this.purposeInput.blur();
if (this.purposeInput?.current) {
this.purposeInput.current.blur();
}
if (this.headerInput) {
this.headerInput.blur();
if (this.headerInput?.current) {
this.headerInput.current.blur();
}
if (this.scroll) {
this.scroll.scrollToPosition(0, 0, true);
if (this.scroll?.current) {
this.scroll.current.scrollToPosition(0, 0, true);
}
};
channelNameRef = (ref) => {
this.nameInput = ref;
};
channelURLRef = (ref) => {
this.urlInput = ref;
};
channelPurposeRef = (ref) => {
this.purposeInput = ref;
};
channelHeaderRef = (ref) => {
this.headerInput = ref;
};
close = (goBack = false) => {
if (goBack) {
this.props.navigator.pop({animated: true});
@@ -99,10 +96,6 @@ export default class EditChannelInfo extends PureComponent {
}
};
lastTextRef = (ref) => {
this.lastText = ref;
};
canUpdate = (displayName, channelURL, purpose, header) => {
const {
oldDisplayName,
@@ -167,13 +160,9 @@ export default class EditChannelInfo extends PureComponent {
}
};
scrollRef = (ref) => {
this.scroll = ref;
};
scrollToEnd = () => {
if (this.scroll && this.lastText) {
this.scroll.scrollToFocusedInput(findNodeHandle(this.lastText));
if (this.scroll?.current && this.lastText?.current) {
this.scroll.current.scrollToFocusedInput(findNodeHandle(this.lastText.current));
}
};
@@ -223,7 +212,7 @@ export default class EditChannelInfo extends PureComponent {
<View style={style.container}>
<StatusBar/>
<KeyboardAwareScrollView
ref={this.scrollRef}
ref={this.scroll}
style={style.container}
>
{displayError}
@@ -240,7 +229,7 @@ export default class EditChannelInfo extends PureComponent {
</View>
<View style={style.inputContainer}>
<TextInputWithLocalizedPlaceholder
ref={this.channelNameRef}
ref={this.nameInput}
value={displayName}
onChangeText={this.onDisplayNameChangeText}
style={style.input}
@@ -269,7 +258,7 @@ export default class EditChannelInfo extends PureComponent {
</View>
<View style={style.inputContainer}>
<TextInputWithLocalizedPlaceholder
ref={this.channelURLRef}
ref={this.urlInput}
value={channelURL}
onChangeText={this.onDisplayURLChangeText}
style={style.input}
@@ -299,7 +288,7 @@ export default class EditChannelInfo extends PureComponent {
</View>
<View style={style.inputContainer}>
<TextInputWithLocalizedPlaceholder
ref={this.channelPurposeRef}
ref={this.purposeInput}
value={purpose}
onChangeText={this.onPurposeChangeText}
style={[style.input, {height: 110}]}
@@ -337,7 +326,7 @@ export default class EditChannelInfo extends PureComponent {
</View>
<View style={style.inputContainer}>
<TextInputWithLocalizedPlaceholder
ref={this.channelHeaderRef}
ref={this.headerInput}
value={header}
onChangeText={this.onHeaderChangeText}
style={[style.input, {height: 110}]}
@@ -353,7 +342,7 @@ export default class EditChannelInfo extends PureComponent {
disableFullscreenUI={true}
/>
</View>
<View ref={this.lastTextRef}>
<View ref={this.lastText}>
<FormattedText
style={style.helpText}
id='channel_modal.headerHelp'

View File

@@ -65,7 +65,7 @@ export default class Emoji extends React.PureComponent {
componentWillReceiveProps(nextProps) {
const {displayTextOnly, emojiName, imageUrl} = nextProps;
if (emojiName !== this.props.emojiName) {
if (emojiName !== this.props.emojiName && this.mounted) {
this.setState({
imageUrl: null,
});
@@ -82,9 +82,11 @@ export default class Emoji extends React.PureComponent {
}
setImageUrl = (imageUrl) => {
this.setState({
imageUrl,
});
if (this.mounted) {
this.setState({
imageUrl,
});
}
};
render() {

View File

@@ -33,7 +33,8 @@ function mapStateToProps(state, ownProps) {
config.EnableCustomEmoji !== 'true' ||
config.ExperimentalEnablePostMetadata === 'true' ||
getCurrentUserId(state) === '' ||
!isMinimumServerVersion(Client4.getServerVersion(), 4, 7);
!isMinimumServerVersion(Client4.getServerVersion(), 4, 7) ||
isMinimumServerVersion(Client4.getServerVersion(), 5, 12);
}
return {

View File

@@ -5,7 +5,7 @@ import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {Animated} from 'react-native';
export const FADE_DURATION = 300;
export const FADE_DURATION = 100;
export default class Fade extends PureComponent {
static propTypes = {
@@ -28,6 +28,7 @@ export default class Fade extends PureComponent {
{
toValue: prevProps.visible ? 0 : 1,
duration: FADE_DURATION,
useNativeDriver: true,
}
).start();
}

View File

@@ -3,6 +3,7 @@
exports[`PostAttachmentOpenGraph should match snapshot with a single image file 1`] = `
<ScrollViewMock
horizontal={true}
keyboardShouldPersistTaps="always"
scrollEnabled={false}
style={
Array [

View File

@@ -4,7 +4,6 @@
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {
Keyboard,
ScrollView,
StyleSheet,
} from 'react-native';
@@ -123,7 +122,6 @@ export default class FileAttachmentList extends Component {
};
handlePreviewPress = preventDoubleTap((idx) => {
Keyboard.dismiss();
previewImageAtIndex(this.props.navigator, this.items, idx, this.galleryFiles);
});
@@ -176,6 +174,7 @@ export default class FileAttachmentList extends Component {
horizontal={true}
scrollEnabled={fileIds.length > 1}
style={[(isFailed && styles.failed)]}
keyboardShouldPersistTaps={'always'}
>
{this.renderItems()}
</ScrollView>

View File

@@ -73,15 +73,13 @@ export default class FileUploadItem extends PureComponent {
const {actions, channelId, file, rootId} = this.props;
const response = JSON.parse(res.data);
if (res.respInfo.status === 200 || res.respInfo.status === 201) {
this.setState({progress: 100}, () => {
const data = response.file_infos.map((f) => {
return {
...f,
clientId: file.clientId,
};
});
actions.uploadComplete(data, channelId, rootId);
const data = response.file_infos.map((f) => {
return {
...f,
clientId: file.clientId,
};
});
actions.uploadComplete(data, channelId, rootId);
} else {
actions.uploadFailed([file.clientId], channelId, rootId, response.message);
}

View File

@@ -10,6 +10,8 @@ import {
View,
} from 'react-native';
import EventEmitter from 'mattermost-redux/utils/event_emitter';
import FormattedText from 'app/components/formatted_text';
import FileUploadItem from './file_upload_item';
@@ -21,9 +23,7 @@ export default class FileUploadPreview extends PureComponent {
deviceHeight: PropTypes.number.isRequired,
files: PropTypes.array.isRequired,
filesUploadingForCurrentChannel: PropTypes.bool.isRequired,
fileSizeWarning: PropTypes.string,
rootId: PropTypes.string,
showFileMaxWarning: PropTypes.bool.isRequired,
theme: PropTypes.object.isRequired,
};
@@ -31,6 +31,21 @@ export default class FileUploadPreview extends PureComponent {
files: [],
};
state = {
fileSizeWarning: null,
showFileMaxWarning: false,
};
componentDidMount() {
EventEmitter.on('fileMaxWarning', this.handleFileMaxWarning);
EventEmitter.on('fileSizeWarning', this.handleFileSizeWarning);
}
componentWillUnmount() {
EventEmitter.off('fileMaxWarning', this.handleFileMaxWarning);
EventEmitter.off('fileSizeWarning', this.handleFileSizeWarning);
}
buildFilePreviews = () => {
return this.props.files.map((file) => {
return (
@@ -45,16 +60,25 @@ export default class FileUploadPreview extends PureComponent {
});
};
handleFileMaxWarning = () => {
this.setState({showFileMaxWarning: true});
setTimeout(() => {
this.setState({showFileMaxWarning: false});
}, 3000);
};
handleFileSizeWarning = (message) => {
this.setState({fileSizeWarning: message});
};
render() {
const {
showFileMaxWarning,
fileSizeWarning,
channelIsLoading,
filesUploadingForCurrentChannel,
deviceHeight,
files,
} = this.props;
const {fileSizeWarning, showFileMaxWarning} = this.state;
if (
!fileSizeWarning && !showFileMaxWarning &&
(channelIsLoading || (!files.length && !filesUploadingForCurrentChannel))
@@ -69,6 +93,7 @@ export default class FileUploadPreview extends PureComponent {
horizontal={true}
style={style.scrollView}
contentContainerStyle={style.scrollViewContent}
keyboardShouldPersistTaps={'handled'}
>
{this.buildFilePreviews()}
</ScrollView>

View File

@@ -2,20 +2,26 @@
// See LICENSE.txt for license information.
import {connect} from 'react-redux';
import {getCurrentChannelId} from 'mattermost-redux/selectors/entities/channels';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
import {getDimensions} from 'app/selectors/device';
import {checkForFileUploadingInChannel} from 'app/selectors/file';
import {getCurrentChannelDraft, getThreadDraft} from 'app/selectors/views';
import FileUploadPreview from './file_upload_preview';
function mapStateToProps(state, ownProps) {
const {deviceHeight} = getDimensions(state);
const currentDraft = ownProps.rootId ? getThreadDraft(state, ownProps.rootId) : getCurrentChannelDraft(state);
const channelId = getCurrentChannelId(state);
return {
channelId,
channelIsLoading: state.views.channel.loading,
deviceHeight,
filesUploadingForCurrentChannel: checkForFileUploadingInChannel(state, ownProps.channelId, ownProps.rootId),
files: currentDraft.files,
filesUploadingForCurrentChannel: checkForFileUploadingInChannel(state, channelId, ownProps.rootId),
theme: getTheme(state),
};
}

View File

@@ -1,6 +1,16 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {connect} from 'react-redux';
import {isLandscape} from 'app/selectors/device';
import KeyboardLayout from './keyboard_layout';
export default KeyboardLayout;
function mapStateToProps(state) {
return {
isLandscape: isLandscape(state),
};
}
export default connect(mapStateToProps, null, null, {forwardRef: true})(KeyboardLayout);

View File

@@ -114,6 +114,11 @@ export default class MarkdownImage extends React.Component {
return;
}
if (!width || !height) {
this.setState({failed: true});
return;
}
this.setState({
failed: false,
originalHeight: height,

View File

@@ -19,7 +19,6 @@ import IonIcon from 'react-native-vector-icons/Ionicons';
import FormattedText from 'app/components/formatted_text';
import {DeviceTypes, ViewTypes} from 'app/constants';
import mattermostBucket from 'app/mattermost_bucket';
import mattermostManaged from 'app/mattermost_managed';
import PushNotifications from 'app/push_notifications';
import networkConnectionListener, {checkConnection} from 'app/utils/network';
import {t} from 'app/utils/i18n';
@@ -36,7 +35,6 @@ const {
IOS_TOP_LANDSCAPE,
IOS_TOP_PORTRAIT,
IOSX_TOP_PORTRAIT,
STATUS_BAR_HEIGHT,
} = ViewTypes;
export default class NetworkIndicator extends PureComponent {
@@ -142,7 +140,7 @@ export default class NetworkIndicator extends PureComponent {
const {connection} = this.props.actions;
clearTimeout(this.connectionRetryTimeout);
NetInfo.isConnected.fetch().then(async (isConnected) => {
NetInfo.fetch().then(async ({isConnected}) => {
const {hasInternet, serverReachable} = await checkConnection(isConnected);
connection(hasInternet);
@@ -204,9 +202,7 @@ export default class NetworkIndicator extends PureComponent {
return IOS_TOP_LANDSCAPE;
} else if (isX) {
return IOSX_TOP_PORTRAIT;
} else if (isLandscape && DeviceTypes.IS_TABLET && mattermostManaged.hasSafeAreaInsets) {
return IOS_TOP_LANDSCAPE + STATUS_BAR_HEIGHT;
} else if (isLandscape) {
} else if (isLandscape && !DeviceTypes.IS_TABLET) {
return IOS_TOP_LANDSCAPE;
}
@@ -233,7 +229,6 @@ export default class NetworkIndicator extends PureComponent {
handleAppStateChange = async (appState) => {
const {actions, currentChannelId} = this.props;
const active = appState === 'active';
if (active) {
this.connect(true);

View File

@@ -4,6 +4,7 @@
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {
Keyboard,
Platform,
TouchableHighlight,
View,
@@ -104,11 +105,14 @@ export default class Post extends PureComponent {
},
};
if (Platform.OS === 'ios') {
navigator.push(options);
} else {
navigator.showModal(options);
}
Keyboard.dismiss();
requestAnimationFrame(() => {
if (Platform.OS === 'ios') {
navigator.push(options);
} else {
navigator.showModal(options);
}
});
};
autofillUserMention = (username) => {

View File

@@ -12,6 +12,9 @@ import {
View,
} from 'react-native';
import {TABLET_WIDTH} from 'app/components/sidebars/drawer_layout';
import {DeviceTypes} from 'app/constants';
import ImageCacheManager from 'app/utils/image_cache_manager';
import {previewImageAtIndex, calculateDimensions} from 'app/utils/images';
import {getNearestPoint} from 'app/utils/opengraph';
@@ -162,8 +165,9 @@ export default class PostAttachmentOpenGraph extends PureComponent {
const {deviceHeight, deviceWidth, isReplyPost} = this.props;
const deviceSize = deviceWidth > deviceHeight ? deviceHeight : deviceWidth;
const viewPortWidth = deviceSize - VIEWPORT_IMAGE_OFFSET - (isReplyPost ? VIEWPORT_IMAGE_REPLY_OFFSET : 0);
const tabletOffset = DeviceTypes.IS_TABLET ? TABLET_WIDTH : 0;
return viewPortWidth;
return viewPortWidth - tabletOffset;
};
goToLink = () => {

View File

@@ -4,6 +4,7 @@
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {
Keyboard,
ScrollView,
TouchableOpacity,
View,
@@ -115,9 +116,9 @@ export default class PostBody extends PureComponent {
const {height} = event.nativeEvent.layout;
const {showLongPost} = this.props;
if (!showLongPost && height >= this.state.maxHeight) {
if (!showLongPost) {
this.setState({
isLongPost: true,
isLongPost: height >= this.state.maxHeight,
});
}
@@ -204,7 +205,10 @@ export default class PostBody extends PureComponent {
},
};
navigator.showModal(options);
Keyboard.dismiss();
requestAnimationFrame(() => {
navigator.showModal(options);
});
};
renderAddChannelMember = (style, messageStyle, textStyles) => {
@@ -439,6 +443,7 @@ export default class PostBody extends PureComponent {
scrollEnabled={false}
showsVerticalScrollIndicator={false}
showsHorizontalScrollIndicator={false}
keyboardShouldPersistTaps={'always'}
>
{messageComponent}
</ScrollView>

View File

@@ -67,4 +67,60 @@ describe('PostBody', () => {
expect(wrapper.find(PostBodyAdditionalContent).exists()).toBeFalsy();
});
test('measurePost should update isLongPost when showLongPost is false', () => {
const event = {
nativeEvent: {
layout: {
height: null,
},
},
};
const props = {...baseProps, showLongPost: false};
const wrapper = shallowWithIntl(<PostBody {...props}/>);
const instance = wrapper.instance();
expect(wrapper.state('isLongPost')).toEqual(false);
event.nativeEvent.layout.height = wrapper.state('maxHeight');
instance.measurePost(event);
expect(wrapper.state('isLongPost')).toEqual(true);
event.nativeEvent.layout.height = wrapper.state('maxHeight') - 1;
instance.measurePost(event);
expect(wrapper.state('isLongPost')).toEqual(false);
event.nativeEvent.layout.height = wrapper.state('maxHeight') + 1;
instance.measurePost(event);
expect(wrapper.state('isLongPost')).toEqual(true);
});
test('measurePost should not update isLongPost when showLongPost is true', () => {
const event = {
nativeEvent: {
layout: {
height: null,
},
},
};
const props = {...baseProps, showLongPost: true};
const wrapper = shallowWithIntl(<PostBody {...props}/>);
const instance = wrapper.instance();
expect(wrapper.state('isLongPost')).toEqual(false);
event.nativeEvent.layout.height = wrapper.state('maxHeight');
instance.measurePost(event);
expect(wrapper.state('isLongPost')).toEqual(false);
event.nativeEvent.layout.height = wrapper.state('maxHeight') - 1;
instance.measurePost(event);
expect(wrapper.state('isLongPost')).toEqual(false);
event.nativeEvent.layout.height = wrapper.state('maxHeight') + 1;
instance.measurePost(event);
expect(wrapper.state('isLongPost')).toEqual(false);
});
});

View File

@@ -14,9 +14,11 @@ import {
import {YouTubeStandaloneAndroid, YouTubeStandaloneIOS} from 'react-native-youtube';
import {intlShape} from 'react-intl';
import {TABLET_WIDTH} from 'app/components/sidebars/drawer_layout';
import PostAttachmentImage from 'app/components/post_attachment_image';
import ProgressiveImage from 'app/components/progressive_image';
import {DeviceTypes} from 'app/constants';
import CustomPropTypes from 'app/constants/custom_prop_types';
import ImageCacheManager from 'app/utils/image_cache_manager';
import {previewImageAtIndex, calculateDimensions} from 'app/utils/images';
@@ -293,7 +295,10 @@ export default class PostBodyAdditionalContent extends PureComponent {
getViewPortWidth = (props) => {
const {deviceHeight, deviceWidth, isReplyPost} = props;
const deviceSize = deviceWidth > deviceHeight ? deviceHeight : deviceWidth;
return deviceSize - VIEWPORT_IMAGE_OFFSET - (isReplyPost ? VIEWPORT_IMAGE_REPLY_OFFSET : 0);
const viewPortWidth = deviceSize - VIEWPORT_IMAGE_OFFSET - (isReplyPost ? VIEWPORT_IMAGE_REPLY_OFFSET : 0);
const tabletOffset = DeviceTypes.IS_TABLET ? TABLET_WIDTH : 0;
return viewPortWidth - tabletOffset;
};
setImageSize = (uri, originalWidth, originalHeight) => {

View File

@@ -26,6 +26,8 @@ exports[`PostList setting channel deep link 1`] = `
initialNumToRender={15}
inverted={true}
keyExtractor={[Function]}
keyboardDismissMode="interactive"
keyboardShouldPersistTaps="handled"
maintainVisibleContentPosition={
Object {
"autoscrollToTopThreshold": 60,
@@ -74,6 +76,8 @@ exports[`PostList setting permalink deep link 1`] = `
initialNumToRender={15}
inverted={true}
keyExtractor={[Function]}
keyboardDismissMode="interactive"
keyboardShouldPersistTaps="handled"
maintainVisibleContentPosition={
Object {
"autoscrollToTopThreshold": 60,
@@ -122,6 +126,8 @@ exports[`PostList should match snapshot 1`] = `
initialNumToRender={15}
inverted={true}
keyExtractor={[Function]}
keyboardDismissMode="interactive"
keyboardShouldPersistTaps="handled"
maintainVisibleContentPosition={
Object {
"autoscrollToTopThreshold": 60,

View File

@@ -66,6 +66,7 @@ export default class PostList extends PureComponent {
siteURL: PropTypes.string.isRequired,
theme: PropTypes.object.isRequired,
location: PropTypes.string,
scrollViewNativeID: PropTypes.string,
};
static defaultProps = {
@@ -82,6 +83,7 @@ export default class PostList extends PureComponent {
this.hasDoneInitialScroll = false;
this.contentOffsetY = 0;
this.shouldScrollToBottom = false;
this.makeExtraData = makeExtraData();
this.flatListRef = React.createRef();
@@ -91,7 +93,7 @@ export default class PostList extends PureComponent {
}
componentDidMount() {
EventEmitter.on('scroll-to-bottom', this.scrollToBottom);
EventEmitter.on('scroll-to-bottom', this.handleSetScrollToBottom);
}
componentWillReceiveProps(nextProps) {
@@ -101,15 +103,22 @@ export default class PostList extends PureComponent {
}
}
componentDidUpdate() {
if (this.props.deepLinkURL) {
this.handleDeepLink(this.props.deepLinkURL);
this.props.actions.setDeepLinkURL('');
componentDidUpdate(prevProps) {
const {actions, channelId, deepLinkURL, postIds} = this.props;
if (deepLinkURL && deepLinkURL !== prevProps.deepLinkURL) {
this.handleDeepLink(deepLinkURL);
actions.setDeepLinkURL('');
}
if (this.shouldScrollToBottom && prevProps.channelId === channelId && prevProps.postIds.length === postIds.length) {
this.scrollToBottom();
this.shouldScrollToBottom = false;
}
}
componentWillUnmount() {
EventEmitter.off('scroll-to-bottom', this.scrollToBottom);
EventEmitter.off('scroll-to-bottom', this.handleSetScrollToBottom);
}
handleClosePermalink = () => {
@@ -198,6 +207,10 @@ export default class PostList extends PureComponent {
});
};
handleSetScrollToBottom = () => {
this.shouldScrollToBottom = true;
}
keyExtractor = (item) => {
// All keys are strings (either post IDs or special keys)
return item;
@@ -267,7 +280,9 @@ export default class PostList extends PureComponent {
};
scrollToBottom = () => {
this.flatListRef.current.scrollToOffset({offset: 0, animated: true});
setTimeout(() => {
this.flatListRef.current.scrollToOffset({offset: 0, animated: true});
}, 250);
};
scrollToInitialIndexIfNeeded = (width, height) => {
@@ -320,6 +335,7 @@ export default class PostList extends PureComponent {
highlightPostId,
postIds,
refreshing,
scrollViewNativeID,
} = this.props;
const refreshControl = {refreshing};
@@ -339,6 +355,8 @@ export default class PostList extends PureComponent {
extraData={this.makeExtraData(channelId, highlightPostId, this.props.extraData)}
initialNumToRender={INITIAL_BATCH_TO_RENDER}
inverted={true}
keyboardDismissMode={'interactive'}
keyboardShouldPersistTaps={'handled'}
keyExtractor={this.keyExtractor}
ListFooterComponent={this.props.renderFooter}
maintainVisibleContentPosition={SCROLL_POSITION_CONFIG}
@@ -351,6 +369,7 @@ export default class PostList extends PureComponent {
renderItem={this.renderItem}
scrollEventThrottle={60}
{...refreshControl}
nativeID={scrollViewNativeID}
/>
);
}

View File

@@ -0,0 +1,47 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import Autocomplete from 'app/components/autocomplete';
import FileUploadPreview from 'app/components/file_upload_preview';
import Typing from './components/typing';
import PostTextBoxBase from './post_textbox_base';
const AUTOCOMPLETE_MARGIN = 20;
const AUTOCOMPLETE_MAX_HEIGHT = 200;
export default class PostTextBoxAndroid extends PostTextBoxBase {
render() {
const {
deactivatedChannel,
files,
rootId,
} = this.props;
if (deactivatedChannel) {
return this.renderDeactivatedChannel();
}
const {cursorPosition, top} = this.state;
return (
<React.Fragment>
<Typing/>
<FileUploadPreview
files={files}
rootId={rootId}
/>
<Autocomplete
cursorPosition={cursorPosition}
maxHeight={Math.min(top - AUTOCOMPLETE_MARGIN, AUTOCOMPLETE_MAX_HEIGHT)}
onChangeText={this.handleTextChange}
value={this.state.value}
rootId={rootId}
/>
{this.renderTextBox()}
</React.Fragment>
);
}
}

View File

@@ -0,0 +1,24 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import Typing from './components/typing';
import PostTextBoxBase from './post_textbox_base';
export default class PostTextBoxIOS extends PostTextBoxBase {
render() {
const {deactivatedChannel} = this.props;
if (deactivatedChannel) {
return this.renderDeactivatedChannel();
}
return (
<React.Fragment>
<Typing/>
{this.renderTextBox()}
</React.Fragment>
);
}
}

View File

@@ -3,7 +3,17 @@
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {Alert, BackHandler, findNodeHandle, Keyboard, NativeModules, Platform, Text, TextInput, View} from 'react-native';
import {
Alert,
BackHandler,
findNodeHandle,
Keyboard,
NativeModules,
Platform,
Text,
TextInput,
View,
} from 'react-native';
import {intlShape} from 'react-intl';
import Button from 'react-native-button';
import {General, RequestStatus} from 'mattermost-redux/constants';
@@ -11,26 +21,20 @@ import EventEmitter from 'mattermost-redux/utils/event_emitter';
import {getFormattedFileSize} from 'mattermost-redux/utils/file_utils';
import AttachmentButton from 'app/components/attachment_button';
import Autocomplete from 'app/components/autocomplete';
import Fade from 'app/components/fade';
import FileUploadPreview from 'app/components/file_upload_preview';
import {INSERT_TO_COMMENT, INSERT_TO_DRAFT, IS_REACTION_REGEX, MAX_CONTENT_HEIGHT, MAX_FILE_COUNT} from 'app/constants/post_textbox';
import {confirmOutOfOfficeDisabled} from 'app/utils/status';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
import {t} from 'app/utils/i18n';
import FormattedMarkdownText from 'app/components/formatted_markdown_text';
import FormattedText from 'app/components/formatted_text';
import SendButton from 'app/components/send_button';
import Typing from './components/typing';
import {INSERT_TO_COMMENT, INSERT_TO_DRAFT, IS_REACTION_REGEX, MAX_CONTENT_HEIGHT, MAX_FILE_COUNT} from 'app/constants/post_textbox';
import {t} from 'app/utils/i18n';
import {confirmOutOfOfficeDisabled} from 'app/utils/status';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
const PLACEHOLDER_COLOR = changeOpacity('#000', 0.5);
const {RNTextInputReset} = NativeModules;
const AUTOCOMPLETE_MARGIN = 20;
const AUTOCOMPLETE_MAX_HEIGHT = 200;
export default class PostTextbox extends PureComponent {
export default class PostTextBoxBase extends PureComponent {
static propTypes = {
actions: PropTypes.shape({
addReactionToLatestPost: PropTypes.func.isRequired,
@@ -66,6 +70,8 @@ export default class PostTextbox extends PureComponent {
userIsOutOfOffice: PropTypes.bool.isRequired,
channelIsArchived: PropTypes.bool,
onCloseChannel: PropTypes.func,
cursorPositionEvent: PropTypes.string,
valueEvent: PropTypes.string,
};
static defaultProps = {
@@ -86,10 +92,8 @@ export default class PostTextbox extends PureComponent {
this.state = {
cursorPosition: 0,
keyboardType: 'default',
fileSizeWarning: null,
top: 0,
value: props.value,
showFileMaxWarning: false,
};
}
@@ -118,7 +122,7 @@ export default class PostTextbox extends PureComponent {
}
blur = () => {
if (this.input?.current) {
if (this.input.current) {
this.input.current.blur();
}
};
@@ -177,6 +181,60 @@ export default class PostTextbox extends PureComponent {
}
};
getAttachmentButton = () => {
const {canUploadFiles, channelIsReadOnly, files, maxFileSize, navigator, theme} = this.props;
let attachmentButton = null;
if (canUploadFiles && !channelIsReadOnly) {
attachmentButton = (
<AttachmentButton
blurTextBox={this.blur}
theme={theme}
navigator={navigator}
fileCount={files.length}
maxFileSize={maxFileSize}
maxFileCount={MAX_FILE_COUNT}
onShowFileMaxWarning={this.onShowFileMaxWarning}
onShowFileSizeWarning={this.onShowFileSizeWarning}
uploadFiles={this.handleUploadFiles}
/>
);
}
return attachmentButton;
};
getInputContainerStyle = () => {
const {canUploadFiles, channelIsReadOnly, theme} = this.props;
const style = getStyleSheet(theme);
const inputContainerStyle = [style.inputContainer];
if (!canUploadFiles) {
inputContainerStyle.push(style.inputContainerWithoutFileUpload);
}
if (channelIsReadOnly) {
inputContainerStyle.push(style.readonlyContainer);
}
return inputContainerStyle;
};
getPlaceHolder = () => {
const {channelIsReadOnly, rootId} = this.props;
let placeholder;
if (channelIsReadOnly) {
placeholder = {id: t('mobile.create_post.read_only'), defaultMessage: 'This channel is read-only.'};
} else if (rootId) {
placeholder = {id: t('create_comment.addComment'), defaultMessage: 'Add a comment...'};
} else {
placeholder = {id: t('create_post.write'), defaultMessage: 'Write to {channelDisplayName}'};
}
return placeholder;
};
handleAndroidKeyboard = () => {
this.blur();
};
@@ -198,6 +256,12 @@ export default class PostTextbox extends PureComponent {
handlePostDraftSelectionChanged = (event) => {
const cursorPosition = event.nativeEvent.selection.end;
const {cursorPositionEvent} = this.props;
if (cursorPositionEvent) {
EventEmitter.emit(cursorPositionEvent, cursorPosition);
}
this.setState({
cursorPosition,
});
@@ -269,15 +333,31 @@ export default class PostTextbox extends PureComponent {
actions,
channelId,
rootId,
cursorPositionEvent,
valueEvent,
} = this.props;
if (valueEvent) {
EventEmitter.emit(valueEvent, value);
}
const nextState = {value};
// Workaround for some Android keyboards that don't play well with cursors (e.g. Samsung keyboards)
if (autocomplete && Platform.OS === 'android' & this.input?.current) {
RNTextInputReset.resetKeyboardInput(findNodeHandle(this.input.current));
if (autocomplete && this.input?.current) {
if (Platform.OS === 'android') {
RNTextInputReset.resetKeyboardInput(findNodeHandle(this.input.current));
} else {
nextState.cursorPosition = value.length;
if (cursorPositionEvent) {
EventEmitter.emit(cursorPositionEvent, nextState.cursorPosition);
}
}
}
this.checkMessageLength(value);
this.setState({value});
this.setState(nextState);
if (value) {
actions.userTyping(channelId, rootId);
@@ -430,11 +510,7 @@ export default class PostTextbox extends PureComponent {
};
onShowFileMaxWarning = () => {
this.setState({showFileMaxWarning: true}, () => {
setTimeout(() => {
this.setState({showFileMaxWarning: false});
}, 3000);
});
EventEmitter.emit('fileMaxWarning');
};
onShowFileSizeWarning = (filename) => {
@@ -447,11 +523,10 @@ export default class PostTextbox extends PureComponent {
filename,
});
this.setState({fileSizeWarning}, () => {
setTimeout(() => {
this.setState({fileSizeWarning: null});
}, 3000);
});
EventEmitter.emit('fileSizeWarning', fileSizeWarning);
setTimeout(() => {
EventEmitter.emit('fileSizeWarning', null);
}, 3000);
};
onCloseChannelPress = () => {
@@ -463,24 +538,26 @@ export default class PostTextbox extends PureComponent {
};
archivedView = (theme, style) => {
return (<View style={style.archivedWrapper}>
<FormattedMarkdownText
id='archivedChannelMessage'
defaultMessage='You are viewing an **archived channel**. New messages cannot be posted.'
theme={theme}
style={style.archivedText}
/>
<Button
containerStyle={style.closeButton}
onPress={this.onCloseChannelPress}
>
<FormattedText
id='center_panel.archived.closeChannel'
defaultMessage='Close Channel'
style={style.closeButtonText}
return (
<View style={style.archivedWrapper}>
<FormattedMarkdownText
id='archivedChannelMessage'
defaultMessage='You are viewing an **archived channel**. New messages cannot be posted.'
theme={theme}
style={style.archivedText}
/>
</Button>
</View>);
<Button
containerStyle={style.closeButton}
onPress={this.onCloseChannelPress}
>
<FormattedText
id='center_panel.archived.closeChannel'
defaultMessage='Close Channel'
style={style.closeButtonText}
/>
</Button>
</View>
);
};
handleLayout = (e) => {
@@ -489,132 +566,67 @@ export default class PostTextbox extends PureComponent {
});
};
render() {
renderDeactivatedChannel = () => {
const {intl} = this.context;
const {
canUploadFiles,
channelId,
channelDisplayName,
channelIsLoading,
channelIsReadOnly,
deactivatedChannel,
files,
maxFileSize,
navigator,
rootId,
theme,
channelIsArchived,
} = this.props;
const style = getStyleSheet(theme);
if (deactivatedChannel) {
return (
<Text style={style.deactivatedMessage}>
{intl.formatMessage({
id: 'create_post.deactivated',
defaultMessage: 'You are viewing an archived channel with a deactivated user.',
})}
</Text>
);
}
const {
cursorPosition,
fileSizeWarning,
showFileMaxWarning,
top,
value,
} = this.state;
const textValue = channelIsLoading ? '' : value;
let placeholder;
if (channelIsReadOnly) {
placeholder = {id: t('mobile.create_post.read_only'), defaultMessage: 'This channel is read-only.'};
} else if (rootId) {
placeholder = {id: t('create_comment.addComment'), defaultMessage: 'Add a comment...'};
} else {
placeholder = {id: t('create_post.write'), defaultMessage: 'Write to {channelDisplayName}'};
}
let attachmentButton = null;
const inputContainerStyle = [style.inputContainer];
if (canUploadFiles) {
attachmentButton = (
<AttachmentButton
blurTextBox={this.blur}
theme={theme}
navigator={navigator}
fileCount={files.length}
maxFileSize={maxFileSize}
maxFileCount={MAX_FILE_COUNT}
onShowFileMaxWarning={this.onShowFileMaxWarning}
onShowFileSizeWarning={this.onShowFileSizeWarning}
uploadFiles={this.handleUploadFiles}
/>
);
} else {
inputContainerStyle.push(style.inputContainerWithoutFileUpload);
}
if (channelIsReadOnly) {
inputContainerStyle.push(style.readonlyContainer);
}
const style = getStyleSheet(this.props.theme);
return (
<React.Fragment>
<Typing/>
<FileUploadPreview
channelId={channelId}
files={files}
fileSizeWarning={fileSizeWarning}
rootId={rootId}
showFileMaxWarning={showFileMaxWarning}
/>
<Autocomplete
cursorPosition={cursorPosition}
maxHeight={Math.min(top - AUTOCOMPLETE_MARGIN, AUTOCOMPLETE_MAX_HEIGHT)}
onChangeText={this.handleTextChange}
value={this.state.value}
rootId={rootId}
/>
{!channelIsArchived && (
<View
style={style.inputWrapper}
onLayout={this.handleLayout}
>
{!channelIsReadOnly && attachmentButton}
<View style={inputContainerStyle}>
<TextInput
ref={this.input}
value={textValue}
onChangeText={this.handleTextChange}
onSelectionChange={this.handlePostDraftSelectionChanged}
placeholder={intl.formatMessage(placeholder, {channelDisplayName})}
placeholderTextColor={PLACEHOLDER_COLOR}
multiline={true}
blurOnSubmit={false}
underlineColorAndroid='transparent'
style={style.input}
keyboardType={this.state.keyboardType}
onEndEditing={this.handleEndEditing}
disableFullscreenUI={true}
editable={!channelIsReadOnly}
/>
<Fade visible={this.isSendButtonVisible()}>
<SendButton
disabled={this.isFileLoading()}
handleSendMessage={this.handleSendMessage}
theme={theme}
/>
</Fade>
</View>
</View>
)}
{channelIsArchived && this.archivedView(theme, style)}
</React.Fragment>
<Text style={style.deactivatedMessage}>
{intl.formatMessage({
id: 'create_post.deactivated',
defaultMessage: 'You are viewing an archived channel with a deactivated user.',
})}
</Text>
);
}
renderTextBox = () => {
const {intl} = this.context;
const {channelDisplayName, channelIsArchived, channelIsLoading, channelIsReadOnly, theme} = this.props;
const style = getStyleSheet(theme);
if (channelIsArchived) {
return this.archivedView(theme, style);
}
const {value} = this.state;
const textValue = channelIsLoading ? '' : value;
const placeholder = this.getPlaceHolder();
return (
<View
style={style.inputWrapper}
onLayout={this.handleLayout}
>
{this.getAttachmentButton()}
<View style={this.getInputContainerStyle()}>
<TextInput
ref={this.input}
value={textValue}
onChangeText={this.handleTextChange}
onSelectionChange={this.handlePostDraftSelectionChanged}
placeholder={intl.formatMessage(placeholder, {channelDisplayName})}
placeholderTextColor={PLACEHOLDER_COLOR}
multiline={true}
blurOnSubmit={false}
underlineColorAndroid='transparent'
style={style.input}
keyboardType={this.state.keyboardType}
onEndEditing={this.handleEndEditing}
disableFullscreenUI={true}
editable={!channelIsReadOnly}
/>
<Fade visible={this.isSendButtonVisible()}>
<SendButton
disabled={this.isFileLoading()}
handleSendMessage={this.handleSendMessage}
theme={theme}
/>
</Fade>
</View>
</View>
);
};
}
const getStyleSheet = makeStyleSheetFromTheme((theme) => {

View File

@@ -186,6 +186,7 @@ export default class Reactions extends PureComponent {
alwaysBounceHorizontal={false}
horizontal={true}
overScrollMode='never'
keyboardShouldPersistTaps={'always'}
>
{reactionElements}
</ScrollView>

View File

@@ -120,7 +120,7 @@ export default class DrawerLayout extends Component {
this.setState({ drawerShown, accessibilityViewIsModal });
}
if (this.props.keyboardDismissMode === 'on-drag') {
if (this.props.keyboardDismissMode === 'on-drag' || drawerShown) {
Keyboard.dismiss();
}
@@ -306,6 +306,7 @@ export default class DrawerLayout extends Component {
if (this.props.onDrawerClose) {
telemetry.end(['channel:close_drawer']);
this.props.onDrawerClose();
this.canClose = true;
}
this._emitStateChanged(IDLE);

View File

@@ -49,11 +49,11 @@ export default class ChannelsList extends PureComponent {
});
}
componentWillReceiveProps(nextProps) {
if (!nextProps.drawerOpened && this.props.drawerOpened) {
this.cancelSearch();
cancelSearch = () => {
if (this.refs.search_bar) {
this.refs.search_bar.cancel();
}
}
};
onSelectChannel = (channel, currentChannelId) => {
if (channel.fake) {
@@ -62,9 +62,7 @@ export default class ChannelsList extends PureComponent {
this.props.onSelectChannel(channel, currentChannelId);
}
if (this.refs.search_bar) {
this.refs.search_bar.cancel();
}
this.cancelSearch();
};
onSearch = (term) => {
@@ -79,7 +77,7 @@ export default class ChannelsList extends PureComponent {
this.props.onSearchStart();
};
cancelSearch = () => {
onSearchCancel = () => {
this.props.onSearchEnds();
this.setState({searching: false});
this.onSearch('');
@@ -142,7 +140,7 @@ export default class ChannelsList extends PureComponent {
titleCancelColor={theme.sidebarHeaderTextColor}
selectionColor={changeOpacity(theme.sidebarHeaderTextColor, 0.5)}
onSearchButtonPress={this.onSearch}
onCancelButtonPress={this.cancelSearch}
onCancelButtonPress={this.onSearchCancel}
onChangeText={this.onSearch}
onFocus={this.onSearchFocused}
searchIconCollapsedMargin={5}

View File

@@ -6,10 +6,11 @@ import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {
FlatList,
Keyboard,
Platform,
Text,
TouchableHighlight,
View,
Platform,
} from 'react-native';
import {injectIntl, intlShape} from 'react-intl';
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
@@ -61,6 +62,12 @@ class FilteredList extends Component {
constructor(props) {
super(props);
this.keyboardDismissProp = {
keyboardDismissMode: Platform.OS === 'ios' ? 'interactive' : 'none',
onScrollBeginDrag: Keyboard.dismiss,
};
this.state = {
dataSource: this.buildData(props),
};
@@ -112,7 +119,6 @@ class FilteredList extends Component {
createChannelElement = (channel) => {
return (
<ChannelItem
ref={channel.id}
channelId={channel.id}
channel={channel}
isSearchResult={true}
@@ -386,7 +392,6 @@ class FilteredList extends Component {
render() {
const {styles} = this.props;
const {dataSource} = this.state;
return (
@@ -394,13 +399,12 @@ class FilteredList extends Component {
style={styles.container}
>
<FlatList
ref='list'
data={dataSource}
renderItem={this.renderItem}
keyExtractor={(item) => item.id}
onViewableItemsChanged={this.updateUnreadIndicators}
keyboardDismissMode={Platform.OS === 'ios' ? 'interactive' : 'on-drag'}
keyboardShouldPersistTaps='always'
{...this.keyboardDismissProp}
keyboardShouldPersistTaps={'always'}
maxToRenderPerBatch={10}
viewabilityConfig={VIEWABILITY_CONFIG}
/>

View File

@@ -13,4 +13,4 @@ function mapStateToProps(state) {
};
}
export default connect(mapStateToProps)(ChannelsList);
export default connect(mapStateToProps, null, null, {forwardRef: true})(ChannelsList);

View File

@@ -17,13 +17,13 @@ import {memoizeResult} from 'mattermost-redux/utils/helpers';
import {isAdmin as checkIsAdmin, isSystemAdmin as checkIsSystemAdmin} from 'mattermost-redux/utils/user_utils';
import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general';
import {SidebarSectionTypes} from 'app/constants/view';
import {DeviceTypes, ViewTypes} from 'app/constants';
import List from './list';
const filterZeroUnreads = memoizeResult((sections) => {
return sections.filter((s) => {
if (s.type === SidebarSectionTypes.UNREADS) {
if (s.type === ViewTypes.SidebarSectionTypes.UNREADS) {
return s.items.length > 0;
}
return true;
@@ -38,11 +38,12 @@ function mapStateToProps(state) {
const isAdmin = checkIsAdmin(roles);
const isSystemAdmin = checkIsSystemAdmin(roles);
const sidebarPrefs = getSidebarPreferences(state);
const unreadChannelIds = getSortedUnreadChannelIds(state, null);
const lastUnreadChannel = DeviceTypes.IS_TABLET ? state.views.channel.keepChannelIdAsUnread : null;
const unreadChannelIds = getSortedUnreadChannelIds(state, lastUnreadChannel);
const favoriteChannelIds = getSortedFavoriteChannelIds(state);
const orderedChannelIds = filterZeroUnreads(getOrderedChannelIds(
state,
null,
lastUnreadChannel,
sidebarPrefs.grouping,
sidebarPrefs.sorting,
true, // The mobile app should always display the Unreads section regardless of user settings (MM-13420)

View File

@@ -5,6 +5,8 @@ import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {
InteractionManager,
Keyboard,
Platform,
SectionList,
Text,
TouchableHighlight,
@@ -17,7 +19,7 @@ import {General} from 'mattermost-redux/constants';
import {debounce} from 'mattermost-redux/actions/helpers';
import ChannelItem from 'app/components/sidebars/main/channels_list/channel_item';
import {ListTypes} from 'app/constants';
import {DeviceTypes, ListTypes} from 'app/constants';
import {SidebarSectionTypes} from 'app/constants/view';
import {t} from 'app/utils/i18n';
import {preventDoubleTap} from 'app/utils/tap';
@@ -44,7 +46,6 @@ export default class List extends PureComponent {
static contextTypes = {
intl: intlShape,
unreadChannelIds: [],
};
constructor(props) {
@@ -56,6 +57,11 @@ export default class List extends PureComponent {
width: 0,
};
this.keyboardDismissProp = {
keyboardDismissMode: Platform.OS === 'ios' ? 'interactive' : 'none',
onScrollBeginDrag: this.scrollBeginDrag,
};
MaterialIcon.getImageSource('close', 20, this.props.theme.sidebarHeaderTextColor).then((source) => {
this.closeButton = source;
});
@@ -135,7 +141,7 @@ export default class List extends PureComponent {
defaultMessage: 'CHANNELS',
};
}
}
};
buildSections = (props) => {
const {
@@ -209,7 +215,7 @@ export default class List extends PureComponent {
modalPresentationStyle: 'overCurrentContext',
},
});
}
};
goToCreatePublicChannel = preventDoubleTap(() => {
const {navigator, theme} = this.props;
@@ -308,6 +314,9 @@ export default class List extends PureComponent {
onSelectChannel = (channel, currentChannelId) => {
const {onSelectChannel} = this.props;
if (DeviceTypes.IS_TABLET) {
Keyboard.dismiss();
}
onSelectChannel(channel, currentChannelId);
};
@@ -409,6 +418,12 @@ export default class List extends PureComponent {
});
};
scrollBeginDrag = () => {
if (DeviceTypes.IS_TABLET) {
Keyboard.dismiss();
}
};
render() {
const {styles, theme} = this.props;
const {sections, width, showIndicator} = this.state;
@@ -425,10 +440,11 @@ export default class List extends PureComponent {
renderSectionHeader={this.renderSectionHeader}
keyExtractor={this.keyExtractor}
onViewableItemsChanged={this.updateUnreadIndicators}
keyboardDismissMode='on-drag'
maxToRenderPerBatch={10}
stickySectionHeadersEnabled={false}
viewabilityConfig={VIEWABILITY_CONFIG}
keyboardShouldPersistTaps={'always'}
{...this.keyboardDismissProp}
/>
{showIndicator &&
<UnreadIndicator

View File

@@ -59,7 +59,6 @@ export default class DrawerSwiper extends Component {
showTeamsPage = () => {
if (this.swiperRef?.current) {
this.swiperRef.current.scrollToIndex(0, true);
this.swiperPageSelected(0);
}
};

View File

@@ -5,6 +5,7 @@ import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {
BackHandler,
Dimensions,
Keyboard,
StyleSheet,
View,
@@ -17,6 +18,7 @@ import EventEmitter from 'mattermost-redux/utils/event_emitter';
import SafeAreaView from 'app/components/safe_area_view';
import DrawerLayout, {TABLET_WIDTH} from 'app/components/sidebars/drawer_layout';
import {DeviceTypes} from 'app/constants';
import mattermostManaged from 'app/mattermost_managed';
import tracker from 'app/utils/time_tracker';
import {t} from 'app/utils/i18n';
@@ -63,20 +65,25 @@ export default class ChannelSidebar extends Component {
this.swiperIndex = 1;
this.drawerRef = React.createRef();
this.channelListRef = React.createRef();
this.state = {
show: false,
openDrawerOffset,
drawerOpened: false,
searching: false,
isSplitView: false,
};
}
componentDidMount() {
this.mounted = true;
this.props.actions.getTeams();
this.handleDimensions();
EventEmitter.on('close_channel_drawer', this.closeChannelDrawer);
EventEmitter.on('renderDrawer', this.handleShowDrawerContent);
EventEmitter.on(WebsocketEvents.CHANNEL_UPDATED, this.handleUpdateTitle);
BackHandler.addEventListener('hardwareBackPress', this.handleAndroidBack);
Dimensions.addEventListener('change', this.handleDimensions);
}
componentWillReceiveProps(nextProps) {
@@ -96,7 +103,7 @@ export default class ChannelSidebar extends Component {
shouldComponentUpdate(nextProps, nextState) {
const {currentTeamId, deviceWidth, isLandscape, teamsCount} = this.props;
const {openDrawerOffset, show, searching} = this.state;
const {openDrawerOffset, isSplitView, show, searching} = this.state;
if (nextState.openDrawerOffset !== openDrawerOffset || nextState.show !== show || nextState.searching !== searching) {
return true;
@@ -104,14 +111,17 @@ export default class ChannelSidebar extends Component {
return nextProps.currentTeamId !== currentTeamId ||
nextProps.isLandscape !== isLandscape || nextProps.deviceWidth !== deviceWidth ||
nextProps.teamsCount !== teamsCount;
nextProps.teamsCount !== teamsCount ||
nextState.isSplitView !== isSplitView;
}
componentWillUnmount() {
this.mounted = false;
EventEmitter.off('close_channel_drawer', this.closeChannelDrawer);
EventEmitter.off(WebsocketEvents.CHANNEL_UPDATED, this.handleUpdateTitle);
EventEmitter.off('renderDrawer', this.handleShowDrawerContent);
BackHandler.removeEventListener('hardwareBackPress', this.handleAndroidBack);
Dimensions.addEventListener('change', this.handleDimensions);
}
handleAndroidBack = () => {
@@ -123,6 +133,15 @@ export default class ChannelSidebar extends Component {
return false;
};
handleDimensions = () => {
if (DeviceTypes.IS_TABLET && this.mounted) {
mattermostManaged.isRunningInSplitView().then((result) => {
const isSplitView = Boolean(result.isSplitView);
this.setState({isSplitView});
});
}
};
handleShowDrawerContent = () => {
this.setState({show: true});
};
@@ -142,6 +161,7 @@ export default class ChannelSidebar extends Component {
handleDrawerClose = () => {
this.setState({
drawerOpened: false,
searching: false,
});
this.resetDrawer();
Keyboard.dismiss();
@@ -283,6 +303,14 @@ export default class ChannelSidebar extends Component {
if (this.drawerSwiper) {
this.drawerSwiper.resetPage();
}
if (this.drawerRef?.current) {
this.drawerRef.current.canClose = true;
}
if (this.channelListRef?.current) {
this.channelListRef.current.cancelSearch();
}
};
renderNavigationView = (drawerWidth) => {
@@ -334,6 +362,7 @@ export default class ChannelSidebar extends Component {
style={style.swiperContent}
>
<ChannelsList
ref={this.channelListRef}
navigator={navigator}
onSelectChannel={this.selectChannel}
onJoinChannel={this.joinChannel}
@@ -369,6 +398,7 @@ export default class ChannelSidebar extends Component {
render() {
const {children, deviceWidth} = this.props;
const {openDrawerOffset} = this.state;
const isTablet = DeviceTypes.IS_TABLET && !this.state.isSplitView;
const drawerWidth = DeviceTypes.IS_TABLET ? TABLET_WIDTH : (deviceWidth - openDrawerOffset);
return (
@@ -379,7 +409,7 @@ export default class ChannelSidebar extends Component {
onDrawerOpen={this.handleDrawerOpen}
drawerWidth={drawerWidth}
useNativeAnimations={true}
isTablet={DeviceTypes.IS_TABLET}
isTablet={isTablet}
>
{children}
</DrawerLayout>

View File

@@ -182,9 +182,7 @@ export default class Swiper extends PureComponent {
}
this.scrollView.scrollTo({x: (index * this.props.width), animated});
if (index === 0) {
this.offset = 0;
}
this.updateIndex(this.props.width * index);
};
updateIndex = (offset) => {

View File

@@ -20,6 +20,13 @@ export const SidebarSectionTypes = {
ALPHA: 'alpha',
};
export const NotificationLevels = {
DEFAULT: 'default',
ALL: 'all',
MENTION: 'mention',
NONE: 'none',
};
const ViewTypes = keyMirror({
DATA_CLEANUP: null,
SERVER_URL_CHANGED: null,
@@ -109,4 +116,6 @@ export default {
PROFILE_PICTURE_SIZE: 32,
DATA_SOURCE_USERS: 'users',
DATA_SOURCE_CHANNELS: 'channels',
NotificationLevels,
SidebarSectionTypes,
};

View File

@@ -7,7 +7,6 @@ import {
AppState,
Dimensions,
InteractionManager,
Keyboard,
NativeModules,
Platform,
YellowBox,
@@ -50,7 +49,7 @@ import telemetry from 'app/telemetry';
import App from './app';
import './fetch_preconfig';
const AUTHENTICATION_TIMEOUT = 5 * 60 * 1000;
const PROMPT_IN_APP_PIN_CODE_AFTER = 5 * 60 * 1000;
// Hide warnings caused by React Native (https://github.com/facebook/react-native/issues/20841)
YellowBox.ignoreWarnings(['Require cycle: node_modules/react-native/Libraries/Network/fetch.js']);
@@ -262,9 +261,9 @@ export const handleManagedConfig = async (eventFromEmmServer = false) => {
if (config && Object.keys(config).length) {
app.setEMMEnabled(true);
authNeeded = config.inAppPinCode && config.inAppPinCode === 'true';
blurApplicationScreen = config.blurApplicationScreen && config.blurApplicationScreen === 'true';
jailbreakProtection = config.jailbreakProtection && config.jailbreakProtection === 'true';
authNeeded = config.inAppPinCode === 'true';
blurApplicationScreen = config.blurApplicationScreen === 'true';
jailbreakProtection = config.jailbreakProtection === 'true';
vendor = config.vendor || 'Mattermost';
if (!state.entities.general.credentials.token) {
@@ -323,29 +322,64 @@ export const handleManagedConfig = async (eventFromEmmServer = false) => {
return true;
};
const handleAuthentication = async (vendor) => {
const handleAuthentication = async (vendor, prompt = true) => {
app.setPerformingEMMAuthentication(true);
const isSecured = await mattermostManaged.isDeviceSecure();
const translations = app.getTranslations();
if (isSecured) {
try {
mattermostBucket.setPreference('emm', vendor);
await mattermostManaged.authenticate({
reason: translations[t('mobile.managed.secured_by')].replace('{vendor}', vendor),
fallbackToPasscode: true,
suppressEnterPassword: true,
});
if (prompt) {
await mattermostManaged.authenticate({
reason: translations[t('mobile.managed.secured_by')].replace('{vendor}', vendor),
fallbackToPasscode: true,
suppressEnterPassword: true,
});
}
} catch (err) {
mattermostManaged.quitApp();
return false;
}
} else {
await showNotSecuredAlert(vendor, translations);
mattermostManaged.quitApp();
return false;
}
app.setPerformingEMMAuthentication(false);
return true;
};
function showNotSecuredAlert(vendor, translations) {
return new Promise((resolve) => {
const options = [];
if (Platform.OS === 'android') {
options.push({
text: translations[t('mobile.managed.settings')],
onPress: () => {
mattermostManaged.goToSecuritySettings();
},
});
}
options.push({
text: translations[t('mobile.managed.exit')],
onPress: resolve,
style: 'cancel',
});
Alert.alert(
translations[t('mobile.managed.blocked_by')].replace('{vendor}', vendor),
Platform.OS === 'ios' ? translations[t('mobile.managed.not_secured.ios')] : translations[t('mobile.managed.not_secured.android')],
options,
{cancelable: false, onDismiss: resolve},
);
});
}
const handleSwitchToDefaultChannel = (teamId) => {
store.dispatch(selectDefaultChannel(teamId));
};
@@ -371,7 +405,7 @@ const launchSelectServer = () => {
});
};
const launchChannel = () => {
const launchChannel = (skipMetrics = false) => {
Navigation.startSingleScreenApp({
screen: {
screen: 'Channel',
@@ -381,6 +415,9 @@ const launchChannel = () => {
statusBarHideWithNavBar: false,
screenBackgroundColor: 'transparent',
},
passProps: {
skipMetrics,
},
},
appStyle: {
orientation: 'auto',
@@ -391,20 +428,20 @@ const launchChannel = () => {
const handleAppStateChange = (appState) => {
const isActive = appState === 'active';
const isBackground = appState === 'background';
store.dispatch(setAppState(isActive));
if (isActive) {
if (isActive && app.previousAppState === 'background') {
handleAppActive();
return;
} else if (isBackground) {
handleAppInActive();
}
handleAppInActive();
app.previousAppState = appState;
};
const handleAppActive = async () => {
const authExpired = (Date.now() - app.inBackgroundSince) >= AUTHENTICATION_TIMEOUT;
// This handles when the app was started in the background
// cause of an iOS push notification reply
if (Platform.OS === 'ios' && app.shouldRelaunchWhenActive) {
@@ -412,21 +449,20 @@ const handleAppActive = async () => {
app.setShouldRelaunchWhenActive(false);
}
// Once the app becomes active after more than 5 minutes in the background and is controlled by an EMM Provider
if (app.emmEnabled && app.inBackgroundSince && authExpired) {
try {
const config = await mattermostManaged.getConfig();
const authNeeded = config.inAppPinCode && config.inAppPinCode === 'true';
if (authNeeded) {
await handleAuthentication(config.vendor);
}
} catch (error) {
// do nothing
// if the app is being controlled by an EMM provider
if (app.emmEnabled) {
const config = await mattermostManaged.getConfig();
const authNeeded = config.inAppPinCode === 'true';
const authExpired = (Date.now() - app.inBackgroundSince) >= PROMPT_IN_APP_PIN_CODE_AFTER;
// Once the app becomes active we check if the device needs to have a passcode set
if (authNeeded) {
const prompt = app.inBackgroundSince && authExpired; // if more than 5 minutes have passed prompt for passcode
await handleAuthentication(config.vendor, prompt);
}
}
app.setInBackgroundSince(null);
Keyboard.dismiss();
};
const handleAppInActive = () => {
@@ -480,14 +516,19 @@ const fromPushNotification = Platform.OS === 'android' && Initialization.replyFr
if (startedSharedExtension || fromPushNotification) {
// Hold on launching Entry screen
app.setAppStarted(true);
// Listen for when the user opens the app
new NativeEventsReceiver().appLaunched(() => {
app.setAppStarted(false);
launchEntry();
});
}
if (!app.appStarted) {
launchEntry();
}
new NativeEventsReceiver().appLaunched(() => {
if (startedSharedExtension || fromPushNotification) {
app.setAppStarted(false);
launchEntry();
} else if (app.token && app.url) {
launchChannel(true);
} else {
launchSelectServer();
}
});

View File

@@ -1,7 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {BackHandler, NativeModules, DeviceEventEmitter} from 'react-native';
import {NativeModules, DeviceEventEmitter} from 'react-native';
import LocalAuth from 'react-native-local-auth';
import JailMonkey from 'jail-monkey';
@@ -36,6 +36,7 @@ export default {
},
authenticate: LocalAuth.auth,
blurAppScreen: MattermostManaged.blurAppScreen,
isRunningInSplitView: MattermostManaged.isRunningInSplitView,
getConfig: async () => {
try {
cachedConfig = await MattermostManaged.getConfig();
@@ -48,6 +49,7 @@ export default {
getCachedConfig: () => {
return cachedConfig;
},
goToSecuritySettings: MattermostManaged.goToSecuritySettings,
isDeviceSecure: async () => {
try {
return await LocalAuth.isDeviceSecure();
@@ -62,5 +64,5 @@ export default {
return JailMonkey.trustFall();
},
quitApp: BackHandler.exitApp,
quitApp: MattermostManaged.quitApp,
};

View File

@@ -45,10 +45,14 @@ export default {
return cachedConfig;
},
goToSecuritySettings: () => {
// Do nothing since iOS doesn't allow apps to do this
},
getCachedConfig: () => {
return cachedConfig;
},
hasSafeAreaInsets: MattermostManaged.hasSafeAreaInsets,
isRunningInSplitView: MattermostManaged.isRunningInSplitView,
isDeviceSecure: async () => {
try {
return await LocalAuth.isDeviceSecure();

View File

@@ -6,6 +6,7 @@ import {
ChannelTypes,
FileTypes,
PostTypes,
UserTypes,
} from 'mattermost-redux/action_types';
import {ViewTypes} from 'app/constants';
@@ -348,6 +349,44 @@ function lastChannelViewTime(state = {}, action) {
}
}
function keepChannelIdAsUnread(state = null, action) {
switch (action.type) {
case ViewTypes.SELECT_CHANNEL_WITH_MEMBER: {
const member = action.member;
const channel = action.channel;
if (!member || !channel) {
return state;
}
const msgCount = channel.total_msg_count - member.msg_count;
const hadMentions = member.mention_count > 0;
const hadUnreads = member.notify_props.mark_unread !== ViewTypes.NotificationLevels.MENTION && msgCount > 0;
if (hadMentions || hadUnreads) {
return {
id: member.channel_id,
hadMentions,
};
}
return null;
}
case ViewTypes.RECEIVED_FOCUSED_POST: {
if (state && action.channelId !== state.id) {
return null;
}
return state;
}
case UserTypes.LOGOUT_SUCCESS:
return null;
default:
return state;
}
}
export default combineReducers({
displayName,
drafts,
@@ -360,4 +399,5 @@ export default combineReducers({
retryFailed,
loadMorePostsVisible,
lastChannelViewTime,
keepChannelIdAsUnread,
});

View File

@@ -0,0 +1,57 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {Dimensions, View} from 'react-native';
import ChannelLoader from 'app/components/channel_loader';
import KeyboardLayout from 'app/components/layout/keyboard_layout';
import NetworkIndicator from 'app/components/network_indicator';
import SafeAreaView from 'app/components/safe_area_view';
import StatusBar from 'app/components/status_bar';
import PostTextbox from 'app/components/post_textbox';
import LocalConfig from 'assets/config';
import ChannelNavBar from './channel_nav_bar';
import ChannelPostList from './channel_post_list';
import ChannelBase, {ClientUpgradeListener, style} from './channel_base';
export default class ChannelAndroid extends ChannelBase {
render() {
const {height} = Dimensions.get('window');
const {
navigator,
} = this.props;
const channelLoaderStyle = [style.channelLoader, {height}];
const drawerContent = (
<SafeAreaView navigator={navigator}>
<StatusBar/>
<NetworkIndicator/>
<ChannelNavBar
navigator={navigator}
openChannelDrawer={this.openChannelSidebar}
openSettingsDrawer={this.openSettingsSidebar}
onPress={this.goToChannelInfo}
/>
<KeyboardLayout>
<View style={style.flex}>
<ChannelPostList navigator={navigator}/>
</View>
<PostTextbox
ref={this.postTextbox}
navigator={navigator}
/>
</KeyboardLayout>
<ChannelLoader
height={height}
style={channelLoaderStyle}
/>
{LocalConfig.EnableMobileClientUpgrade && <ClientUpgradeListener navigator={navigator}/>}
</SafeAreaView>
);
return this.renderChannel(drawerContent);
}
}

View File

@@ -0,0 +1,87 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {Dimensions, View} from 'react-native';
import {KeyboardTrackingView} from 'react-native-keyboard-tracking-view';
import Autocomplete, {AUTOCOMPLETE_MAX_HEIGHT} from 'app/components/autocomplete';
import ChannelLoader from 'app/components/channel_loader';
import FileUploadPreview from 'app/components/file_upload_preview';
import NetworkIndicator from 'app/components/network_indicator';
import PostTextbox from 'app/components/post_textbox';
import SafeAreaView from 'app/components/safe_area_view';
import StatusBar from 'app/components/status_bar';
import {DeviceTypes} from 'app/constants';
import LocalConfig from 'assets/config';
import ChannelBase, {ClientUpgradeListener, style} from './channel_base';
import ChannelNavBar from './channel_nav_bar';
import ChannelPostList from './channel_post_list';
const ACCESSORIES_CONTAINER_NATIVE_ID = 'channelAccessoriesContainer';
const CHANNEL_POST_TEXTBOX_CURSOR_CHANGE = 'onChannelTextBoxCursorChange';
const CHANNEL_POST_TEXTBOX_VALUE_CHANGE = 'onChannelTextBoxValueChange';
export default class ChannelIOS extends ChannelBase {
render() {
const {height} = Dimensions.get('window');
const {
currentChannelId,
navigator,
} = this.props;
const channelLoaderStyle = [style.channelLoader, {height}];
if ((DeviceTypes.IS_IPHONE_X || DeviceTypes.IS_TABLET)) {
channelLoaderStyle.push(style.iOSHomeIndicator);
}
const drawerContent = (
<React.Fragment>
<SafeAreaView navigator={navigator}>
<StatusBar/>
<NetworkIndicator/>
<ChannelNavBar
navigator={navigator}
openChannelDrawer={this.openChannelSidebar}
openSettingsDrawer={this.openSettingsSidebar}
onPress={this.goToChannelInfo}
/>
<ChannelPostList
navigator={navigator}
updateNativeScrollView={this.updateNativeScrollView}
/>
<View nativeID={ACCESSORIES_CONTAINER_NATIVE_ID}>
<FileUploadPreview/>
<Autocomplete
maxHeight={AUTOCOMPLETE_MAX_HEIGHT}
onChangeText={this.handleAutoComplete}
cursorPositionEvent={CHANNEL_POST_TEXTBOX_CURSOR_CHANGE}
valueEvent={CHANNEL_POST_TEXTBOX_VALUE_CHANGE}
/>
</View>
<ChannelLoader
height={height}
style={channelLoaderStyle}
/>
{LocalConfig.EnableMobileClientUpgrade && <ClientUpgradeListener navigator={navigator}/>}
</SafeAreaView>
<KeyboardTrackingView
ref={this.keyboardTracker}
scrollViewNativeID={currentChannelId}
accessoriesContainerID={ACCESSORIES_CONTAINER_NATIVE_ID}
>
<PostTextbox
cursorPositionEvent={CHANNEL_POST_TEXTBOX_CURSOR_CHANGE}
valueEvent={CHANNEL_POST_TEXTBOX_VALUE_CHANGE}
ref={this.postTextbox}
navigator={navigator}
/>
</KeyboardTrackingView>
</React.Fragment>
);
return this.renderChannel(drawerContent);
}
}

View File

@@ -5,7 +5,7 @@ import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {intlShape} from 'react-intl';
import {
Dimensions,
Keyboard,
Platform,
StyleSheet,
View,
@@ -16,38 +16,22 @@ import EventEmitter from 'mattermost-redux/utils/event_emitter';
import {app} from 'app/mattermost';
import InteractiveDialogController from 'app/components/interactive_dialog_controller';
import EmptyToolbar from 'app/components/start/empty_toolbar';
import ChannelLoader from 'app/components/channel_loader';
import InteractiveDialogController from 'app/components/interactive_dialog_controller';
import MainSidebar from 'app/components/sidebars/main';
import SettingsSidebar from 'app/components/sidebars/settings';
import KeyboardLayout from 'app/components/layout/keyboard_layout';
import NetworkIndicator from 'app/components/network_indicator';
import SafeAreaView from 'app/components/safe_area_view';
import StatusBar from 'app/components/status_bar';
import {DeviceTypes, ViewTypes} from 'app/constants';
import SettingsSidebar from 'app/components/sidebars/settings';
import {preventDoubleTap} from 'app/utils/tap';
import PostTextbox from 'app/components/post_textbox';
import PushNotifications from 'app/push_notifications';
import tracker from 'app/utils/time_tracker';
import LocalConfig from 'assets/config';
import telemetry from 'app/telemetry';
import ChannelNavBar from './channel_nav_bar';
import ChannelPostList from './channel_post_list';
import LocalConfig from 'assets/config';
const {
ANDROID_TOP_LANDSCAPE,
ANDROID_TOP_PORTRAIT,
IOS_TOP_LANDSCAPE,
IOS_TOP_PORTRAIT,
IOSX_TOP_PORTRAIT,
} = ViewTypes;
export let ClientUpgradeListener;
let ClientUpgradeListener;
export default class Channel extends PureComponent {
export default class ChannelBase extends PureComponent {
static propTypes = {
actions: PropTypes.shape({
loadChannelsIfNecessary: PropTypes.func.isRequired,
@@ -64,6 +48,7 @@ export default class Channel extends PureComponent {
theme: PropTypes.object.isRequired,
showTermsOfService: PropTypes.bool,
disableTermsModal: PropTypes.bool,
skipMetrics: PropTypes.bool,
};
static contextTypes = {
@@ -77,6 +62,13 @@ export default class Channel extends PureComponent {
constructor(props) {
super(props);
this.postTextbox = React.createRef();
this.keyboardTracker = React.createRef();
props.navigator.setStyle({
screenBackgroundColor: props.theme.centerChannelBg,
});
if (LocalConfig.EnableMobileClientUpgrade && !ClientUpgradeListener) {
ClientUpgradeListener = require('app/components/client_upgrade_listener').default;
}
@@ -97,7 +89,7 @@ export default class Channel extends PureComponent {
}
componentDidMount() {
if (tracker.initialLoad) {
if (tracker.initialLoad && !this.props.skipMetrics) {
this.props.actions.recordLoadTime('Start time', 'initialLoad');
}
@@ -107,7 +99,9 @@ export default class Channel extends PureComponent {
EventEmitter.emit('renderDrawer');
telemetry.end(['start:channel_screen']);
if (!this.props.skipMetrics) {
telemetry.end(['start:channel_screen']);
}
}
componentWillReceiveProps(nextProps) {
@@ -140,48 +134,22 @@ export default class Channel extends PureComponent {
if (this.props.currentChannelId && !prevProps.currentChannelId) {
EventEmitter.emit('renderDrawer');
}
if (this.props.currentChannelId && this.props.currentChannelId !== prevProps.currentChannelId) {
this.updateNativeScrollView();
}
}
componentWillUnmount() {
EventEmitter.off('leave_team', this.handleLeaveTeam);
}
attachPostTextBox = (ref) => {
this.postTextbox = ref;
};
blurPostTextBox = () => {
if (this.postTextbox) {
this.postTextbox.blur();
if (this.postTextbox?.current) {
this.postTextbox.current.blur();
}
};
channelLoaderDimensions = () => {
const {isLandscape} = this.props;
let top = 0;
let {height} = Dimensions.get('window');
switch (Platform.OS) {
case 'android':
if (isLandscape) {
top = ANDROID_TOP_LANDSCAPE;
} else {
top = ANDROID_TOP_PORTRAIT;
height -= 84;
}
break;
case 'ios':
if (isLandscape) {
top = IOS_TOP_LANDSCAPE;
} else {
height = DeviceTypes.IS_IPHONE_X ? (height - IOSX_TOP_PORTRAIT) : (height - IOS_TOP_PORTRAIT);
top = DeviceTypes.IS_IPHONE_X ? IOSX_TOP_PORTRAIT : IOS_TOP_PORTRAIT;
}
break;
}
return {height, top};
};
channelSidebarRef = (ref) => {
if (ref) {
this.channelSidebar = ref;
@@ -233,13 +201,23 @@ export default class Channel extends PureComponent {
},
};
Keyboard.dismiss();
if (Platform.OS === 'android') {
navigator.showModal(options);
} else {
navigator.push(options);
requestAnimationFrame(() => {
navigator.push(options);
});
}
});
handleAutoComplete = (value) => {
if (this.postTextbox?.current) {
this.postTextbox.current.handleTextChange(value, true);
}
};
handleLeaveTeam = () => {
this.props.actions.selectDefaultTeam();
};
@@ -278,7 +256,13 @@ export default class Channel extends PureComponent {
this.loadChannels(this.props.currentTeamId);
};
render() {
updateNativeScrollView = () => {
if (this.keyboardTracker?.current) {
this.keyboardTracker.current.resetScrollView(this.props.currentChannelId);
}
};
renderChannel(drawerContent) {
const {
channelsRequestFailed,
currentChannelId,
@@ -304,7 +288,7 @@ export default class Channel extends PureComponent {
<View style={style.flex}>
<EmptyToolbar
theme={theme}
isLandscape={this.props.isLandscape}
isLandscape={isLandscape}
/>
<Loading channelIsLoading={true}/>
</View>
@@ -312,8 +296,6 @@ export default class Channel extends PureComponent {
);
}
const loaderDimensions = this.channelLoaderDimensions();
return (
<MainSidebar
ref={this.channelSidebarRef}
@@ -325,30 +307,7 @@ export default class Channel extends PureComponent {
blurPostTextBox={this.blurPostTextBox}
navigator={navigator}
>
<SafeAreaView navigator={navigator}>
<StatusBar/>
<NetworkIndicator/>
<ChannelNavBar
navigator={navigator}
openChannelDrawer={this.openChannelSidebar}
openSettingsDrawer={this.openSettingsSidebar}
onPress={this.goToChannelInfo}
/>
<KeyboardLayout>
<View style={style.flex}>
<ChannelPostList navigator={navigator}/>
</View>
<PostTextbox
ref={this.attachPostTextBox}
navigator={navigator}
/>
</KeyboardLayout>
<ChannelLoader
style={[style.channelLoader, loaderDimensions]}
maxRows={isLandscape ? 4 : 6}
/>
{LocalConfig.EnableMobileClientUpgrade && <ClientUpgradeListener navigator={navigator}/>}
</SafeAreaView>
{drawerContent}
</SettingsSidebar>
<InteractiveDialogController
navigator={navigator}
@@ -359,7 +318,7 @@ export default class Channel extends PureComponent {
}
}
const style = StyleSheet.create({
export const style = StyleSheet.create({
flex: {
flex: 1,
},
@@ -368,4 +327,7 @@ const style = StyleSheet.create({
width: '100%',
flex: 1,
},
iOSHomeIndicator: {
paddingBottom: 5,
},
});

View File

@@ -3,9 +3,10 @@
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {Platform, View} from 'react-native';
import {Dimensions, Platform, View} from 'react-native';
import {DeviceTypes, ViewTypes} from 'app/constants';
import mattermostManaged from 'app/mattermost_managed';
import {makeStyleSheetFromTheme} from 'app/utils/theme';
import ChannelDrawerButton from './channel_drawer_button';
@@ -31,6 +32,30 @@ export default class ChannelNavBar extends PureComponent {
theme: PropTypes.object.isRequired,
};
state = {
isSplitView: false,
};
componentDidMount() {
this.mounted = true;
this.handleDimensions();
Dimensions.addEventListener('change', this.handleDimensions);
}
componentWillUnmount() {
this.mounted = false;
Dimensions.removeEventListener('change', this.handleDimensions);
}
handleDimensions = () => {
if (DeviceTypes.IS_TABLET && this.mounted) {
mattermostManaged.isRunningInSplitView().then((result) => {
const isSplitView = Boolean(result.isSplitView);
this.setState({isSplitView});
});
}
};
render() {
const {isLandscape, navigator, onPress, theme} = this.props;
const {openChannelDrawer, openSettingsDrawer} = this.props;
@@ -60,7 +85,7 @@ export default class ChannelNavBar extends PureComponent {
}
let drawerButtonVisible = false;
if (!DeviceTypes.IS_TABLET) {
if (!DeviceTypes.IS_TABLET || this.state.isSplitView) {
drawerButtonVisible = true;
}

View File

@@ -4,6 +4,7 @@
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {
Keyboard,
TouchableOpacity,
View,
} from 'react-native';
@@ -25,6 +26,7 @@ export default class ChannelSearchButton extends PureComponent {
handlePress = preventDoubleTap(async () => {
const {actions, navigator} = this.props;
Keyboard.dismiss();
await actions.clearSearch();
await actions.showSearchModal(navigator);
});

View File

@@ -4,6 +4,7 @@
import PropTypes from 'prop-types';
import React, {PureComponent} from 'react';
import {
Keyboard,
Platform,
StyleSheet,
View,
@@ -43,6 +44,7 @@ export default class ChannelPostList extends PureComponent {
postVisibility: PropTypes.number,
refreshing: PropTypes.bool.isRequired,
theme: PropTypes.object.isRequired,
updateNativeScrollView: PropTypes.func,
};
static defaultProps = {
@@ -86,6 +88,11 @@ export default class ChannelPostList extends PureComponent {
if (prevProps.channelId !== this.props.channelId && tracker.channelSwitch) {
this.props.actions.recordLoadTime('Switch Channel', 'channelSwitch');
}
if (!prevProps.postIds?.length && this.props.postIds?.length > 0 && this.props.updateNativeScrollView) {
// This is needed to re-bind the scrollview natively when getting the first posts
this.props.updateNativeScrollView();
}
}
componentWillUnmount() {
@@ -101,6 +108,7 @@ export default class ChannelPostList extends PureComponent {
const {actions, channelId, navigator, theme} = this.props;
const rootId = (post.root_id || post.id);
Keyboard.dismiss();
actions.loadThreadIfNecessary(rootId);
actions.selectPost(rootId);
@@ -123,7 +131,9 @@ export default class ChannelPostList extends PureComponent {
if (Platform.OS === 'android') {
navigator.showModal(options);
} else {
navigator.push(options);
requestAnimationFrame(() => {
navigator.push(options);
});
}
};
@@ -216,6 +226,7 @@ export default class ChannelPostList extends PureComponent {
navigator={navigator}
renderFooter={this.renderFooter}
refreshing={refreshing}
scrollViewNativeID={channelId}
/>
);
}

View File

@@ -186,8 +186,6 @@ export default class EditPost extends PureComponent {
numberOfLines={10}
style={[style.input, {height}]}
autoFocus={true}
autoCapitalize='none'
autoCorrect={false}
placeholder={{id: t('edit_post.editPost'), defaultMessage: 'Edit the post...'}}
placeholderTextColor={changeOpacity(theme.centerChannelColor, 0.4)}
underlineColorAndroid='transparent'

View File

@@ -364,6 +364,22 @@ export default class Login extends PureComponent {
);
}
let forgotPassword;
if (this.props.config.EnableSignInWithEmail === 'true' || this.props.config.EnableSignInWithUsername === 'true') {
forgotPassword = (
<Button
onPress={this.forgotPassword}
containerStyle={[style.forgotPasswordBtn]}
>
<FormattedText
id='login.forgot'
defaultMessage='I forgot my password'
style={style.forgotPasswordTxt}
/>
</Button>
);
}
return (
<View style={style.container}>
<StatusBar/>
@@ -419,16 +435,7 @@ export default class Login extends PureComponent {
disableFullscreenUI={true}
/>
{proceed}
<Button
onPress={this.forgotPassword}
containerStyle={[style.forgotPasswordBtn]}
>
<FormattedText
id='login.forgot'
defaultMessage='I forgot my password'
style={style.forgotPasswordTxt}
/>
</Button>
{forgotPassword}
</KeyboardAwareScrollView>
</TouchableWithoutFeedback>
</View>

View File

@@ -0,0 +1,75 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import FormattedText from 'app/components/formatted_text';
import {shallowWithIntl} from 'test/intl-test-helper';
import Login from './login';
describe('Login', () => {
const baseProps = {
config: {
EnableSignInWithEmail: 'true',
EnableSignInWithUsername: 'true',
},
license: {
IsLicensed: 'false',
},
loginId: '',
password: '',
loginRequest: {},
actions: {
handleLoginIdChanged: jest.fn(),
handlePasswordChanged: jest.fn(),
handleSuccessfulLogin: jest.fn(),
scheduleExpiredNotification: jest.fn(),
login: jest.fn(),
},
};
test('should show "I forgot my password" with only email login enabled', () => {
const props = {
...baseProps,
config: {
...baseProps.config,
EnableSignInWithUsername: 'false',
},
};
const wrapper = shallowWithIntl(<Login {...props}/>);
expect(wrapper.find(FormattedText).find({id: 'login.forgot'}).exists()).toBe(true);
});
test('should show "I forgot my password" with only username login enabled', () => {
const props = {
...baseProps,
config: {
...baseProps.config,
EnableSignInWithEmail: 'false',
},
};
const wrapper = shallowWithIntl(<Login {...props}/>);
expect(wrapper.find(FormattedText).find({id: 'login.forgot'}).exists()).toBe(true);
});
test('should not show "I forgot my password" without email or username login enabled', () => {
const props = {
...baseProps,
config: {
...baseProps.config,
EnableSignInWithEmail: 'false',
EnableSignInWithUsername: 'false',
},
};
const wrapper = shallowWithIntl(<Login {...props}/>);
expect(wrapper.find(FormattedText).find({id: 'login.forgot'}).exists()).toBe(false);
});
});

View File

@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`MoreChannels should match snapshot 1`] = `
<KeyboardLayout>
<ForwardRef(forwardConnectRef)>
<Connect(StatusBar) />
<React.Fragment>
<View
@@ -110,5 +110,5 @@ exports[`MoreChannels should match snapshot 1`] = `
}
/>
</React.Fragment>
</KeyboardLayout>
</ForwardRef(forwardConnectRef)>
`;

View File

@@ -292,6 +292,12 @@ export default class MoreChannels extends PureComponent {
);
};
renderItem = (props) => {
return (
<ChannelListRow {...props}/>
);
}
searchChannels = (text) => {
const {actions, channels, currentTeamId} = this.props;
@@ -363,7 +369,7 @@ export default class MoreChannels extends PureComponent {
noResults={this.renderNoResults()}
onLoadMore={more}
onRowPress={this.onSelectChannel}
renderItem={ChannelListRow}
renderItem={this.renderItem}
theme={theme}
/>
</React.Fragment>

View File

@@ -27,7 +27,7 @@ import {getDimensions} from 'app/selectors/device';
import PostOptions from './post_options';
function mapStateToProps(state, ownProps) {
export function mapStateToProps(state, ownProps) {
const post = ownProps.post;
const channel = getChannel(state, post.channel_id) || {};
const config = getConfig(state);
@@ -74,10 +74,6 @@ function mapStateToProps(state, ownProps) {
}
}
if (ownProps.channelIsReadOnly) {
canFlag = false;
}
if (ownProps.isSystemMessage) {
canAddReaction = false;
canReply = false;

View File

@@ -0,0 +1,51 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {mapStateToProps} from './index';
import * as channelSelectors from 'mattermost-redux/selectors/entities/channels';
import * as generalSelectors from 'mattermost-redux/selectors/entities/general';
import * as userSelectors from 'mattermost-redux/selectors/entities/users';
import * as commonSelectors from 'mattermost-redux/selectors/entities/common';
import * as teamSelectors from 'mattermost-redux/selectors/entities/teams';
import * as deviceSelectors from 'app/selectors/device';
import * as preferencesSelectors from 'mattermost-redux/selectors/entities/preferences';
channelSelectors.getChannel = jest.fn();
channelSelectors.getCurrentChannelId = jest.fn();
generalSelectors.getConfig = jest.fn();
generalSelectors.getLicense = jest.fn();
generalSelectors.hasNewPermissions = jest.fn();
userSelectors.getCurrentUserId = jest.fn();
commonSelectors.getCurrentUserId = jest.fn();
commonSelectors.getCurrentChannelId = jest.fn();
teamSelectors.getCurrentTeamId = jest.fn();
teamSelectors.getCurrentTeamUrl = jest.fn();
deviceSelectors.getDimensions = jest.fn();
preferencesSelectors.getTheme = jest.fn();
describe('mapStateToProps', () => {
const baseState = {};
const baseOwnProps = {
post: {},
};
test('canFlag is false for system messages', () => {
const ownProps = {
...baseOwnProps,
isSystemMessage: true,
};
const props = mapStateToProps(baseState, ownProps);
expect(props.canFlag).toBe(false);
});
test('canFlag is true for non-system messages', () => {
const ownProps = {
...baseOwnProps,
isSystemMessage: false,
};
const props = mapStateToProps(baseState, ownProps);
expect(props.canFlag).toBe(true);
});
});

View File

@@ -270,7 +270,7 @@ export default class PostOptions extends PureComponent {
const {navigator, theme} = this.props;
this.close();
requestAnimationFrame(() => {
setTimeout(() => {
MaterialIcon.getImageSource('close', 20, theme.sidebarHeaderTextColor).then((source) => {
navigator.showModal({
screen: 'AddReaction',
@@ -288,7 +288,7 @@ export default class PostOptions extends PureComponent {
},
});
});
});
}, 300);
};
handleReply = () => {
@@ -368,7 +368,7 @@ export default class PostOptions extends PureComponent {
const {navigator, post, theme} = this.props;
this.close();
requestAnimationFrame(() => {
setTimeout(() => {
MaterialIcon.getImageSource('close', 20, theme.sidebarHeaderTextColor).then((source) => {
navigator.showModal({
screen: 'EditPost',
@@ -386,7 +386,7 @@ export default class PostOptions extends PureComponent {
},
});
});
});
}, 300);
};
handleUnflagPost = () => {

View File

@@ -275,6 +275,18 @@ export default class SelectorScreen extends PureComponent {
);
};
renderChannelItem = (props) => {
return <ChannelListRow {...props}/>;
};
renderOptionItem = (props) => {
return <OptionListRow {...props}/>;
};
renderUserItem = (props) => {
return <UserListRow {...props}/>;
};
render() {
const {formatMessage} = this.context.intl;
const {theme, dataSource} = this.props;
@@ -294,11 +306,11 @@ export default class SelectorScreen extends PureComponent {
let rowComponent;
if (dataSource === ViewTypes.DATA_SOURCE_USERS) {
rowComponent = UserListRow;
rowComponent = this.renderUserItem;
} else if (dataSource === ViewTypes.DATA_SOURCE_CHANNELS) {
rowComponent = ChannelListRow;
rowComponent = this.renderChannelItem;
} else {
rowComponent = OptionListRow;
rowComponent = this.renderOptionItem;
}
const {data, listType} = this.getDataResults();

View File

@@ -1,64 +1,81 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`thread should match snapshot, has root post 1`] = `
<Connect(SafeAreaIos)
excludeHeader={true}
keyboardOffset={20}
>
<Connect(StatusBar) />
<KeyboardLayout
style={
Object {
"backgroundColor": "#ffffff",
"flex": 1,
}
}
<React.Fragment>
<Connect(SafeAreaIos)
excludeHeader={true}
>
<Connect(PostList)
currentUserId="member_user_id"
indicateNewMessages={false}
lastPostIndex={-1}
location="thread"
navigator={
Object {
"dismissModal": [MockFunction],
"pop": [MockFunction],
"resetTo": [MockFunction],
"setTitle": [MockFunction] {
"calls": Array [
Array [
<Connect(StatusBar) />
<React.Fragment>
<Connect(PostList)
currentUserId="member_user_id"
indicateNewMessages={false}
lastPostIndex={2}
lastViewedAt={0}
location="thread"
navigator={
Object {
"dismissModal": [MockFunction],
"pop": [MockFunction],
"resetTo": [MockFunction],
"setTitle": [MockFunction] {
"calls": Array [
Array [
Object {
"title": undefined,
},
],
],
"results": Array [
Object {
"title": undefined,
"type": "return",
"value": undefined,
},
],
],
"results": Array [
Object {
"type": "return",
"value": undefined,
},
],
},
},
}
}
}
postIds={
Array [
"root_id",
"post_id_1",
"post_id_2",
]
}
renderFooter={
<Loading
color="grey"
size="large"
style={Object {}}
onPostPress={[Function]}
postIds={
Array [
"root_id",
"post_id_1",
"post_id_2",
]
}
renderFooter={
<Loading
color="grey"
size="large"
style={Object {}}
/>
}
scrollViewNativeID="threadPostList"
/>
<View
nativeID="threadAccessoriesContainer"
>
<Connect(FileUploadPreview)
rootId="root_id"
/>
}
/>
<ForwardRef(forwardConnectRef)
cursorPositionEvent="onThreadTextBoxCursorChange"
maxHeight={200}
onChangeText={[Function]}
rootId="root_id"
valueEvent="onThreadTextBoxValueChange"
/>
</View>
</React.Fragment>
</Connect(SafeAreaIos)>
<KeyboardTrackingView
accessoriesContainerID="threadAccessoriesContainer"
scrollViewNativeID="threadPostList"
>
<ForwardRef(forwardConnectRef)
channelId="channel_id"
channelIsArchived={false}
cursorPositionEvent="onThreadTextBoxCursorChange"
navigator={
Object {
"dismissModal": [MockFunction],
@@ -83,84 +100,32 @@ exports[`thread should match snapshot, has root post 1`] = `
}
onCloseChannel={[Function]}
rootId="root_id"
valueEvent="onThreadTextBoxValueChange"
/>
</KeyboardLayout>
</Connect(SafeAreaIos)>
</KeyboardTrackingView>
</React.Fragment>
`;
exports[`thread should match snapshot, no root post, loading 1`] = `
<Connect(SafeAreaIos)
excludeHeader={true}
keyboardOffset={20}
>
<Connect(StatusBar) />
<KeyboardLayout
style={
Object {
"backgroundColor": "#ffffff",
"flex": 1,
}
}
<React.Fragment>
<Connect(SafeAreaIos)
excludeHeader={true}
>
<Connect(StatusBar) />
<Loading
color="grey"
size="large"
style={Object {}}
/>
</KeyboardLayout>
</Connect(SafeAreaIos)>
</Connect(SafeAreaIos)>
</React.Fragment>
`;
exports[`thread should match snapshot, render footer 1`] = `
<Connect(PostList)
currentUserId="member_user_id"
indicateNewMessages={false}
lastPostIndex={-1}
location="thread"
navigator={
Object {
"dismissModal": [MockFunction],
"pop": [MockFunction],
"resetTo": [MockFunction],
"setTitle": [MockFunction] {
"calls": Array [
Array [
Object {
"title": undefined,
},
],
],
"results": Array [
Object {
"type": "return",
"value": undefined,
},
],
},
}
}
postIds={
Array [
"root_id",
"post_id_1",
"post_id_2",
]
}
renderFooter={
<Loading
color="grey"
size="large"
style={Object {}}
/>
}
/>
`;
exports[`thread should match snapshot, render footer 2`] = `
<Connect(PostList)
currentUserId="member_user_id"
indicateNewMessages={false}
lastPostIndex={-1}
lastPostIndex={2}
lastViewedAt={0}
location="thread"
navigator={
@@ -185,6 +150,55 @@ exports[`thread should match snapshot, render footer 2`] = `
},
}
}
onPostPress={[Function]}
postIds={
Array [
"root_id",
"post_id_1",
"post_id_2",
]
}
renderFooter={
<Loading
color="grey"
size="large"
style={Object {}}
/>
}
scrollViewNativeID="threadPostList"
/>
`;
exports[`thread should match snapshot, render footer 2`] = `
<Connect(PostList)
currentUserId="member_user_id"
indicateNewMessages={false}
lastPostIndex={2}
lastViewedAt={0}
location="thread"
navigator={
Object {
"dismissModal": [MockFunction],
"pop": [MockFunction],
"resetTo": [MockFunction],
"setTitle": [MockFunction] {
"calls": Array [
Array [
Object {
"title": undefined,
},
],
],
"results": Array [
Object {
"type": "return",
"value": undefined,
},
],
},
}
}
onPostPress={[Function]}
postIds={
Array [
"root_id",
@@ -193,28 +207,21 @@ exports[`thread should match snapshot, render footer 2`] = `
]
}
renderFooter={null}
scrollViewNativeID="threadPostList"
/>
`;
exports[`thread should match snapshot, render footer 3`] = `
<Connect(SafeAreaIos)
excludeHeader={true}
keyboardOffset={20}
>
<Connect(StatusBar) />
<KeyboardLayout
style={
Object {
"backgroundColor": "#ffffff",
"flex": 1,
}
}
<React.Fragment>
<Connect(SafeAreaIos)
excludeHeader={true}
>
<Connect(StatusBar) />
<Loading
color="grey"
size="large"
style={Object {}}
/>
</KeyboardLayout>
</Connect(SafeAreaIos)>
</Connect(SafeAreaIos)>
</React.Fragment>
`;

View File

@@ -0,0 +1,70 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {THREAD} from 'app/constants/screen';
import Loading from 'app/components/loading';
import KeyboardLayout from 'app/components/layout/keyboard_layout';
import PostList from 'app/components/post_list';
import PostTextbox from 'app/components/post_textbox';
import SafeAreaView from 'app/components/safe_area_view';
import StatusBar from 'app/components/status_bar';
import ThreadBase from './thread_base';
export default class ThreadAndroid extends ThreadBase {
render() {
const {
channelId,
myMember,
navigator,
postIds,
rootId,
channelIsArchived,
} = this.props;
let content;
let postTextBox;
if (this.hasRootPost()) {
content = (
<PostList
renderFooter={this.renderFooter()}
indicateNewMessages={false}
postIds={postIds}
currentUserId={myMember && myMember.user_id}
lastViewedAt={this.state.lastViewedAt}
lastPostIndex={-1}
navigator={navigator}
onPostPress={this.hideKeyboard}
location={THREAD}
/>
);
postTextBox = (
<PostTextbox
channelIsArchived={channelIsArchived}
rootId={rootId}
channelId={channelId}
navigator={navigator}
onCloseChannel={this.onCloseChannel}
/>
);
} else {
content = (
<Loading/>
);
}
return (
<SafeAreaView>
<StatusBar/>
<KeyboardLayout>
{content}
{postTextBox}
</KeyboardLayout>
</SafeAreaView>
);
}
}

View File

@@ -0,0 +1,103 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {View} from 'react-native';
import {KeyboardTrackingView} from 'react-native-keyboard-tracking-view';
import {getLastPostIndex} from 'mattermost-redux/utils/post_list';
import Autocomplete, {AUTOCOMPLETE_MAX_HEIGHT} from 'app/components/autocomplete';
import FileUploadPreview from 'app/components/file_upload_preview';
import Loading from 'app/components/loading';
import PostList from 'app/components/post_list';
import PostTextbox from 'app/components/post_textbox';
import SafeAreaView from 'app/components/safe_area_view';
import StatusBar from 'app/components/status_bar';
import {THREAD} from 'app/constants/screen';
import ThreadBase from './thread_base';
const ACCESSORIES_CONTAINER_NATIVE_ID = 'threadAccessoriesContainer';
const THREAD_POST_TEXTBOX_CURSOR_CHANGE = 'onThreadTextBoxCursorChange';
const THREAD_POST_TEXTBOX_VALUE_CHANGE = 'onThreadTextBoxValueChange';
const SCROLLVIEW_NATIVE_ID = 'threadPostList';
export default class ThreadIOS extends ThreadBase {
render() {
const {
channelId,
myMember,
navigator,
postIds,
rootId,
channelIsArchived,
} = this.props;
let content;
let postTextBox;
if (this.hasRootPost()) {
content = (
<React.Fragment>
<PostList
renderFooter={this.renderFooter()}
indicateNewMessages={false}
postIds={postIds}
lastPostIndex={getLastPostIndex(postIds)}
currentUserId={myMember && myMember.user_id}
lastViewedAt={this.state.lastViewedAt}
navigator={navigator}
onPostPress={this.hideKeyboard}
location={THREAD}
scrollViewNativeID={SCROLLVIEW_NATIVE_ID}
/>
<View nativeID={ACCESSORIES_CONTAINER_NATIVE_ID}>
<FileUploadPreview
rootId={rootId}
/>
<Autocomplete
maxHeight={AUTOCOMPLETE_MAX_HEIGHT}
onChangeText={this.handleAutoComplete}
cursorPositionEvent={THREAD_POST_TEXTBOX_CURSOR_CHANGE}
valueEvent={THREAD_POST_TEXTBOX_VALUE_CHANGE}
rootId={rootId}
/>
</View>
</React.Fragment>
);
postTextBox = (
<KeyboardTrackingView
scrollViewNativeID={SCROLLVIEW_NATIVE_ID}
accessoriesContainerID={ACCESSORIES_CONTAINER_NATIVE_ID}
>
<PostTextbox
ref={this.postTextbox}
channelIsArchived={channelIsArchived}
rootId={rootId}
channelId={channelId}
navigator={navigator}
onCloseChannel={this.onCloseChannel}
cursorPositionEvent={THREAD_POST_TEXTBOX_CURSOR_CHANGE}
valueEvent={THREAD_POST_TEXTBOX_VALUE_CHANGE}
/>
</KeyboardTrackingView>
);
} else {
content = (
<Loading/>
);
}
return (
<React.Fragment>
<SafeAreaView excludeHeader={true}>
<StatusBar/>
{content}
</SafeAreaView>
{postTextBox}
</React.Fragment>
);
}
}

View File

@@ -8,7 +8,7 @@ import Preferences from 'mattermost-redux/constants/preferences';
import {General, RequestStatus} from 'mattermost-redux/constants';
import PostList from 'app/components/post_list';
import Thread from './thread.js';
import ThreadIOS from './thread.ios';
jest.mock('react-intl');
@@ -37,7 +37,7 @@ describe('thread', () => {
test('should match snapshot, has root post', () => {
const wrapper = shallow(
<Thread {...baseProps}/>,
<ThreadIOS {...baseProps}/>,
{context: {intl: {formatMessage: jest.fn()}}},
);
expect(wrapper.getElement()).toMatchSnapshot();
@@ -46,7 +46,7 @@ describe('thread', () => {
test('should match snapshot, no root post, loading', () => {
const newPostIds = ['post_id_1', 'post_id_2'];
const wrapper = shallow(
<Thread
<ThreadIOS
{...baseProps}
postIds={newPostIds}
/>,
@@ -75,7 +75,7 @@ describe('thread', () => {
};
const newNavigator = {...navigator};
const wrapper = shallow(
<Thread
<ThreadIOS
{...baseProps}
navigator={newNavigator}
/>,
@@ -88,7 +88,7 @@ describe('thread', () => {
test('should match snapshot, render footer', () => {
const wrapper = shallow(
<Thread {...baseProps}/>,
<ThreadIOS {...baseProps}/>,
{context: {intl: {formatMessage: jest.fn()}}},
);

View File

@@ -3,24 +3,16 @@
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {Platform} from 'react-native';
import {Keyboard, Platform} from 'react-native';
import {intlShape} from 'react-intl';
import {General, RequestStatus} from 'mattermost-redux/constants';
import {getLastPostIndex} from 'mattermost-redux/utils/post_list';
import {THREAD} from 'app/constants/screen';
import Loading from 'app/components/loading';
import KeyboardLayout from 'app/components/layout/keyboard_layout';
import PostList from 'app/components/post_list';
import PostTextbox from 'app/components/post_textbox';
import SafeAreaView from 'app/components/safe_area_view';
import StatusBar from 'app/components/status_bar';
import {makeStyleSheetFromTheme, setNavigatorStyles} from 'app/utils/theme';
import {setNavigatorStyles} from 'app/utils/theme';
import DeletedPost from 'app/components/deleted_post';
export default class Thread extends PureComponent {
export default class ThreadBase extends PureComponent {
static propTypes = {
actions: PropTypes.shape({
selectPost: PropTypes.func.isRequired,
@@ -41,26 +33,32 @@ export default class Thread extends PureComponent {
postIds: [],
};
state = {};
static contextTypes = {
intl: intlShape,
};
componentWillMount() {
const {channelType, displayName} = this.props;
const {intl} = this.context;
constructor(props, context) {
super(props);
const {channelType, displayName} = props;
const {formatMessage} = context.intl;
let title;
if (channelType === General.DM_CHANNEL) {
title = intl.formatMessage({id: 'mobile.routes.thread_dm', defaultMessage: 'Direct Message Thread'});
title = formatMessage({id: 'mobile.routes.thread_dm', defaultMessage: 'Direct Message Thread'});
} else {
title = intl.formatMessage({id: 'mobile.routes.thread', defaultMessage: '{channelName} Thread'}, {channelName: displayName});
title = formatMessage({id: 'mobile.routes.thread', defaultMessage: '{channelName} Thread'}, {channelName: displayName});
}
this.postTextbox = React.createRef();
this.props.navigator.setTitle({
title,
});
this.state = {
lastViewedAt: props.myMember && props.myMember.last_viewed_at,
};
}
componentWillReceiveProps(nextProps) {
@@ -96,10 +94,20 @@ export default class Thread extends PureComponent {
}
};
handleAutoComplete = (value) => {
if (this.postTextbox?.current) {
this.postTextbox.current.handleTextChange(value, true);
}
};
hasRootPost = () => {
return this.props.postIds.includes(this.props.rootId);
};
hideKeyboard = () => {
Keyboard.dismiss();
};
renderFooter = () => {
if (!this.hasRootPost() && this.props.threadLoadingStatus.status !== RequestStatus.STARTED) {
return (
@@ -133,69 +141,4 @@ export default class Thread extends PureComponent {
},
});
};
render() {
const {
channelId,
myMember,
navigator,
postIds,
rootId,
theme,
channelIsArchived,
} = this.props;
const style = getStyle(theme);
let content;
let postTextBox;
if (this.hasRootPost()) {
content = (
<PostList
renderFooter={this.renderFooter()}
indicateNewMessages={false}
postIds={postIds}
lastPostIndex={Platform.OS === 'android' ? getLastPostIndex(postIds) : -1}
currentUserId={myMember && myMember.user_id}
lastViewedAt={this.state.lastViewedAt}
navigator={navigator}
location={THREAD}
/>
);
postTextBox = (
<PostTextbox
channelIsArchived={channelIsArchived}
rootId={rootId}
channelId={channelId}
navigator={navigator}
onCloseChannel={this.onCloseChannel}
/>
);
} else {
content = (
<Loading/>
);
}
return (
<SafeAreaView
excludeHeader={true}
keyboardOffset={20}
>
<StatusBar/>
<KeyboardLayout style={style.container}>
{content}
{postTextBox}
</KeyboardLayout>
</SafeAreaView>
);
}
}
const getStyle = makeStyleSheetFromTheme((theme) => {
return {
container: {
flex: 1,
backgroundColor: theme.centerChannelBg,
},
};
});

View File

@@ -146,7 +146,7 @@ class Telemetry {
const {config} = store.getState().entities.general;
const deviceInfo = getDeviceInfo();
deviceInfo.serverVersion = config.Version;
deviceInfo.server_version = config.Version;
saveToTelemetryServer({trace_events: metrics, device_info: deviceInfo});

View File

@@ -1,6 +1,8 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Keyboard} from 'react-native';
import {
IMAGE_MAX_HEIGHT,
IMAGE_MIN_DIMENSION,
@@ -75,18 +77,21 @@ export function previewImageAtIndex(navigator, components, index, files) {
}
function goToImagePreview(navigator, passProps) {
navigator.showModal({
screen: 'ImagePreview',
title: '',
animationType: 'none',
passProps,
navigatorStyle: {
navBarHidden: true,
statusBarHidden: false,
statusBarHideWithNavBar: false,
screenBackgroundColor: 'transparent',
modalPresentationStyle: 'overCurrentContext',
},
Keyboard.dismiss();
requestAnimationFrame(() => {
navigator.showModal({
screen: 'ImagePreview',
title: '',
animationType: 'none',
passProps,
navigatorStyle: {
navBarHidden: true,
statusBarHidden: false,
statusBarHideWithNavBar: false,
screenBackgroundColor: 'transparent',
modalPresentationStyle: 'overCurrentContext',
},
});
});
}

View File

@@ -13,6 +13,10 @@ import mattermostManaged from 'app/mattermost_managed';
let certificate = '';
let previousState;
export async function checkConnection(isConnected) {
if (!isConnected) {
return {hasInternet: false, serverReachable: false};
}
if (!Client4.getBaseRoute().startsWith('http')) {
// If we don't have a connection or have a server yet, return the default implementation
return {hasInternet: isConnected, serverReachable: false};
@@ -43,6 +47,7 @@ export async function checkConnection(isConnected) {
auto: true,
waitsForConnectivity,
timeoutIntervalForResource,
timeout: 3000,
};
if (Platform.OS === 'ios' && certificate === '') {
@@ -59,7 +64,7 @@ export async function checkConnection(isConnected) {
}
function handleConnectionChange(onChange) {
return async (isConnected) => {
return async ({isConnected}) => {
if (isConnected !== previousState) {
previousState = isConnected;
@@ -72,13 +77,7 @@ function handleConnectionChange(onChange) {
export default function networkConnectionListener(onChange) {
const connectionChanged = handleConnectionChange(onChange);
NetInfo.isConnected.fetch().then((isConnected) => {
NetInfo.isConnected.addEventListener('connectionChange', connectionChanged);
connectionChanged(isConnected);
});
const removeEventListener = () => NetInfo.isConnected.removeEventListener('connectionChange', connectionChanged);
const removeEventListener = NetInfo.addEventListener(connectionChanged);
return {
removeEventListener,

View File

@@ -45,19 +45,19 @@
"combined_system_message.added_to_team.two": "{firstUser} und {secondUser} wurden durch {actor} **zum Team hinzugefügt**.",
"combined_system_message.joined_channel.many_expanded": "{users} und {lastUser} **sind dem Kanal beigetreten**.",
"combined_system_message.joined_channel.one": "{firstUser} **ist dem Kanal beigetreten**.",
"combined_system_message.joined_channel.one_you": "**sind dem Kanal beigetreten**.",
"combined_system_message.joined_channel.one_you": "Sie **sind dem Kanal beigetreten**.",
"combined_system_message.joined_channel.two": "{firstUser} und {secondUser} **sind dem Kanal beigetreten**.",
"combined_system_message.joined_team.many_expanded": "{users} und {lastUser} **sind dem Team beigetreten**.",
"combined_system_message.joined_team.one": "{firstUser} **ist dem Team beigetreten**.",
"combined_system_message.joined_team.one_you": "**ist dem Team beigetreten**.",
"combined_system_message.joined_team.one_you": "Sie **sind dem Team beigetreten**.",
"combined_system_message.joined_team.two": "{firstUser} und {secondUser} **sind dem Team beigetreten**.",
"combined_system_message.left_channel.many_expanded": "{users} und {lastUser} **haben den Kanal verlassen**.",
"combined_system_message.left_channel.one": "{firstUser} **hat den Kanal verlassen**.",
"combined_system_message.left_channel.one_you": "**hat den Kanal verlassen**.",
"combined_system_message.left_channel.one_you": "Sie **haben den Kanal verlassen**.",
"combined_system_message.left_channel.two": "{firstUser} und {secondUser} **haben den Kanal verlassen**.",
"combined_system_message.left_team.many_expanded": "{users} und {lastUser} **haben das Team verlassen**.",
"combined_system_message.left_team.one": "{firstUser} **hat das Team verlassen**.",
"combined_system_message.left_team.one_you": "**hat das Team verlassen**.",
"combined_system_message.left_team.one_you": "Sie **haben das Team verlassen**.",
"combined_system_message.left_team.two": "{firstUser} und {secondUser} **haben das Team verlassen**.",
"combined_system_message.removed_from_channel.many_expanded": "{users} und {lastUser} wurden **aus dem Kanal entfernt**.",
"combined_system_message.removed_from_channel.one": "{firstUser} wurde **aus dem Kanal entfernt**.",
@@ -70,7 +70,7 @@
"combined_system_message.you": "Sie",
"create_comment.addComment": "Kommentar hinzufügen...",
"create_post.deactivated": "Sie betrachten einen archivierten Kanal mit einem deaktivierten Benutzer.",
"create_post.write": "Write to {channelDisplayName}",
"create_post.write": "In {channelDisplayName} schreiben",
"edit_post.editPost": "Nachricht bearbeiten...",
"edit_post.save": "Speichern",
"error.team_not_found.title": "Team nicht gefunden",
@@ -80,11 +80,10 @@
"integrations.add": "Hinzufügen",
"intro_messages.anyMember": " Jedes Mitglied kann diesem Kanal beitreten und folgen.",
"intro_messages.beginning": "Start von {name}",
"intro_messages.channel": "Kanal",
"intro_messages.creator": "Dies ist der Start von {type} {name}, erstellt durch {creator} am {date}.",
"intro_messages.group": "Privater Kanal",
"intro_messages.creator": "Dies ist der Start von {name}, erstellt durch {creator} am {date}.",
"intro_messages.creatorPrivate": "Dies ist der Start von {name}, erstellt durch {creator} am {date}.",
"intro_messages.group_message": "Dies ist der Start ihres Gruppennachrichten-Verlaufs mit diesen Teammitgliedern. Nachrichten und hier geteilte Dateien sind für Personen außerhalb dieses Bereichs nicht sichtbar.",
"intro_messages.noCreator": "Dies ist der Start von {type} {name}, erstellt am {date}.",
"intro_messages.noCreator": "Dies ist der Start von {name}, erstellt am {date}.",
"intro_messages.onlyInvited": " Nur eingeladene Mitglieder können diesen privaten Kanal sehen.",
"last_users_message.added_to_channel.type": "wurden durch {actor} **dem Kanal hinzugefügt**.",
"last_users_message.added_to_team.type": "wurden durch {actor} **dem Team hinzugefügt**.",
@@ -181,7 +180,7 @@
"mobile.client_upgrade.close": "Später Aktualisieren",
"mobile.client_upgrade.current_version": "Neueste Version: {version}",
"mobile.client_upgrade.download_error.message": "Es ist ein Fehler beim Herunterladen der neuen Version aufgetreten.",
"mobile.client_upgrade.download_error.title": "Aktualisierung konnte nicht installiert werden",
"mobile.client_upgrade.download_error.title": "Konnte Aktualisierung nicht installieren.",
"mobile.client_upgrade.latest_version": "Ihre Version: {version}",
"mobile.client_upgrade.listener.dismiss_button": "Verwerfen",
"mobile.client_upgrade.listener.learn_more_button": "Mehr erfahren",
@@ -223,6 +222,7 @@
"mobile.drawer.teamsTitle": "Teams",
"mobile.edit_channel": "Speichern",
"mobile.edit_post.title": "Nachricht bearbeiten",
"mobile.edit_profile.remove_profile_photo": "Foto entfernen",
"mobile.emoji_picker.activity": "AKTIVITÄTEN",
"mobile.emoji_picker.custom": "BENUTZERDEFINIERT",
"mobile.emoji_picker.flags": "FLAGGEN",
@@ -251,6 +251,7 @@
"mobile.file_upload.camera_video": "Video aufnehmen",
"mobile.file_upload.library": "Foto-Bibliothek",
"mobile.file_upload.max_warning": "Uploads sind auf maximal fünf Dateien beschränkt.",
"mobile.file_upload.unsupportedMimeType": "Nur Dateien des folgenden MIME-Typen können hochgeladen werden:\n{mimeTypes}",
"mobile.file_upload.video": "Videobibliothek",
"mobile.flagged_posts.empty_description": "Markierungen dienen als Möglichkeit, Nachrichten für eine Wiedervorlage zu markieren. Ihre Markierungen sind persönlich und können nicht von anderen Benutzern gesehen werden.",
"mobile.flagged_posts.empty_title": "Keine markierte Nachrichten",
@@ -271,7 +272,10 @@
"mobile.managed.blocked_by": "Blockiert durch {vendor}",
"mobile.managed.exit": "Beenden",
"mobile.managed.jailbreak": "Geräten mit Jailbreak wird von {vendor} nicht vertraut, bitte beenden Sie die App.",
"mobile.managed.not_secured.android": "Dieses Gerät muss mit einer Bildschirmsperre gesichert werden, um Mattermost verwenden zu können.",
"mobile.managed.not_secured.ios": "Dieses Gerät muss mit einem Passcode gesichert werden, um Mattermost verwenden zu können.\n \nGehen Sie zu Einstellungen > Face ID & Passwort.",
"mobile.managed.secured_by": "Gesichert durch {vendor}",
"mobile.managed.settings": "Zu Einstellungen gehen",
"mobile.markdown.code.copy_code": "Code kopieren",
"mobile.markdown.code.plusMoreLines": "+{count, number} weitere {count, plural, one {Zeile} other {Zeilen}}",
"mobile.markdown.image.too_large": "Bild überschreitet die maximale Auflösung von {maxWidth} x {maxHeight}:",
@@ -338,6 +342,7 @@
"mobile.post_info.copy_text": "Text kopieren",
"mobile.post_info.flag": "Markieren",
"mobile.post_info.pin": "An Kanal anheften",
"mobile.post_info.reply": "Antworten",
"mobile.post_info.unflag": "Markierung entfernen",
"mobile.post_info.unpin": "Vom Kanal abheften",
"mobile.post_pre_header.flagged": "Markiert",
@@ -353,9 +358,11 @@
"mobile.post.delete_title": "Nachricht löschen",
"mobile.post.failed_delete": "Nachricht löschen",
"mobile.post.failed_retry": "Erneut versuchen",
"mobile.post.failed_title": "Ihre Nachricht konnte nicht gesendet werden",
"mobile.post.failed_title": "Konnte ihre Nachricht nicht senden.",
"mobile.post.retry": "Aktualisieren",
"mobile.posts_view.moreMsg": "Weitere neue Nachrichten oberhalb",
"mobile.privacy_link": "Datenschutzbedingungen",
"mobile.reaction_header.all_emojis": "Alle",
"mobile.recent_mentions.empty_description": "Hier werden Nachrichten auftauchen, die ihren Benutzernamen oder andere Wörter enthalten, die Erwähnungen auslösen.",
"mobile.recent_mentions.empty_title": "Keine letzten Erwähnungen",
"mobile.rename_channel.display_name_maxLength": "Kanalname muss kürzer als {maxLength, number} Zeichen sein",
@@ -378,6 +385,7 @@
"mobile.routes.channelInfo.createdBy": "Erstellt durch {creator} am ",
"mobile.routes.channelInfo.delete_channel": "Kanal archivieren",
"mobile.routes.channelInfo.favorite": "Favoriten",
"mobile.routes.channelInfo.groupManaged": "Mitglieder werden von verknüpften Gruppen verwaltet.",
"mobile.routes.code": "{language}-Code",
"mobile.routes.code.noLanguage": "Code",
"mobile.routes.edit_profile": "Profil bearbeiten",
@@ -432,6 +440,7 @@
"mobile.timezone_settings.automatically": "Automatisch einstellen",
"mobile.timezone_settings.manual": "Zeitzone ändern",
"mobile.timezone_settings.select": "Zeitzone auswählen",
"mobile.tos_link": "Nutzungsbedingungen",
"mobile.user_list.deactivated": "Deaktiviert",
"mobile.user.settings.notifications.email.fifteenMinutes": "Alle 15 Minuten",
"mobile.video_playback.failed_description": "Beim Abspielen des Videos ist ein Fehler aufgetreten.\n",
@@ -458,12 +467,13 @@
"password_send.link": "Falls das Konto existiert, wurde eine E-Mail zur Passwortzurücksetzung gesendet an:",
"password_send.reset": "Mein Passwort zurücksetzen",
"permalink.error.access": "Der dauerhafte Link verweist auf eine gelöschte Nachricht oder einen Kanal auf den Sie keinen Zugriff haben.",
"post_body.check_for_out_of_channel_groups_mentions.message": "wurde durch diese Erwähnung nicht benachrichtigt, da sich der Benutzer nicht im Kanal befinden. Er kann dem Kanal nicht hinzugefügt werden, da er nicht Mitglied der verknüpften Gruppen ist. Um ihn zu diesem Kanal hinzuzufügen, müssen er zu den verknüpften Gruppen hinzugefügt werden.",
"post_body.check_for_out_of_channel_mentions.link.and": " und ",
"post_body.check_for_out_of_channel_mentions.link.private": "sie zu diesem privaten Kanal hinzufügen",
"post_body.check_for_out_of_channel_mentions.link.public": "sie zu diesem Kanal hinzufügen",
"post_body.check_for_out_of_channel_mentions.message_last": "? Sie werden Zugriff auf den Nachrichtenverlauf haben.",
"post_body.check_for_out_of_channel_mentions.message.multiple": "wurden erwähnt, befinden sich aber nicht im Kanal. Möchten Sie ",
"post_body.check_for_out_of_channel_mentions.message.one": "wurde erwähnt, befinden sich aber nicht im Kanal. Möchten Sie ",
"post_body.check_for_out_of_channel_mentions.message.multiple": "wurde durch diese Erwähnung nicht benachrichtigt, da der Benutzer sich nicht im Kanal befindet. Möchten Sie ",
"post_body.check_for_out_of_channel_mentions.message.one": "wurde durch diese Erwähnung nicht benachrichtigt, da der Benutzer sich nicht im Kanal befindet. Möchten Sie ",
"post_body.commentedOn": "Kommentierte auf die Nachricht von {name}: ",
"post_body.deleted": "(Nachricht gelöscht)",
"post_info.auto_responder": "AUTOMATISCHE ANTWORT",
@@ -498,10 +508,10 @@
"status_dropdown.set_offline": "Offline",
"status_dropdown.set_online": "Online",
"status_dropdown.set_ooo": "Nicht im Büro",
"suggestion.mention.all": "ACHTUNG: Dies erwähnt jeden im Kanal",
"suggestion.mention.all": "Benachrichtigt jeden in diesem Kanal",
"suggestion.mention.channel": "Benachrichtigt jeden in diesem Kanal",
"suggestion.mention.channels": "Meine Kanäle",
"suggestion.mention.here": "Benachrichtigt jeden der im Kanal und online ist",
"suggestion.mention.here": "Benachrichtigt jeden in diesem Kanal",
"suggestion.mention.members": "Kanalmitglieder",
"suggestion.mention.morechannels": "Andere Kanäle",
"suggestion.mention.nonmembers": "Nicht im Kanal",
@@ -512,6 +522,7 @@
"terms_of_service.agreeButton": "Ich stimme zu",
"terms_of_service.api_error": "Konnte die Anfrage nicht abschließen. Falls der Fehler weiterhin besteht, fragen Sie den Systemadministrator.",
"user.settings.display.clockDisplay": "Uhrzeit-Format",
"user.settings.display.custom_theme": "Benutzerdefiniertes Motiv",
"user.settings.display.militaryClock": "24-Stunden-Format (z.B.: 16:00)",
"user.settings.display.normalClock": "12-Stunden-Format (z.B.: 4:00 PM)",
"user.settings.display.preferTime": "Wählen Sie das bevorzugte Zeitformat aus.",

View File

@@ -272,7 +272,10 @@
"mobile.managed.blocked_by": "Blocked by {vendor}",
"mobile.managed.exit": "Exit",
"mobile.managed.jailbreak": "Jailbroken devices are not trusted by {vendor}, please exit the app.",
"mobile.managed.not_secured.android": "This device must be secured with a screen lock to use Mattermost.",
"mobile.managed.not_secured.ios": "This device must be secured with a passcode to use Mattermost.\n\nGo to Settings > Face ID & Passcode.",
"mobile.managed.secured_by": "Secured by {vendor}",
"mobile.managed.settings": "Go to settings",
"mobile.markdown.code.copy_code": "Copy Code",
"mobile.markdown.code.plusMoreLines": "+{count, number} more {count, plural, one {line} other {lines}}",
"mobile.markdown.image.too_large": "Image exceeds max dimensions of {maxWidth} by {maxHeight}:",
@@ -555,4 +558,4 @@
"user.settings.push_notification.offline": "Offline",
"user.settings.push_notification.online": "Online, away or offline",
"web.root.signup_info": "All team communication in one place, searchable and accessible anywhere"
}
}

View File

@@ -45,19 +45,19 @@
"combined_system_message.added_to_team.two": "{firstUser} y {secondUser} **agregados al equipo** por {actor}.",
"combined_system_message.joined_channel.many_expanded": "{users} and {lastUser} **se unieron al canal**.",
"combined_system_message.joined_channel.one": "{firstUser} **se unió al canal**.",
"combined_system_message.joined_channel.one_you": "**unieron al canal**.",
"combined_system_message.joined_channel.one_you": "Tú **te uniste al canal**.",
"combined_system_message.joined_channel.two": "{firstUser} y {secondUser} **se unieron al canal**.",
"combined_system_message.joined_team.many_expanded": "{users} y {lastUser} **se unieron al equipo**.",
"combined_system_message.joined_team.one": "{firstUser} **se unió al equipo**.",
"combined_system_message.joined_team.one_you": "**unieron al equipo**.",
"combined_system_message.joined_team.one_you": "Tú **te uniste al equipo**.",
"combined_system_message.joined_team.two": "{firstUser} y {secondUser} **se unieron al equipo**.",
"combined_system_message.left_channel.many_expanded": "{users} y {lastUser} **abandonaron el canal**.",
"combined_system_message.left_channel.one": "{firstUser} **abandonó el canal**.",
"combined_system_message.left_channel.one_you": "**abandonaron el canal**.",
"combined_system_message.left_channel.one_you": "**abandonaste el canal**.",
"combined_system_message.left_channel.two": "{firstUser} y {secondUser} **abandonaron el canal**.",
"combined_system_message.left_team.many_expanded": "{users} y {lastUser} **abandonaron el equipo**.",
"combined_system_message.left_team.one": "{firstUser} **abandonó el equipo**.",
"combined_system_message.left_team.one_you": "**abandonó el equipo**.",
"combined_system_message.left_team.one_you": "**abandonaste el equipo**.",
"combined_system_message.left_team.two": "{firstUser} y {secondUser} **abandonaron el equipo**.",
"combined_system_message.removed_from_channel.many_expanded": "{users} y {lastUser} fueron **eliminados del canal**.",
"combined_system_message.removed_from_channel.one": "{firstUser} fue **eliminado del canal**.",
@@ -70,7 +70,7 @@
"combined_system_message.you": "Tu",
"create_comment.addComment": "Agregar un comentario...",
"create_post.deactivated": "Estás viendo un canal archivado con un usuario desactivado.",
"create_post.write": "Write to {channelDisplayName}",
"create_post.write": "Escribir en {channelDisplayName}",
"edit_post.editPost": "Editar el mensaje...",
"edit_post.save": "Guardar",
"error.team_not_found.title": "Equipo no encontrado",
@@ -80,11 +80,10 @@
"integrations.add": "Agregar",
"intro_messages.anyMember": " Cualquier miembro se puede unir y leer este canal.",
"intro_messages.beginning": "Inicio de {name}",
"intro_messages.channel": "canal",
"intro_messages.creator": "Este es el inicio del {type} {name}, creado por {creator} el {date}.",
"intro_messages.group": "canal privado",
"intro_messages.creator": "Este es el inicio del canal {name}, creado por {creator} el {date}.",
"intro_messages.creatorPrivate": "Este es el inicio del canal privado {name}, creado por {creator} el {date}.",
"intro_messages.group_message": "Este es el inicio de tu historial del grupo de mensajes con estos compañeros. Los mensajes y archivos que se comparten aquí no son mostrados a personas fuera de esta área.",
"intro_messages.noCreator": "Este es el inicio del {type} {name}, creado el {date}.",
"intro_messages.noCreator": "Este es el inicio del canal {name}, creado el {date}.",
"intro_messages.onlyInvited": " Sólo miembros invitados pueden ver este canal privado.",
"last_users_message.added_to_channel.type": "fueron **agregados al canal** por {actor}.",
"last_users_message.added_to_team.type": "fueron **agregados al equipo** por {actor}.",
@@ -223,6 +222,7 @@
"mobile.drawer.teamsTitle": "Equipos",
"mobile.edit_channel": "Guardar",
"mobile.edit_post.title": "Editando Mensaje",
"mobile.edit_profile.remove_profile_photo": "Quitar Foto",
"mobile.emoji_picker.activity": "ACTIVIDAD",
"mobile.emoji_picker.custom": "PERSONALIZADO",
"mobile.emoji_picker.flags": "BANDERAS",
@@ -251,6 +251,7 @@
"mobile.file_upload.camera_video": "Capturar Vídeo",
"mobile.file_upload.library": "Librería de Fotos",
"mobile.file_upload.max_warning": "Se pueden subir un máximo de 5 archivos.",
"mobile.file_upload.unsupportedMimeType": "Sólo archivos con el siguiente tipo pueden ser cargados:\n{mimeTypes}",
"mobile.file_upload.video": "Librería de Videos",
"mobile.flagged_posts.empty_description": "Las banderas son una forma de marcar los mensajes para hacerles seguimiento. Tus banderas son personales, y no puede ser vistas por otros usuarios.",
"mobile.flagged_posts.empty_title": "No hay Mensajes Marcados",
@@ -271,7 +272,10 @@
"mobile.managed.blocked_by": "Bloqueado por {vendor}",
"mobile.managed.exit": "Salir",
"mobile.managed.jailbreak": "{vendor} no confía en los dispositivos con jailbreak, por favor, salga de la aplicación.",
"mobile.managed.not_secured.android": "Este dispositivo debe estar asegurado con un bloqueo de pantalla para utilizar Mattermost.",
"mobile.managed.not_secured.ios": "Este dispositivo debe estar protegido con un código de acceso para utilizar Mattermost.\n\nVaya a Configuración > Identificación Facial y clave de acceso.",
"mobile.managed.secured_by": "Asegurado por {vendor}",
"mobile.managed.settings": "Ir a configuración",
"mobile.markdown.code.copy_code": "Copiar código",
"mobile.markdown.code.plusMoreLines": "+{count, number} más {count, plural, one {línea} other {líneas}}",
"mobile.markdown.image.too_large": "La imagen excede la dimensión máxima de {maxWidth} x {maxHeight}:",
@@ -338,6 +342,7 @@
"mobile.post_info.copy_text": "Copiar Texto",
"mobile.post_info.flag": "Marcar",
"mobile.post_info.pin": "Anclar al Canal",
"mobile.post_info.reply": "Responder",
"mobile.post_info.unflag": "Desmarcar",
"mobile.post_info.unpin": "Desprender del Canal",
"mobile.post_pre_header.flagged": "Marcado",
@@ -356,6 +361,8 @@
"mobile.post.failed_title": "No se pudo enviar el mensaje",
"mobile.post.retry": "Actualizar",
"mobile.posts_view.moreMsg": "Más Mensajes Arriba",
"mobile.privacy_link": "Política de Privacidad",
"mobile.reaction_header.all_emojis": "Todos",
"mobile.recent_mentions.empty_description": "Mensajes que contienen tu nombre de usuario u otras palabras que desencadenan menciones aparecerán aquí.",
"mobile.recent_mentions.empty_title": "No hay Menciones recientes",
"mobile.rename_channel.display_name_maxLength": "El nombre del canal debe tener menos de {maxLength, number} caracteres",
@@ -378,6 +385,7 @@
"mobile.routes.channelInfo.createdBy": "Creado por {creator} el ",
"mobile.routes.channelInfo.delete_channel": "Archivar Canal",
"mobile.routes.channelInfo.favorite": "Favorito",
"mobile.routes.channelInfo.groupManaged": "Los miembros son gestionados por grupos enlazados",
"mobile.routes.code": "Código {language}",
"mobile.routes.code.noLanguage": "Código",
"mobile.routes.edit_profile": "Editar Perfil",
@@ -432,6 +440,7 @@
"mobile.timezone_settings.automatically": "Asignar automáticamente",
"mobile.timezone_settings.manual": "Cambiar zona horaria",
"mobile.timezone_settings.select": "Seleccione la zona horaria",
"mobile.tos_link": "Términos de Servicio",
"mobile.user_list.deactivated": "Desactivado",
"mobile.user.settings.notifications.email.fifteenMinutes": "Cada 15 minutos",
"mobile.video_playback.failed_description": "Ocurrió un error al reproducir el vídeo.\n",
@@ -458,12 +467,13 @@
"password_send.link": "Si la cuenta existe, una correo electrónico de reinicio de contraseña será enviado a:",
"password_send.reset": "Restablecer mi contraseña",
"permalink.error.access": "El Enlace permanente pertenece a un mensaje eliminado o a un canal al cual no tienes acceso.",
"post_body.check_for_out_of_channel_groups_mentions.message": "no fueron notificados por esta mención porque no se encuentra en este canal. No pueden ser agregados al canal porque no son miembros de los grupos enlazados. Para agregarlos a este canal, deben ser agregados a alguno de los grupos enlazados.",
"post_body.check_for_out_of_channel_mentions.link.and": " y ",
"post_body.check_for_out_of_channel_mentions.link.private": "agregarlos a este canal privado",
"post_body.check_for_out_of_channel_mentions.link.public": "agregarlos al canal",
"post_body.check_for_out_of_channel_mentions.message_last": "? Tendrán acceso al historial de mensajes.",
"post_body.check_for_out_of_channel_mentions.message.multiple": "fueron mencionados pero no son parte de este canal. Quieres ",
"post_body.check_for_out_of_channel_mentions.message.one": "fue mencionado pero no es parte de este canal. Quieres ",
"post_body.check_for_out_of_channel_mentions.message.multiple": "no fueron notificados por esta mención porque no se encuentran en el canal. Quieres ",
"post_body.check_for_out_of_channel_mentions.message.one": "no fue notificado por esta mención porque no se encuentra en el canal. Quieres ",
"post_body.commentedOn": "Comento en el mensaje de {name}: ",
"post_body.deleted": "(mensaje eliminado)",
"post_info.auto_responder": "RESPUESTA AUTOMÁTICA",
@@ -498,10 +508,10 @@
"status_dropdown.set_offline": "Desconectado",
"status_dropdown.set_online": "En línea",
"status_dropdown.set_ooo": "Fuera de Oficina",
"suggestion.mention.all": "PRECAUCIÓN: Esto menciona a todos los usuarios en el canal",
"suggestion.mention.channel": "Notifica a todas las personas en el canal",
"suggestion.mention.all": "Notifica a todas las personas en este canal",
"suggestion.mention.channel": "Notifica a todas las personas en este canal",
"suggestion.mention.channels": "Mis Canales",
"suggestion.mention.here": "Notifica a todos en el canal que estén en línea",
"suggestion.mention.here": "Notifica a todas las personas disponibles en este canal",
"suggestion.mention.members": "Miembros del Canal",
"suggestion.mention.morechannels": "Otros Canales",
"suggestion.mention.nonmembers": "No en el Canal",
@@ -512,6 +522,7 @@
"terms_of_service.agreeButton": "Acepto",
"terms_of_service.api_error": "No se puede completar la solicitud. Si el problema persiste, contacta a tu Administrador de Sistema.",
"user.settings.display.clockDisplay": "Visualización del Reloj",
"user.settings.display.custom_theme": "Tema Personalizado",
"user.settings.display.militaryClock": "Reloj de 24 horas (ejemplo: 16:00)",
"user.settings.display.normalClock": "Reloj de 12 horas (ejemplo: 4:00 pm)",
"user.settings.display.preferTime": "Selecciona como prefieres mostrar la hora.",

View File

@@ -45,19 +45,19 @@
"combined_system_message.added_to_team.two": "{firstUser} et {secondUser} ont été **ajoutés à l'équipe** par {actor}.",
"combined_system_message.joined_channel.many_expanded": "{users} et {lastUser} ont **rejoint le canal**.",
"combined_system_message.joined_channel.one": "{firstUser} a **rejoint le canal**.",
"combined_system_message.joined_channel.one_you": "a **rejoint le canal**.",
"combined_system_message.joined_channel.one_you": "Vous avez **rejoint le canal**.",
"combined_system_message.joined_channel.two": "{firstUser} et {secondUser} ont **rejoint le canal**.",
"combined_system_message.joined_team.many_expanded": "{users} et {lastUser} ont **rejoint l'équipe**.",
"combined_system_message.joined_team.one": "{firstUser} a **rejoint l'équipe**.",
"combined_system_message.joined_team.one_you": "a **rejoint l'équipe**.",
"combined_system_message.joined_team.one_you": "Vous avez **rejoint l'équipe**.",
"combined_system_message.joined_team.two": "{firstUser} et {secondUser} ont **rejoint l'équipe**.",
"combined_system_message.left_channel.many_expanded": "{users} et {lastUser} ont **quitté le canal**.",
"combined_system_message.left_channel.one": "{firstUser} a **quitté le canal**.",
"combined_system_message.left_channel.one_you": "a **quitté le canal**.",
"combined_system_message.left_channel.one_you": "Vous avez **quitté le canal**.",
"combined_system_message.left_channel.two": "{firstUser} et {secondUser} ont **quitté le canal**.",
"combined_system_message.left_team.many_expanded": "{users} et {lastUser} ont **quitté l'équipe**.",
"combined_system_message.left_team.one": "{firstUser} a **quitté l'équipe**.",
"combined_system_message.left_team.one_you": "a **quitté l'équipe**.",
"combined_system_message.left_team.one_you": "Vous avez **quitté l'équipe**.",
"combined_system_message.left_team.two": "{firstUser} et {secondUser} ont **quitté l'équipe**.",
"combined_system_message.removed_from_channel.many_expanded": "{users} et {lastUser} ont été **retirés du canal**.",
"combined_system_message.removed_from_channel.one": "{firstUser} a été **retiré du canal**.",
@@ -70,7 +70,7 @@
"combined_system_message.you": "Vous",
"create_comment.addComment": "Commenter...",
"create_post.deactivated": "Ceci est un canal de messages personnels archivé contenant une discussion avec un utilisateur désactivé.",
"create_post.write": "Write to {channelDisplayName}",
"create_post.write": "Écrire à {channelDisplayName}",
"edit_post.editPost": "Modifier le message...",
"edit_post.save": "Enregistrer",
"error.team_not_found.title": "Équipe introuvable",
@@ -80,11 +80,10 @@
"integrations.add": "Ajouter",
"intro_messages.anyMember": " Tout membre peut rejoindre et lire ce canal.",
"intro_messages.beginning": "Début de {name}",
"intro_messages.channel": "canal",
"intro_messages.creator": "Ceci est le début de {type} {name}, créé par {creator} le {date}.",
"intro_messages.group": "canal privé",
"intro_messages.creator": "Ceci est le début du {type} {name}, créé par {creator} le {date}.",
"intro_messages.creatorPrivate": "Ceci est le début de {type} {name}, créé par {creator} le {date}.",
"intro_messages.group_message": "Vous êtes au début de votre historique de messages de groupe avec ces utilisateurs. Les messages privés et les fichiers partagés ici ne sont pas visibles par les autres utilisateurs.",
"intro_messages.noCreator": "Ceci est le début de {name} {type}, créé le {date}.",
"intro_messages.noCreator": "Ceci est le début du {name} {type}, créé le {date}.",
"intro_messages.onlyInvited": " Seuls les membres invités peuvent voir ce canal privé.",
"last_users_message.added_to_channel.type": "a été **ajouté au canal** par {actor}.",
"last_users_message.added_to_team.type": "a été **ajouté à l'équipe** par {actor}.",
@@ -223,6 +222,7 @@
"mobile.drawer.teamsTitle": "Équipes",
"mobile.edit_channel": "Enregistrer",
"mobile.edit_post.title": "Edition du message",
"mobile.edit_profile.remove_profile_photo": "Remove Photo",
"mobile.emoji_picker.activity": "ACTIVITÉ",
"mobile.emoji_picker.custom": "PERSONNALISÉ",
"mobile.emoji_picker.flags": "DRAPEAUX",
@@ -251,6 +251,7 @@
"mobile.file_upload.camera_video": "Enregistrer une vidéo",
"mobile.file_upload.library": "Bibliothèque de photos",
"mobile.file_upload.max_warning": "Envois limités à maximum 5 fichiers.",
"mobile.file_upload.unsupportedMimeType": "Seuls les fichiers avec ce type MIME peuvent être envoyés :\n{mimeTypes}",
"mobile.file_upload.video": "Bibliothèque vidéo",
"mobile.flagged_posts.empty_description": "Marquer un message est un bon moyen d'assurer le suivi. Marquer un message est personnel et ne peut être vu par les autres utilisateurs.",
"mobile.flagged_posts.empty_title": "Aucun message marqué d'un indicateur",
@@ -271,7 +272,10 @@
"mobile.managed.blocked_by": "Bloqué par {vendor}",
"mobile.managed.exit": "Quitter",
"mobile.managed.jailbreak": "Les dispositifs jailbreakés ne sont pas approuvés par {vendor}, veuillez quitter l'application.",
"mobile.managed.not_secured.android": "This device must be secured with a screen lock to use Mattermost.",
"mobile.managed.not_secured.ios": "This device must be secured with a passcode to use Mattermost.\n\nGo to Settings > Face ID & Passcode.",
"mobile.managed.secured_by": "Sécurisé par {vendor}",
"mobile.managed.settings": "Go to settings",
"mobile.markdown.code.copy_code": "Copier le code",
"mobile.markdown.code.plusMoreLines": "+{count, number} other {count, plural, one {ligne} other {lignes}}",
"mobile.markdown.image.too_large": "L'image dépasse les dimensions maximales de {maxWidth} par {maxHeight} :",
@@ -338,6 +342,7 @@
"mobile.post_info.copy_text": "Copier le texte",
"mobile.post_info.flag": "Marquer avec un indicateur",
"mobile.post_info.pin": "Épingler au canal",
"mobile.post_info.reply": "Répondre",
"mobile.post_info.unflag": "Supprimer l'indicateur",
"mobile.post_info.unpin": "Désépingler du canal",
"mobile.post_pre_header.flagged": "Marqué d'un indicateur",
@@ -356,6 +361,8 @@
"mobile.post.failed_title": "Impossible d'envoyer votre message",
"mobile.post.retry": "Rafraîchir",
"mobile.posts_view.moreMsg": "Plus de nouveaux messages au-dessus",
"mobile.privacy_link": "Politique de respect de la vie privée",
"mobile.reaction_header.all_emojis": "Tous",
"mobile.recent_mentions.empty_description": "Les messages qui contiennent votre nom d'utilisateur et d'autres mots qui déclenchent des mentions apparaissent ici.",
"mobile.recent_mentions.empty_title": "Aucune mention récente",
"mobile.rename_channel.display_name_maxLength": "Ce champ doit faire moins de {maxLength, number} caractères",
@@ -378,6 +385,7 @@
"mobile.routes.channelInfo.createdBy": "Créé par {creator} le ",
"mobile.routes.channelInfo.delete_channel": "Archiver le canal",
"mobile.routes.channelInfo.favorite": "Favoris",
"mobile.routes.channelInfo.groupManaged": "Members are managed by linked groups",
"mobile.routes.code": "Code {language}",
"mobile.routes.code.noLanguage": "Code",
"mobile.routes.edit_profile": "Éditer le profil",
@@ -432,6 +440,7 @@
"mobile.timezone_settings.automatically": "Définir automatiquement",
"mobile.timezone_settings.manual": "Changer le fuseau horaire",
"mobile.timezone_settings.select": "Sélectionner un fuseau horaire",
"mobile.tos_link": "Conditions d'utilisation",
"mobile.user_list.deactivated": "Désactivé",
"mobile.user.settings.notifications.email.fifteenMinutes": "Toutes les 15 minutes",
"mobile.video_playback.failed_description": "Une erreur s'est produite lors de la tentative de lecture de la vidéo.\n",
@@ -458,12 +467,13 @@
"password_send.link": "Si le compte existe, un e-mail de redéfinition de mot de passe sera envoyé à :",
"password_send.reset": "Réinitialiser mon mot de passe",
"permalink.error.access": "Ce lien correspond à un message supprimé ou appartenant à un canal auquel vous n'avez pas accès.",
"post_body.check_for_out_of_channel_groups_mentions.message": "did not get notified by this mention because they are not in the channel. They cannot be added to the channel because they are not a member of the linked groups. To add them to this channel, they must be added to the linked groups.",
"post_body.check_for_out_of_channel_mentions.link.and": " et ",
"post_body.check_for_out_of_channel_mentions.link.private": "ajouter à ce canal privé",
"post_body.check_for_out_of_channel_mentions.link.public": "ajouter à ce canal",
"post_body.check_for_out_of_channel_mentions.message_last": "? Ils auront alors accès à tout l'historique de messages pour ce canal.",
"post_body.check_for_out_of_channel_mentions.message.multiple": "ont été mentionnés, mais ne sont pas dans le canal. Voulez-vous ",
"post_body.check_for_out_of_channel_mentions.message.one": "a été mentionné, mais n'est pas dans le canal. Voulez-vous ",
"post_body.check_for_out_of_channel_mentions.message.multiple": "did not get notified by this mention because they are not in the channel. Would you like to ",
"post_body.check_for_out_of_channel_mentions.message.one": "did not get notified by this mention because they are not in the channel. Would you like to ",
"post_body.commentedOn": "a commenté le message de {name} : ",
"post_body.deleted": "(message supprimé)",
"post_info.auto_responder": "RÉPONSE AUTOMATIQUE",
@@ -498,10 +508,10 @@
"status_dropdown.set_offline": "Hors ligne",
"status_dropdown.set_online": "En ligne",
"status_dropdown.set_ooo": "Absent du bureau",
"suggestion.mention.all": "ATTENTION : Ceci mentionne tout le monde dans le canal",
"suggestion.mention.all": "Notifier tout le monde dans le canal",
"suggestion.mention.channel": "Notifier tout le monde dans le canal",
"suggestion.mention.channels": "Mes canaux",
"suggestion.mention.here": "Notifier toutes les personnes connectées dans ce canal",
"suggestion.mention.here": "Notifier tout le monde dans le canal",
"suggestion.mention.members": "Membres du canal",
"suggestion.mention.morechannels": "Autres canaux",
"suggestion.mention.nonmembers": "Pas dans le canal",
@@ -512,6 +522,7 @@
"terms_of_service.agreeButton": "Je suis d'accord",
"terms_of_service.api_error": "Impossible de terminer la requête. Si ce problème persiste, contactez votre administrateur système.",
"user.settings.display.clockDisplay": "Affichage de l'horloge",
"user.settings.display.custom_theme": "Thème personnalisé",
"user.settings.display.militaryClock": "Horloge 24 heures (ex. : 16:00)",
"user.settings.display.normalClock": "Horloge 12 heures (ex. : 4:00 PM)",
"user.settings.display.preferTime": "Choisissez la façon dont vous préférez voir les heures affichées dans l'application.",

View File

@@ -45,19 +45,19 @@
"combined_system_message.added_to_team.two": "{firstUser} e {secondUser} **aggiunti al gruppo** da {actor}.",
"combined_system_message.joined_channel.many_expanded": "{users} e {lastUser} **aggiunti al canale**.",
"combined_system_message.joined_channel.one": "{firstUser} **aggiunto al canale**.",
"combined_system_message.joined_channel.one_you": "**si è unito al canale**.",
"combined_system_message.joined_channel.one_you": "**Ti sei è unito al canale**.",
"combined_system_message.joined_channel.two": "{firstUser} e {secondUser} **aggiunto al canale**.",
"combined_system_message.joined_team.many_expanded": "{users} e {lastUser} **aggiunti al gruppo**.",
"combined_system_message.joined_team.one": "{firstUser} **aggiunto al gruppo**.",
"combined_system_message.joined_team.one_you": "**si è unito al gruppo**.",
"combined_system_message.joined_team.one_you": "**Ti sei è unito al gruppo**.",
"combined_system_message.joined_team.two": "{firstUser} e {secondUser} **aggiunto al gruppo**.",
"combined_system_message.left_channel.many_expanded": "{users} e {lastUser} hanno **abbandonato il canale**.",
"combined_system_message.left_channel.one": "{firstUser} ha **abbandonato il canale**.",
"combined_system_message.left_channel.one_you": "**ha abbandonato il canale**.",
"combined_system_message.left_channel.one_you": "**Hai abbandonato il canale**.",
"combined_system_message.left_channel.two": "{firstUser} e {secondUser} hanno **abbandonato il canale**.",
"combined_system_message.left_team.many_expanded": "{users} e {lastUser} hanno **abbandonato il canale**.",
"combined_system_message.left_team.one": "{firstUser} ha **abbandonato il gruppo**.",
"combined_system_message.left_team.one_you": "**ha abbandonato il gruppo**.",
"combined_system_message.left_team.one_you": "**Hai abbandonato il gruppo**.",
"combined_system_message.left_team.two": "{firstUser} e {secondUser} hanno **abbandonato il gruppo**.",
"combined_system_message.removed_from_channel.many_expanded": "{users} e {lastUser} sono stati **rimossi dal canale**.",
"combined_system_message.removed_from_channel.one": "{firstUser} è stato **rimosso dal canale**.",
@@ -70,7 +70,7 @@
"combined_system_message.you": "Tu",
"create_comment.addComment": "Aggiungi un commento...",
"create_post.deactivated": "Stai visualizzando un canale archiviato con un utente disattivato.",
"create_post.write": "Write to {channelDisplayName}",
"create_post.write": "Scrive su {channelDisplayName}",
"edit_post.editPost": "Modifica post...",
"edit_post.save": "Salva",
"error.team_not_found.title": "Gruppo Non Trovato",
@@ -80,11 +80,10 @@
"integrations.add": "Aggiungi",
"intro_messages.anyMember": " Tutti i membri possono entrare e leggere questo canale.",
"intro_messages.beginning": "Inizio di {name}",
"intro_messages.channel": "canale",
"intro_messages.creator": "Questo è l'inizio di {name} {type}, creato il {date} da {creator}.",
"intro_messages.group": "canale privato",
"intro_messages.creator": "Questo è l'inizio di {name}, creato da {creator} il {date}.",
"intro_messages.creatorPrivate": "Questo è l'inizio della chat privata {name}, creato da {creator} il {date}.",
"intro_messages.group_message": "Questo è l'inizio della tua conversazione privata con questi colleghi. Messaggi privati e file condivisi qui non sono accessibili ad altre persone.",
"intro_messages.noCreator": "Questo è l'inizio di {name} {type}, creato il {date}.",
"intro_messages.noCreator": "Questo è l'inizio di della chat {name}, creata il {date}.",
"intro_messages.onlyInvited": " Invita solo i membri che possono vedere questo canale privato.",
"last_users_message.added_to_channel.type": "sono stati **aggiunti al canale** da {actor}.",
"last_users_message.added_to_team.type": "sono stati **aggiunti al canale** da {actor}.",
@@ -223,6 +222,7 @@
"mobile.drawer.teamsTitle": "Gruppi",
"mobile.edit_channel": "Salva",
"mobile.edit_post.title": "Modifica Messaggio",
"mobile.edit_profile.remove_profile_photo": "Cancella foto",
"mobile.emoji_picker.activity": "ATTIVITA'",
"mobile.emoji_picker.custom": "PERSONALIZZATO",
"mobile.emoji_picker.flags": "BANDIERE",
@@ -251,6 +251,7 @@
"mobile.file_upload.camera_video": "Gira video",
"mobile.file_upload.library": "Galleria Fotografica",
"mobile.file_upload.max_warning": "Numero massimo di file caricabili limitato a 5.",
"mobile.file_upload.unsupportedMimeType": "Solo i file con i seguenti tipi MIME possono essere caricati:\n{mimeTypes}",
"mobile.file_upload.video": "Libreria video",
"mobile.flagged_posts.empty_description": "Contrassegnare i messaggi è uno strumento per seguirli. I contrassegni sono personali e non sono visibili agli altri utenti.",
"mobile.flagged_posts.empty_title": "Nessuna pubblicazione contrassegnata",
@@ -271,7 +272,10 @@
"mobile.managed.blocked_by": "Bloccati da {vendor}",
"mobile.managed.exit": "Esci",
"mobile.managed.jailbreak": "Dispositivi con jailbreak considerati non sicuri da {vendor}, per favore uscire dall'app.",
"mobile.managed.not_secured.android": "Questo dispositivo deve essere protetto con un blocco schermo per utilizzare Mattermost.",
"mobile.managed.not_secured.ios": "Questo dispositivo deve essere protetto da un codice per utilizzare Mattermost.\n\nAndare in Impostazioni > Face ID & Codice.",
"mobile.managed.secured_by": "Verificato da {vendor}",
"mobile.managed.settings": "Vai alle impostazioni",
"mobile.markdown.code.copy_code": "Copia codice",
"mobile.markdown.code.plusMoreLines": "+{count, number} oltre {count, plural, one {line} other {lines}}",
"mobile.markdown.image.too_large": "L'immagine supera la dimensione massima consentita di {maxWidth}x{maxHeight}:",
@@ -338,6 +342,7 @@
"mobile.post_info.copy_text": "Copia Testo",
"mobile.post_info.flag": "Contrassegna",
"mobile.post_info.pin": "Blocca al canale",
"mobile.post_info.reply": "Rispondi",
"mobile.post_info.unflag": "Togli contrassegno",
"mobile.post_info.unpin": "Sblocca dal canale",
"mobile.post_pre_header.flagged": "Contrassegnato",
@@ -356,6 +361,8 @@
"mobile.post.failed_title": "Impossibile inviare il messaggio",
"mobile.post.retry": "Aggiorna",
"mobile.posts_view.moreMsg": "Aggiungere più nuovi messaggi sopra",
"mobile.privacy_link": "Politica sulla Privacy",
"mobile.reaction_header.all_emojis": "Tutti",
"mobile.recent_mentions.empty_description": "I messaggi contenti il tuo nome utente e altre parole che scatenano citazioni appariranno qui.",
"mobile.recent_mentions.empty_title": "Nessuna Citazione Recente",
"mobile.rename_channel.display_name_maxLength": "Il nome del canale deve essere al massimo di {maxLength, number} caratteri",
@@ -378,6 +385,7 @@
"mobile.routes.channelInfo.createdBy": "Creato da {creator} il ",
"mobile.routes.channelInfo.delete_channel": "Archivia Canale",
"mobile.routes.channelInfo.favorite": "Preferiti",
"mobile.routes.channelInfo.groupManaged": "I membri sono gestiti dai gruppi collegati",
"mobile.routes.code": "{language} codice",
"mobile.routes.code.noLanguage": "Codice",
"mobile.routes.edit_profile": "Modifica Profilo",
@@ -432,6 +440,7 @@
"mobile.timezone_settings.automatically": "Imposta automaticamente",
"mobile.timezone_settings.manual": "Cambia fuso orario",
"mobile.timezone_settings.select": "Seleziona fuso orario",
"mobile.tos_link": "Termini di Servizio",
"mobile.user_list.deactivated": "Disattivato",
"mobile.user.settings.notifications.email.fifteenMinutes": "Ogni 15 minuti",
"mobile.video_playback.failed_description": "Si è verificato un errore durante la riproduzione del video.\n",
@@ -458,12 +467,13 @@
"password_send.link": "Se l'account esiste, verrà inviata un'email per reimpostare la password a:",
"password_send.reset": "Reimposta password",
"permalink.error.access": "Permalink appartenente a un messaggio cancellato o a un gruppo in cui non si può accedere.",
"post_body.check_for_out_of_channel_groups_mentions.message": "non sono stati avvisati di questa citazione poiché non sono nel canale. Non possono essere aggiunti al canale poiché non sono membri dei gruppi collegati. Per aggiungerli a questo canale devono essere aggiunti a uno dei gruppi collegati.",
"post_body.check_for_out_of_channel_mentions.link.and": " e ",
"post_body.check_for_out_of_channel_mentions.link.private": "aggiungerli a questo canale privato",
"post_body.check_for_out_of_channel_mentions.link.public": "aggiungerli al canale",
"post_body.check_for_out_of_channel_mentions.message_last": "? Avranno accesso a tutta la cronologia dei messaggi.",
"post_body.check_for_out_of_channel_mentions.message.multiple": "sono stati citati ma non sono nel canale. Vuoi ",
"post_body.check_for_out_of_channel_mentions.message.one": "è stato citato ma non è nel canale. Vuoi ",
"post_body.check_for_out_of_channel_mentions.message.multiple": "non sono stati avvisati di questa citazione poiché non sono nel canale. Vuoi ",
"post_body.check_for_out_of_channel_mentions.message.one": "non sono stati avvisati di questa citazione poiché non sono nel canale. Vuoi ",
"post_body.commentedOn": "Ha commentato il messaggio di {name}: ",
"post_body.deleted": "(messaggio cancellato)",
"post_info.auto_responder": "RISPOSTE AUTOMATICHE",
@@ -498,10 +508,10 @@
"status_dropdown.set_offline": "Offline",
"status_dropdown.set_online": "Online",
"status_dropdown.set_ooo": "Fuori sede",
"suggestion.mention.all": "ATTENZIONE: Questo citerà tutti nel canale",
"suggestion.mention.all": "Notifica tutti nel canale",
"suggestion.mention.channel": "Notifica tutti nel canale",
"suggestion.mention.channels": "Miei canali",
"suggestion.mention.here": "Notifica tutti gli utenti online nel canale",
"suggestion.mention.here": "Notifica tutti i membri in linea nel canale",
"suggestion.mention.members": "Membri del canale",
"suggestion.mention.morechannels": "Altri canali",
"suggestion.mention.nonmembers": "Non nel canale",
@@ -512,6 +522,7 @@
"terms_of_service.agreeButton": "Accetto",
"terms_of_service.api_error": "Impossibile completare la richiesta. Se il problema persiste contattare l'Amministrazione di Sistema.",
"user.settings.display.clockDisplay": "Visualizza orologio",
"user.settings.display.custom_theme": "Tema personalizzato",
"user.settings.display.militaryClock": "Orologio 24 ore (esempio: 16:00)",
"user.settings.display.normalClock": "Orologio 12 ore (esempio 4:00 PM)",
"user.settings.display.preferTime": "Seleziona come visualizzare l'ora.",

View File

@@ -70,7 +70,7 @@
"combined_system_message.you": "あなた",
"create_comment.addComment": "コメントを追加する...",
"create_post.deactivated": "無効化されたユーザーのいるアーカイブされたチャンネルを見ています。",
"create_post.write": "Write to {channelDisplayName}",
"create_post.write": "{channelDisplayName}へ投稿する",
"edit_post.editPost": "投稿を編集する...",
"edit_post.save": "保存する",
"error.team_not_found.title": "チームが見つかりません",
@@ -80,11 +80,10 @@
"integrations.add": "追加する",
"intro_messages.anyMember": " このチャンネルには誰でも参加して投稿を閲覧することができます。",
"intro_messages.beginning": "{name}のトップ",
"intro_messages.channel": "チャンネル",
"intro_messages.creator": "ここは{name} {type}のトップです。{date}に{creator}によって作成されました。",
"intro_messages.group": "非公開チャンネル",
"intro_messages.creator": "ここは {name} チャンネルのトップです。{date}に{creator}によって作成されました。",
"intro_messages.creatorPrivate": "ここは非公開チャンネル {name} のトップです。{date}に{creator}によって作成されました。",
"intro_messages.group_message": "チームメイトとのグループメッセージの履歴の最初です。メッセージとそこで共有されているファイルは、この領域の外のユーザーからは見ることができません。",
"intro_messages.noCreator": "ここは{name} {type}のトップ画面です。{date}に作成されました。",
"intro_messages.noCreator": "ここは {name} チャンネルのトップです。{date}に作成されました。",
"intro_messages.onlyInvited": " 招待されたメンバーだけがこの非公開チャンネルを見ることができます。",
"last_users_message.added_to_channel.type": "が {actor} によって **チャンネルに追加されました**。",
"last_users_message.added_to_team.type": "が {actor} によって **チームに追加されました**。",
@@ -145,8 +144,8 @@
"mobile.android.photos_permission_denied_title": "フォトライブラリーへのアクセスを要求しています",
"mobile.android.storage_permission_denied_description": "Androidデバイスから画像をアップロードするには権限設定を変更してください。",
"mobile.android.storage_permission_denied_title": "ファイルストレージへのアクセスを要求しています",
"mobile.android.videos_permission_denied_description": "ライブラリからビデオをアップロードするには権限設定を変更してください。",
"mobile.android.videos_permission_denied_title": "ビデオライブラリーへのアクセスを要求しています",
"mobile.android.videos_permission_denied_description": "ライブラリから動画をアップロードするには権限設定を変更してください。",
"mobile.android.videos_permission_denied_title": "動画ライブラリーへのアクセスを要求しています",
"mobile.announcement_banner.title": "アナウンス",
"mobile.authentication_error.message": "Mattermostでエラーが発生しました。新しいセッションを開始するため再度認証してください。",
"mobile.authentication_error.title": "認証エラー",
@@ -223,7 +222,8 @@
"mobile.drawer.teamsTitle": "チーム",
"mobile.edit_channel": "保存する",
"mobile.edit_post.title": "メッセージ編集中",
"mobile.emoji_picker.activity": "アクティビティ",
"mobile.edit_profile.remove_profile_photo": "画像を削除する",
"mobile.emoji_picker.activity": "アクティビティー",
"mobile.emoji_picker.custom": "カスタム",
"mobile.emoji_picker.flags": "国旗",
"mobile.emoji_picker.foods": "食べ物",
@@ -251,7 +251,8 @@
"mobile.file_upload.camera_video": "動画を撮る",
"mobile.file_upload.library": "フォトライブラリー",
"mobile.file_upload.max_warning": "最大5ファイルまでアップロードできます。",
"mobile.file_upload.video": "ビデオライブラリー",
"mobile.file_upload.unsupportedMimeType": "以下のMIMEタイプを持つファイルのみアップロードできます:\n{mimeTypes}",
"mobile.file_upload.video": "動画ライブラリー",
"mobile.flagged_posts.empty_description": "フラグはメッセージに追跡のためのマークを付ける一つの方法です。あなたのフラグは個人のもので、他のユーザーからは見えません。",
"mobile.flagged_posts.empty_title": "フラグの立てられた投稿はありません",
"mobile.help.title": "ヘルプ",
@@ -260,7 +261,7 @@
"mobile.intro_messages.default_message": "ここはチームメイトが利用登録した際に最初に見るチャンネルです - みんなが知るべき情報を投稿してください。",
"mobile.intro_messages.default_welcome": "{name}へようこそ!",
"mobile.intro_messages.DM": "{teammate}とのダイレクトメッセージの履歴の最初です。ダイレクトメッセージとそこで共有されているファイルは、この領域の外のユーザーからは見ることができません。",
"mobile.ios.photos_permission_denied_description": "写真やビデオを保存するために権限設定を変更してください。",
"mobile.ios.photos_permission_denied_description": "写真や動画を保存するために権限設定を変更してください。",
"mobile.join_channel.error": "チャンネル {displayName} に参加できませんでした。接続を確認し、もう一度試してみてください。",
"mobile.loading_channels": "チャンネルをロードしています...",
"mobile.loading_members": "メンバーをロードしています...",
@@ -271,7 +272,10 @@
"mobile.managed.blocked_by": "{vendor}によりブロックされました",
"mobile.managed.exit": "終了",
"mobile.managed.jailbreak": "Jailbrokenデバイスは{vendor}から信頼されていません。アプリを終了してください。",
"mobile.managed.not_secured.android": "このデバイスでMattermostを使うにはスクリーンロックを設定する必要があります。",
"mobile.managed.not_secured.ios": "このデバイスでMattermostを使うにはパスコードを設定する必要があります。\n\n設定 > Face IDとパスコード から設定してください。",
"mobile.managed.secured_by": "{vendor}により保護されました",
"mobile.managed.settings": "設定へ移動する",
"mobile.markdown.code.copy_code": "コードのコピー",
"mobile.markdown.code.plusMoreLines": "あと {count, number} {count, plural, one {line} other {lines}} あります",
"mobile.markdown.image.too_large": "画像が {maxWidth} x {maxHeight} の上限を超えています:",
@@ -338,6 +342,7 @@
"mobile.post_info.copy_text": "テキストをコピーする",
"mobile.post_info.flag": "フラグ",
"mobile.post_info.pin": "チャンネルにピン留めする",
"mobile.post_info.reply": "返信する",
"mobile.post_info.unflag": "フラグを消す",
"mobile.post_info.unpin": "チャンネルへのピン留めをやめる",
"mobile.post_pre_header.flagged": "フラグ済み",
@@ -356,6 +361,8 @@
"mobile.post.failed_title": "メッセージを送信できませんでした",
"mobile.post.retry": "更新",
"mobile.posts_view.moreMsg": "さらに新しいメッセージがあります",
"mobile.privacy_link": "プライバシーポリシー",
"mobile.reaction_header.all_emojis": "すべて",
"mobile.recent_mentions.empty_description": "あなたのユーザー名やあなたについての投稿となる単語が含まれた投稿は、ここに表示されます。",
"mobile.recent_mentions.empty_title": "あなたについての投稿はありません",
"mobile.rename_channel.display_name_maxLength": "チャンネル名は {maxLength, number} 文字未満でなくてはなりません",
@@ -378,6 +385,7 @@
"mobile.routes.channelInfo.createdBy": "{creator} によって作成 ",
"mobile.routes.channelInfo.delete_channel": "チャンネルをアーカイブする",
"mobile.routes.channelInfo.favorite": "お気に入り",
"mobile.routes.channelInfo.groupManaged": "メンバーはリンクされたグループにより管理されています",
"mobile.routes.code": "{language} コード",
"mobile.routes.code.noLanguage": "コード",
"mobile.routes.edit_profile": "プロフィールを編集する",
@@ -432,13 +440,14 @@
"mobile.timezone_settings.automatically": "自動検出",
"mobile.timezone_settings.manual": "タイムゾーンを変更",
"mobile.timezone_settings.select": "タイムゾーンを選択",
"mobile.tos_link": "利用規約",
"mobile.user_list.deactivated": "無効にする",
"mobile.user.settings.notifications.email.fifteenMinutes": "15 分毎",
"mobile.video_playback.failed_description": "動画を再生する際にエラーが発生しました。\n",
"mobile.video_playback.failed_title": "動画の再生に失敗しました",
"mobile.video.save_error_message": "動画ファイルを保存するには、まずダウンロードする必要があります。",
"mobile.video.save_error_title": "動画保存エラー",
"mobile.youtube_playback_error.description": "YouTubeビデオを再生する際にエラーが発生しました。\n詳細: {details}",
"mobile.youtube_playback_error.description": "YouTube動画を再生する際にエラーが発生しました。\n詳細: {details}",
"mobile.youtube_playback_error.title": "YouTube再生エラー",
"modal.manual_status.auto_responder.message_": "あなたのステータスを \"{status}\" に変更し、自動返信を無効化してもよろしいですか?",
"modal.manual_status.auto_responder.message_away": "あなたのステータスを \"離席中\" に変更し、自動返信を無効化してもよろしいですか?",
@@ -458,12 +467,13 @@
"password_send.link": "アカウントが存在する場合、パスワード初期化メールが送信されます:",
"password_send.reset": "自分のパスワードを初期化する",
"permalink.error.access": "削除されたメッセージまたはアクセス権限のないチャンネルへのパーマリンクです。",
"post_body.check_for_out_of_channel_groups_mentions.message": "彼らはチャンネルにいないため、このメンションによる通知は行われませんでした。また、彼らはリンクされたグループのメンバーではないため、チャンネルに追加することもできません。彼らをこのチャンネルに追加するには、リンクされたグループに追加しなければなりません。",
"post_body.check_for_out_of_channel_mentions.link.and": " と ",
"post_body.check_for_out_of_channel_mentions.link.private": "彼らを非公開チャンネルに追加しますか",
"post_body.check_for_out_of_channel_mentions.link.public": "彼らをチャンネルに追加しますか",
"post_body.check_for_out_of_channel_mentions.message_last": "? 彼らは全ての会話履歴にアクセスできます。",
"post_body.check_for_out_of_channel_mentions.message.multiple": "についての投稿が行われましたが、彼らはチャンネルにいません。 ",
"post_body.check_for_out_of_channel_mentions.message.one": "についての投稿が行われましたが、彼はチャンネルにいません。 ",
"post_body.check_for_out_of_channel_mentions.message.multiple": "彼らはチャンネルにいないため、このメンションによる通知は行われませんでした。以下の対処を行いますか? ",
"post_body.check_for_out_of_channel_mentions.message.one": "彼らはチャンネルにいないため、このメンションによる通知は行われませんでした。以下の対処を行いますか? ",
"post_body.commentedOn": "{name}のメッセージへのコメント: ",
"post_body.deleted": "(メッセージは削除されています)",
"post_info.auto_responder": "自動返信",
@@ -498,10 +508,10 @@
"status_dropdown.set_offline": "オフライン",
"status_dropdown.set_online": "オンライン",
"status_dropdown.set_ooo": "外出中",
"suggestion.mention.all": "注意: チャンネルの全員に対して投稿します",
"suggestion.mention.channel": "チャンネルの全員に通知します。",
"suggestion.mention.all": "このチャンネルの全員に通知",
"suggestion.mention.channel": "このチャンネルの全員に通知",
"suggestion.mention.channels": "自分のチャンネル",
"suggestion.mention.here": "チャンネルのオンラインな全員へ通知します",
"suggestion.mention.here": "このチャンネルの現在オンラインの人に通知",
"suggestion.mention.members": "チャンネルのメンバー",
"suggestion.mention.morechannels": "他のチャンネル",
"suggestion.mention.nonmembers": "チャンネルにいません",
@@ -512,6 +522,7 @@
"terms_of_service.agreeButton": "同意する",
"terms_of_service.api_error": "リクエストを完了できませんでした。問題が続くようならば、システム管理者に連絡してください。",
"user.settings.display.clockDisplay": "時計表示",
"user.settings.display.custom_theme": "カスタムテーマ",
"user.settings.display.militaryClock": "時計の24時間表示(例: 16:00)",
"user.settings.display.normalClock": "時計の12時間表示(例: 4:00 PM)",
"user.settings.display.preferTime": "時刻の表示形式を選択します。",

View File

@@ -10,7 +10,7 @@
"about.teamEditiont0": "팀 에디션",
"about.teamEditiont1": "엔터프라이즈 에디션",
"about.title": "Mattermost에 대하여",
"announcment_banner.dont_show_again": "Don't show again",
"announcment_banner.dont_show_again": "다시 보지 않기",
"api.channel.add_member.added": "{username}님이 {addedUsername}님을 채널에 초대했습니다.",
"archivedChannelMessage": "**보존 처리 된 채널**을 보고계십니다. 새 메시지를 게시 할 수 없습니다.",
"center_panel.archived.closeChannel": "채널 닫기",
@@ -70,7 +70,7 @@
"combined_system_message.you": "당신",
"create_comment.addComment": "답글 달기...",
"create_post.deactivated": "You are viewing an archived channel with a deactivated user.",
"create_post.write": "Write to {channelDisplayName}",
"create_post.write": "{channelDisplayName}에 글쓰기",
"edit_post.editPost": "글 편집하기...",
"edit_post.save": "저장",
"error.team_not_found.title": "Team Not Found",
@@ -80,11 +80,10 @@
"integrations.add": "추가",
"intro_messages.anyMember": " 모든 회원이 채널에 가입하고 글을 읽을 수 있습니다.",
"intro_messages.beginning": "{name}의 시작",
"intro_messages.channel": "채널",
"intro_messages.creator": "이것은 {date}에 {creator} 님이 작성한 {name} {type}의 시작입니다.",
"intro_messages.group": "비공개 채널",
"intro_messages.creator": "이것은 {date}에 {creator} 님이 작성한 {name} 채널의 시작입니다.",
"intro_messages.creatorPrivate": "{date}에 {name} 비공개 채널을 {creator} 님이 생성하였습니다.",
"intro_messages.group_message": "This is the start of your direct message history with this teammate. Direct messages and files shared here are not shown to people outside this area.",
"intro_messages.noCreator": "{name} {type}입니다. 만들어진 날짜: {date}.",
"intro_messages.noCreator": "이것은 {date}에 생성된 {name} 채널의 시작입니다.",
"intro_messages.onlyInvited": " 초대받은 회원만 이 비공개 그룹을 볼 수 있습니다.",
"last_users_message.added_to_channel.type": "{actor}님이 **채널에 추가했습니다**.",
"last_users_message.added_to_team.type": "{actor}님이 **팀에 추가했습니다**.",
@@ -173,28 +172,28 @@
"mobile.channel_list.members": "MEMBERS",
"mobile.channel_list.not_member": "NOT A MEMBER",
"mobile.channel_list.unreads": "읽지 않음",
"mobile.channel_members.add_members_alert": "You must select at least one member to add to the channel.",
"mobile.channel_members.add_members_alert": "채널에 추가할 최소 한명의 멤버를 선택해야합니다.",
"mobile.channel.markAsRead": "읽음으로 표시",
"mobile.client_upgrade": "Update App",
"mobile.client_upgrade.can_upgrade_subtitle": "A new version is available for download.",
"mobile.client_upgrade.can_upgrade_title": "Update Available",
"mobile.client_upgrade.close": "Update Later",
"mobile.client_upgrade.current_version": "Latest Version: {version}",
"mobile.client_upgrade.download_error.message": "An error occurred downloading the new version.",
"mobile.client_upgrade.download_error.message": "새 버전을 다운로드 하는 동안 오류가 발생했습니다.",
"mobile.client_upgrade.download_error.title": "Unable to Install Update",
"mobile.client_upgrade.latest_version": "Your Version: {version}",
"mobile.client_upgrade.listener.dismiss_button": "Dismiss",
"mobile.client_upgrade.listener.learn_more_button": "Learn More",
"mobile.client_upgrade.listener.message": "A client upgrade is available!",
"mobile.client_upgrade.listener.upgrade_button": "Upgrade",
"mobile.client_upgrade.must_upgrade_subtitle": "Please update the app to continue.",
"mobile.client_upgrade.must_upgrade_subtitle": "계속하기 위해서 앱을 업데이트 해주세요.",
"mobile.client_upgrade.must_upgrade_title": "Update Required",
"mobile.client_upgrade.no_upgrade_subtitle": "You already have the latest version.",
"mobile.client_upgrade.no_upgrade_title": "Your App Is Up to Date",
"mobile.client_upgrade.upgrade": "Update",
"mobile.client_upgrade.upgrade": "업데이트",
"mobile.commands.error_title": "Error Executing Command",
"mobile.components.error_list.dismiss_all": "Dismiss All",
"mobile.components.select_server_view.connect": "연결 되었습니다.",
"mobile.components.select_server_view.connect": "연결",
"mobile.components.select_server_view.connecting": "연결 중...",
"mobile.components.select_server_view.enterServerUrl": "Enter Server URL",
"mobile.components.select_server_view.proceed": "Proceed",
@@ -205,7 +204,7 @@
"mobile.create_post.read_only": "이 채널은 읽기 전용입니다.",
"mobile.custom_list.no_results": "결과가 없습니다.",
"mobile.display_settings.theme": "테마",
"mobile.document_preview.failed_description": "An error occurred while opening the document. Please make sure you have a {fileType} viewer installed and try again.\n",
"mobile.document_preview.failed_description": "문서를 여는 중에 에러가 발생했습니다. {fileType} 뷰어를 설치한 후에 다시 시도해 주십시오.\n",
"mobile.document_preview.failed_title": "Open Document failed",
"mobile.downloader.android_complete": "Download complete",
"mobile.downloader.android_failed": "Download failed",
@@ -215,14 +214,15 @@
"mobile.downloader.complete": "Download complete",
"mobile.downloader.disabled_description": "File downloads are disabled on this server. Please contact your System Admin for more details.\n",
"mobile.downloader.disabled_title": "Download disabled",
"mobile.downloader.downloading": "Downloading...",
"mobile.downloader.failed_description": "An error occurred while downloading the file. Please check your internet connection and try again.\n",
"mobile.downloader.downloading": "다운로드중...",
"mobile.downloader.failed_description": "파일을 다운로드 하는 중 오류가 발생했습니다. 인터넷 연결 상태를 확인하고 다시 시도해 주세요.\n",
"mobile.downloader.failed_title": "Download failed",
"mobile.downloader.image_saved": "Image Saved",
"mobile.downloader.video_saved": "Video Saved",
"mobile.drawer.teamsTitle": "서비스 약관",
"mobile.edit_channel": "저장",
"mobile.edit_post.title": "Editing Message",
"mobile.edit_profile.remove_profile_photo": "사진 삭제",
"mobile.emoji_picker.activity": "활동",
"mobile.emoji_picker.custom": "CUSTOM",
"mobile.emoji_picker.flags": "FLAGS",
@@ -235,7 +235,7 @@
"mobile.emoji_picker.symbols": "SYMBOLS",
"mobile.error_handler.button": "Relaunch",
"mobile.error_handler.description": "\nClick relaunch to open the app again. After restart, you can report the problem from the settings menu.\n",
"mobile.error_handler.title": "Unexpected error occurred",
"mobile.error_handler.title": "예상치 못한 오류가 발생했습니다.",
"mobile.extension.authentication_required": "Authentication required: Please first login using the app.",
"mobile.extension.file_error": "There was an error reading the file to be shared.\nPlease try again.",
"mobile.extension.file_limit": "Sharing is limited to a maximum of 5 files.",
@@ -251,6 +251,7 @@
"mobile.file_upload.camera_video": "Take Video",
"mobile.file_upload.library": "Photo Library",
"mobile.file_upload.max_warning": "Uploads limited to 5 files maximum.",
"mobile.file_upload.unsupportedMimeType": "Only files of the following MIME type can be uploaded:\n{mimeTypes}",
"mobile.file_upload.video": "Video Library",
"mobile.flagged_posts.empty_description": "중요한 메시지를 따로 모아 확인할 수 있습니다. 중요 메시지는 개인별로 표시되며 다른 사람이 볼 수 없습니다.",
"mobile.flagged_posts.empty_title": "중요 메세지 없음",
@@ -271,7 +272,10 @@
"mobile.managed.blocked_by": "Blocked by {vendor}",
"mobile.managed.exit": "종료",
"mobile.managed.jailbreak": "Jailbroken devices are not trusted by {vendor}, please exit the app.",
"mobile.managed.not_secured.android": "This device must be secured with a screen lock to use Mattermost.",
"mobile.managed.not_secured.ios": "This device must be secured with a passcode to use Mattermost.\n\nGo to Settings > Face ID & Passcode.",
"mobile.managed.secured_by": "Secured by {vendor}",
"mobile.managed.settings": "Go to settings",
"mobile.markdown.code.copy_code": "Copy Code",
"mobile.markdown.code.plusMoreLines": "+{count, number} more {count, plural, one {line} other {lines}}",
"mobile.markdown.image.too_large": "Image exceeds max dimensions of {maxWidth} by {maxHeight}:",
@@ -331,13 +335,14 @@
"mobile.offlineIndicator.offline": "인터넷 연결 안 됨",
"mobile.open_dm.error": "We couldn't open a direct message with {displayName}. Please check your connection and try again.",
"mobile.open_gm.error": "We couldn't open a group message with those users. Please check your connection and try again.",
"mobile.open_unknown_channel.error": "Unable to join the channel. Please reset the cache and try again.",
"mobile.open_unknown_channel.error": "채널에 접속할 수 없습니다. cache 를 리셋하고 다시 시도하세요.",
"mobile.pinned_posts.empty_description": "Pin important items by holding down on any message and selecting \"Pin to Channel\".",
"mobile.pinned_posts.empty_title": "포스트 고정",
"mobile.post_info.add_reaction": "Add Reaction",
"mobile.post_info.copy_text": "Copy Text",
"mobile.post_info.flag": "중요 지정",
"mobile.post_info.pin": "공지하기",
"mobile.post_info.reply": "답글",
"mobile.post_info.unflag": "중요 메시지 해제",
"mobile.post_info.unpin": "공지 해제하기",
"mobile.post_pre_header.flagged": "Flagged",
@@ -356,12 +361,14 @@
"mobile.post.failed_title": "Unable to send your message",
"mobile.post.retry": "Refresh",
"mobile.posts_view.moreMsg": "More New Messages Above",
"mobile.privacy_link": "Privacy Policy",
"mobile.reaction_header.all_emojis": "모두",
"mobile.recent_mentions.empty_description": "Messages containing your username and other words that trigger mentions will appear here.",
"mobile.recent_mentions.empty_title": "최근 멘션",
"mobile.rename_channel.display_name_maxLength": "Channel name must be less than {maxLength, number} characters",
"mobile.rename_channel.display_name_minLength": "Channel name must be {minLength, number} or more characters",
"mobile.rename_channel.display_name_required": "Channel name is required",
"mobile.rename_channel.name_lowercase": "Must be lowercase alphanumeric characters",
"mobile.rename_channel.name_lowercase": "알파벳 소문자로 작성 가능",
"mobile.rename_channel.name_maxLength": "URL must be less than {maxLength, number} characters",
"mobile.rename_channel.name_minLength": "URL must be {minLength, number} or more characters",
"mobile.rename_channel.name_required": "URL is required",
@@ -378,6 +385,7 @@
"mobile.routes.channelInfo.createdBy": "Created by {creator} on ",
"mobile.routes.channelInfo.delete_channel": "Archive Channel",
"mobile.routes.channelInfo.favorite": "즐겨찾기",
"mobile.routes.channelInfo.groupManaged": "Members are managed by linked groups",
"mobile.routes.code": "{language} Code",
"mobile.routes.code.noLanguage": "Code",
"mobile.routes.edit_profile": "Edit Profile",
@@ -432,6 +440,7 @@
"mobile.timezone_settings.automatically": "자동으로 설정",
"mobile.timezone_settings.manual": "타임존 변경",
"mobile.timezone_settings.select": "Select Timezone",
"mobile.tos_link": "Terms of Service",
"mobile.user_list.deactivated": "비활성화",
"mobile.user.settings.notifications.email.fifteenMinutes": "Every 15 minutes",
"mobile.video_playback.failed_description": "An error occurred while trying to play the video.\n",
@@ -453,17 +462,18 @@
"navbar.leave": "채널 떠나기",
"password_form.title": "패스워드 재설정",
"password_send.checkInbox": "이메일을 확인하세요.",
"password_send.description": "To reset your password, enter the email address you used to sign up",
"password_send.description": "패스워드 재설정을 위해 등록한 이메일을 입력하세요.",
"password_send.error": "유효한 이메일 주소를 입력하세요.",
"password_send.link": "If the account exists, a password reset email will be sent to:",
"password_send.reset": "Reset my password",
"permalink.error.access": "Permalink belongs to a deleted message or to a channel to which you do not have access.",
"post_body.check_for_out_of_channel_groups_mentions.message": "did not get notified by this mention because they are not in the channel. They cannot be added to the channel because they are not a member of the linked groups. To add them to this channel, they must be added to the linked groups.",
"post_body.check_for_out_of_channel_mentions.link.and": " and ",
"post_body.check_for_out_of_channel_mentions.link.private": "add them to this private channel",
"post_body.check_for_out_of_channel_mentions.link.private": "팀에 사용자 초대하기",
"post_body.check_for_out_of_channel_mentions.link.public": "add them to the channel",
"post_body.check_for_out_of_channel_mentions.message_last": "? They will have access to all message history.",
"post_body.check_for_out_of_channel_mentions.message.multiple": "were mentioned but they are not in the channel. Would you like to ",
"post_body.check_for_out_of_channel_mentions.message.one": "was mentioned but is not in the channel. Would you like to ",
"post_body.check_for_out_of_channel_mentions.message.multiple": "did not get notified by this mention because they are not in the channel. Would you like to ",
"post_body.check_for_out_of_channel_mentions.message.one": "did not get notified by this mention because they are not in the channel. Would you like to ",
"post_body.commentedOn": "Commented on {name}'s message: ",
"post_body.deleted": "(삭제된 메시지)",
"post_info.auto_responder": "AUTOMATIC REPLY",
@@ -498,10 +508,10 @@
"status_dropdown.set_offline": "오프라인",
"status_dropdown.set_online": "온라인",
"status_dropdown.set_ooo": "Out of Office",
"suggestion.mention.all": "CAUTION: This mentions everyone in channel",
"suggestion.mention.all": "모든 채널 회원들에게 알림을 보냅니다",
"suggestion.mention.channel": "모든 채널 회원들에게 알림을 보냅니다",
"suggestion.mention.channels": "채널 더보기",
"suggestion.mention.here": "채널에 접속 중인 회원들에게 알림을 보냅니다",
"suggestion.mention.here": "모든 채널 회원들에게 알림을 보냅니다",
"suggestion.mention.members": "채널 회원",
"suggestion.mention.morechannels": "Other Channels",
"suggestion.mention.nonmembers": "Not in Channel",
@@ -512,6 +522,7 @@
"terms_of_service.agreeButton": "I Agree",
"terms_of_service.api_error": "Unable to complete the request. If this issue persists, contact your System Administrator.",
"user.settings.display.clockDisplay": "시간 표시",
"user.settings.display.custom_theme": "커스텀 테마",
"user.settings.display.militaryClock": "24시간으로 보이기 (예: 16:00)",
"user.settings.display.normalClock": "12시간으로 보이기 (예: 4:00 PM)",
"user.settings.display.preferTime": "시간이 어떻게 표시될지 선택하세요.",
@@ -535,7 +546,7 @@
"user.settings.notifications.comments": "답글 알림",
"user.settings.notifications.email.disabled": "이메일 알림이 활성화되지 않았습니다",
"user.settings.notifications.email.everyHour": "매 시간마다",
"user.settings.notifications.email.immediately": "바로",
"user.settings.notifications.email.immediately": "즉시 ",
"user.settings.notifications.email.never": "사용 안함",
"user.settings.notifications.email.send": "알림 받기",
"user.settings.notifications.emailInfo": "이메일 알림은 멘션 혹은 개인 메세지를 받았을때, 당신이 {siteName}로부터 오프라인 상태로 60초가 지났거나 자리비움상태로 5분 이상 지났을때 알림이 갑니다.",

View File

@@ -80,9 +80,8 @@
"integrations.add": "Toevoegen",
"intro_messages.anyMember": "Ieder lid kan dit kanaal lezen en volgen.",
"intro_messages.beginning": "Begin van {name}",
"intro_messages.channel": "kanaal",
"intro_messages.creator": "Dit is de start van {name} {type}, gemaakt door {creator} op {date}.",
"intro_messages.group": "Verlaat kanaal",
"intro_messages.creatorPrivate": "Dit is de start van {name} {type}, gemaakt door {creator} op {date}.",
"intro_messages.group_message": "Dit is de start van uw privé berichten historiek met dit teamlid. Privé berichten en bestanden die hier gedeeld worden zijn niet zichtbaar voor anderen.",
"intro_messages.noCreator": "Dit is de start van {name} {type}, gemaakt op {date}.",
"intro_messages.onlyInvited": " Alleen uitgenodigde deelnemers kunnen deze privé groep bekijken.",
@@ -223,6 +222,7 @@
"mobile.drawer.teamsTitle": "Termen",
"mobile.edit_channel": "Opslaan",
"mobile.edit_post.title": "Editing Message",
"mobile.edit_profile.remove_profile_photo": "Remove Photo",
"mobile.emoji_picker.activity": "ACTIVITY",
"mobile.emoji_picker.custom": "CUSTOM",
"mobile.emoji_picker.flags": "FLAGS",
@@ -251,6 +251,7 @@
"mobile.file_upload.camera_video": "Take Video",
"mobile.file_upload.library": "Photo Library",
"mobile.file_upload.max_warning": "Uploads limited to 5 files maximum.",
"mobile.file_upload.unsupportedMimeType": "Only files of the following MIME type can be uploaded:\n{mimeTypes}",
"mobile.file_upload.video": "Video bibliotheek",
"mobile.flagged_posts.empty_description": "Flags are a way to mark messages for follow up. Your flags are personal, and cannot be seen by other users.",
"mobile.flagged_posts.empty_title": "Gemarkeerde Berichten",
@@ -271,7 +272,10 @@
"mobile.managed.blocked_by": "Blocked by {vendor}",
"mobile.managed.exit": "Bewerken",
"mobile.managed.jailbreak": "Jailbroken devices are not trusted by {vendor}, please exit the app.",
"mobile.managed.not_secured.android": "This device must be secured with a screen lock to use Mattermost.",
"mobile.managed.not_secured.ios": "This device must be secured with a passcode to use Mattermost.\n\nGo to Settings > Face ID & Passcode.",
"mobile.managed.secured_by": "Secured by {vendor}",
"mobile.managed.settings": "Go to settings",
"mobile.markdown.code.copy_code": "Copy Code",
"mobile.markdown.code.plusMoreLines": "{count} {count, plural, =0 {0 members} een {member} andere {members}}",
"mobile.markdown.image.too_large": "Image exceeds max dimensions of {maxWidth} by {maxHeight}:",
@@ -338,6 +342,7 @@
"mobile.post_info.copy_text": "Copy Text",
"mobile.post_info.flag": "Markeer",
"mobile.post_info.pin": "Pin to Channel",
"mobile.post_info.reply": "Antwoord",
"mobile.post_info.unflag": "Demarkeer",
"mobile.post_info.unpin": "Unpin from Channel",
"mobile.post_pre_header.flagged": "Flagged",
@@ -356,6 +361,8 @@
"mobile.post.failed_title": "Unable to send your message",
"mobile.post.retry": "Refresh",
"mobile.posts_view.moreMsg": "More New Messages Above",
"mobile.privacy_link": "Privacy Policy",
"mobile.reaction_header.all_emojis": "All",
"mobile.recent_mentions.empty_description": "Messages containing your username and other words that trigger mentions will appear here.",
"mobile.recent_mentions.empty_title": "Recente vermeldingen",
"mobile.rename_channel.display_name_maxLength": "Channel name must be less than {maxLength, number} characters",
@@ -378,6 +385,7 @@
"mobile.routes.channelInfo.createdBy": "Created by {creator} on ",
"mobile.routes.channelInfo.delete_channel": "Archive Channel",
"mobile.routes.channelInfo.favorite": "Favorite",
"mobile.routes.channelInfo.groupManaged": "Members are managed by linked groups",
"mobile.routes.code": "{language} Code",
"mobile.routes.code.noLanguage": "Code",
"mobile.routes.edit_profile": "Edit Profile",
@@ -432,6 +440,7 @@
"mobile.timezone_settings.automatically": "Set automatically",
"mobile.timezone_settings.manual": "Change timezone",
"mobile.timezone_settings.select": "Select Timezone",
"mobile.tos_link": "Terms of Service",
"mobile.user_list.deactivated": "Deactivated",
"mobile.user.settings.notifications.email.fifteenMinutes": "Every 15 minutes",
"mobile.video_playback.failed_description": "An error occurred while trying to play the video.\n",
@@ -458,12 +467,13 @@
"password_send.link": "If the account exists, a password reset email will be sent to:",
"password_send.reset": "Reset wachtwoord",
"permalink.error.access": "Permalink behoort toe aan een verwijderd bericht of aan een kanaal waar je geen toegang tot hebt.",
"post_body.check_for_out_of_channel_groups_mentions.message": "did not get notified by this mention because they are not in the channel. They cannot be added to the channel because they are not a member of the linked groups. To add them to this channel, they must be added to the linked groups.",
"post_body.check_for_out_of_channel_mentions.link.and": " and ",
"post_body.check_for_out_of_channel_mentions.link.private": "add them to this private channel",
"post_body.check_for_out_of_channel_mentions.link.private": "Nodig anderen uit voor dit team",
"post_body.check_for_out_of_channel_mentions.link.public": "add them to the channel",
"post_body.check_for_out_of_channel_mentions.message_last": "? They will have access to all message history.",
"post_body.check_for_out_of_channel_mentions.message.multiple": "were mentioned but they are not in the channel. Would you like to ",
"post_body.check_for_out_of_channel_mentions.message.one": "was mentioned but is not in the channel. Would you like to ",
"post_body.check_for_out_of_channel_mentions.message.multiple": "did not get notified by this mention because they are not in the channel. Would you like to ",
"post_body.check_for_out_of_channel_mentions.message.one": "did not get notified by this mention because they are not in the channel. Would you like to ",
"post_body.commentedOn": "Commented on {name}'s message: ",
"post_body.deleted": "(bericht verwijderd)",
"post_info.auto_responder": "AUTOMATIC REPLY",
@@ -498,10 +508,10 @@
"status_dropdown.set_offline": "Offline",
"status_dropdown.set_online": "Online",
"status_dropdown.set_ooo": "Out of Office",
"suggestion.mention.all": "CAUTION: This mentions everyone in channel",
"suggestion.mention.all": "Notificeer iedereen in het kanaal",
"suggestion.mention.channel": "Notificeer iedereen in het kanaal",
"suggestion.mention.channels": "Mijn Kanalen",
"suggestion.mention.here": "Notificeert iedereen in het kanaal en online",
"suggestion.mention.here": "Notificeer iedereen in het kanaal",
"suggestion.mention.members": "Kanaal Leden",
"suggestion.mention.morechannels": "Andere Kanalen",
"suggestion.mention.nonmembers": "Niet in kanaal",
@@ -512,6 +522,7 @@
"terms_of_service.agreeButton": "I Agree",
"terms_of_service.api_error": "Unable to complete the request. If this issue persists, contact your System Administrator.",
"user.settings.display.clockDisplay": "Klok weergave",
"user.settings.display.custom_theme": "Aangepast thema",
"user.settings.display.militaryClock": "24 uren klok (bijvoorbeeld: 16:00)",
"user.settings.display.normalClock": "12 uren klok (bijvoorbeeld: 4:00 PM)",
"user.settings.display.preferTime": "Selecteer hoe u de tijd wilt zien.",

View File

@@ -8,7 +8,7 @@
"about.teamEditionLearn": "Dołącz do społeczności Mattermost na ",
"about.teamEditionSt": "Cała komunikacja Twojego zespołu w jednym miejscu, z natychmiastowym przeszukiwaniem i dostępna z każdego miejsca.",
"about.teamEditiont0": "Edycja Zespołowa",
"about.teamEditiont1": "Wydanie Enterprise",
"about.teamEditiont1": "Edycja Enterprise",
"about.title": "O Mattermost",
"announcment_banner.dont_show_again": "Nie pokazuj ponownie",
"api.channel.add_member.added": "{addedUsername} został dodany do kanału przez {username}",
@@ -45,19 +45,19 @@
"combined_system_message.added_to_team.two": "{firstUser} i {secondUser} zostali **dodani do zespołu** przez {actor}.",
"combined_system_message.joined_channel.many_expanded": "{users} i {lastUser} **dołączyli do kanału**.",
"combined_system_message.joined_channel.one": "{firstUser} **dołączył do kanału**.",
"combined_system_message.joined_channel.one_you": "**dołączył do kanału**.",
"combined_system_message.joined_channel.one_you": "Ty **dołączył do kanału**.",
"combined_system_message.joined_channel.two": "{firstUser} i {secondUser} **dołączyli do kanału**.",
"combined_system_message.joined_team.many_expanded": "{users} i {lastUser} **dołączyli do zespołu**.",
"combined_system_message.joined_team.one": "{firstUser} **dołączył do zespołu**.",
"combined_system_message.joined_team.one_you": "**dołączył do zespołu**.",
"combined_system_message.joined_team.one_you": "Ty **dołączył do zespołu**.",
"combined_system_message.joined_team.two": "{firstUser} i {secondUser} **dołączyli do zespołu**.",
"combined_system_message.left_channel.many_expanded": "{users} i {lastUser} **opuścili kanał**.",
"combined_system_message.left_channel.one": "{firstUser} **opuścił kanał**.",
"combined_system_message.left_channel.one_you": "**opuścił kanał**.",
"combined_system_message.left_channel.one_you": "Ty **opuścił kanał**.",
"combined_system_message.left_channel.two": "{firstUser} i {secondUser} **opuścili kanał**.",
"combined_system_message.left_team.many_expanded": "{users} i {lastUser} **opuścili zespół**.",
"combined_system_message.left_team.one": "{firstUser} **opuścił zespół**.",
"combined_system_message.left_team.one_you": "**opuścił zespół**.",
"combined_system_message.left_team.one_you": "Ty **opuścił zespół**.",
"combined_system_message.left_team.two": "{firstUser} i {secondUser} **opuścili zespół**.",
"combined_system_message.removed_from_channel.many_expanded": "{users} i {lastUser} zostali **usunięci z kanału**.",
"combined_system_message.removed_from_channel.one": "{firstUser} został **usunięty z kanału**.",
@@ -70,7 +70,7 @@
"combined_system_message.you": "Ty",
"create_comment.addComment": "Dodaj komentarz...",
"create_post.deactivated": "Przeglądasz zarchiwizowany kanał z deaktywowanym użytkownikiem.",
"create_post.write": "Write to {channelDisplayName}",
"create_post.write": "Napisz na {channelDisplayName}",
"edit_post.editPost": "Edytuj post...",
"edit_post.save": "Zapisz",
"error.team_not_found.title": "Zespół Nie Znaleziony",
@@ -80,11 +80,10 @@
"integrations.add": "Dodaj",
"intro_messages.anyMember": " Każdy użytkownik może dołączyć i przeczytać ten kanał.",
"intro_messages.beginning": "Początek {name}",
"intro_messages.channel": "kanału",
"intro_messages.creator": "To jest początek {type} {name}, stworzonego/ej przez {creator} dnia {date}.",
"intro_messages.group": "kanał prywatny",
"intro_messages.creator": "To jest początek kanału {name}, stworzonego przez {creator} dnia {date}.",
"intro_messages.creatorPrivate": "To jest początek prywatnego kanału {name}, stworzonego przez {creator} dnia {date}.",
"intro_messages.group_message": "To jest początek historii wiadomości grupowych z tymi zespołami. Wiadomości i pliki udostępnione w tym miejscu nie są wyświetlane osobom spoza tego obszaru.",
"intro_messages.noCreator": "To początek {type} {name}, utworzony {date}.",
"intro_messages.noCreator": "To początek kanału {name}, utworzony dnia {date}.",
"intro_messages.onlyInvited": " Tylko zaproszeni użytkownicy mogą zobaczyć ten prywatny kanał.",
"last_users_message.added_to_channel.type": "zostało **dodanych do kanału** przez {actor}.",
"last_users_message.added_to_team.type": "zostało **dodanych do zespołu** przez {actor}.",
@@ -223,6 +222,7 @@
"mobile.drawer.teamsTitle": "Zespoły",
"mobile.edit_channel": "Zapisz",
"mobile.edit_post.title": "Edycja Wiadomości",
"mobile.edit_profile.remove_profile_photo": "Usuń zdjęcie",
"mobile.emoji_picker.activity": "AKTYWNOŚCI",
"mobile.emoji_picker.custom": "NIESTANDARDOWE",
"mobile.emoji_picker.flags": "FLAGI",
@@ -251,6 +251,7 @@
"mobile.file_upload.camera_video": "Wrzuć wideo",
"mobile.file_upload.library": "Biblioteka Zdjęć",
"mobile.file_upload.max_warning": "Wrzucanie jest limitowane do 5 plików maksymalnie.",
"mobile.file_upload.unsupportedMimeType": "Można przesyłać tylko pliki następującego typu MIME: \n{mimeTypes}",
"mobile.file_upload.video": "Biblioteka wideo",
"mobile.flagged_posts.empty_description": "Oznaczanie wiadomości stanowi sposób ich zapisywania \"na później\". Twoje flagi są poufne i nie są widoczne dla innych użytkowników.",
"mobile.flagged_posts.empty_title": "Brak Oznaczonych Postów",
@@ -271,7 +272,10 @@
"mobile.managed.blocked_by": "Zablokowany przez {vendor}",
"mobile.managed.exit": "Wyjście",
"mobile.managed.jailbreak": "Urządzenia z Jailbreakiem nie są zaufane przez {vendor}, proszę wyjść z aplikacji.",
"mobile.managed.not_secured.android": "To urządzenie musi zostać zabezpieczone blokadą ekranu, aby użyć Mattermost.",
"mobile.managed.not_secured.ios": "To urządzenie musi zostać zabezpieczone hasłem, aby używać Mattermost.\n\nIdz do Ustawienia > Identyfikacja twarzy i hasło.",
"mobile.managed.secured_by": "Zabezpieczony przez {vendor}",
"mobile.managed.settings": "Idz do ustawień",
"mobile.markdown.code.copy_code": "Skopiuj Kod",
"mobile.markdown.code.plusMoreLines": "+{count, number} więcej {count, plural, one {line} other {lines}}",
"mobile.markdown.image.too_large": "Obrazek przekracza maksymalne rozmiary szerokości {maxWidth} oraz {maxHeight} wysokości:",
@@ -338,6 +342,7 @@
"mobile.post_info.copy_text": "Kopiuj Tekst",
"mobile.post_info.flag": "Oflaguj",
"mobile.post_info.pin": "Przypnij do kanału",
"mobile.post_info.reply": "Odpowiedz",
"mobile.post_info.unflag": "Usuń flagę",
"mobile.post_info.unpin": "Odepnij od kanału",
"mobile.post_pre_header.flagged": "Oflagowany",
@@ -356,6 +361,8 @@
"mobile.post.failed_title": "Nie można wysłać wiadomości",
"mobile.post.retry": "Odśwież",
"mobile.posts_view.moreMsg": "Więcej Nowych Wiadomości Powyżej",
"mobile.privacy_link": "Polityka prywatności",
"mobile.reaction_header.all_emojis": "Wszystko",
"mobile.recent_mentions.empty_description": "W tym miejscu pojawią się wiadomości zawierające Twoją nazwę użytkownika i inne słowa, które będą wyzwalać wzmianki.",
"mobile.recent_mentions.empty_title": "Brak Ostatnich Wzmianek",
"mobile.rename_channel.display_name_maxLength": "Nazwa kanału musi posiadać mniej niż {maxLength, number} znaków",
@@ -378,6 +385,7 @@
"mobile.routes.channelInfo.createdBy": "Utworzono przez {creator}, dnia ",
"mobile.routes.channelInfo.delete_channel": "Archiwizuj kanał",
"mobile.routes.channelInfo.favorite": "Ulubiony",
"mobile.routes.channelInfo.groupManaged": "Członkowie są zarządzani przez grupy połączone",
"mobile.routes.code": "{language} Kod",
"mobile.routes.code.noLanguage": "Kod",
"mobile.routes.edit_profile": "Edytuj Profil",
@@ -432,6 +440,7 @@
"mobile.timezone_settings.automatically": "Ustaw automatycznie",
"mobile.timezone_settings.manual": "Zmień strefę czasową",
"mobile.timezone_settings.select": "Wybierz Strefę Czasową",
"mobile.tos_link": "Warunki korzystania z usługi",
"mobile.user_list.deactivated": "Dezaktywowany",
"mobile.user.settings.notifications.email.fifteenMinutes": "Co 15 minut",
"mobile.video_playback.failed_description": "Wystąpił błąd podczas próby odtworzenia filmu. \n",
@@ -458,12 +467,13 @@
"password_send.link": "Jeśli konto istnieje, wiadomość e-mail dotycząca resetowania hasła zostanie wysłana na adres:",
"password_send.reset": "Zresetuj hasło",
"permalink.error.access": "Permalink należy do usuniętych wiadomości lub do kanału, do którego nie masz dostępu.",
"post_body.check_for_out_of_channel_groups_mentions.message": "nie zostali powiadomieni poprzez tą wzmiankę, ponieważ nie są na kanale. Nie można ich dodać do kanału, ponieważ nie są członkami grup połączonych. Aby dodać je do tego kanału, muszą zostać dodane do połączonych grup.",
"post_body.check_for_out_of_channel_mentions.link.and": " i ",
"post_body.check_for_out_of_channel_mentions.link.private": "dodaj je do tego prywatnego kanału",
"post_body.check_for_out_of_channel_mentions.link.public": "dodaj je do kanału",
"post_body.check_for_out_of_channel_mentions.message_last": "? Będą mieć dostęp do całej historii wiadomości.",
"post_body.check_for_out_of_channel_mentions.message.multiple": "zostały wspomniane, ale nie są w kanale. Czy chciałbyś ",
"post_body.check_for_out_of_channel_mentions.message.one": "został wspomniany, ale nie ma go w kanale. Czy chciałbyś ",
"post_body.check_for_out_of_channel_mentions.message.multiple": "nie zostali powiadomieni poprzez tą wzmiankę, ponieważ nie są na kanale. Czy chciałbyś ",
"post_body.check_for_out_of_channel_mentions.message.one": "nie zostali powiadomieni poprzez tą wzmiankę, ponieważ nie są na kanale. Czy chciałbyś ",
"post_body.commentedOn": "skomentował wiadomość {name}: ",
"post_body.deleted": "(wiadomość usunięta)",
"post_info.auto_responder": "AUTOMATYCZNA ODPOWIEDŹ",
@@ -498,10 +508,10 @@
"status_dropdown.set_offline": "Offline",
"status_dropdown.set_online": "Dostępny",
"status_dropdown.set_ooo": "Poza Biurem",
"suggestion.mention.all": "OSTRZEŻENIE: wspomina wszystkich na kanale",
"suggestion.mention.channel": "Powiadamia wszystkich na kanale",
"suggestion.mention.all": "Powiadamia wszystkich na tym kanale",
"suggestion.mention.channel": "Powiadamia wszystkich na tym kanale",
"suggestion.mention.channels": "Moje kanały",
"suggestion.mention.here": "Powiadamia wszystkich na kanale i on-line",
"suggestion.mention.here": "Powiadamia wszystkich obecnie dostępnych na kanale",
"suggestion.mention.members": "Członkowie kanału",
"suggestion.mention.morechannels": "Inne kanały",
"suggestion.mention.nonmembers": "Nie na kanale",
@@ -512,6 +522,7 @@
"terms_of_service.agreeButton": "Zgadzam się",
"terms_of_service.api_error": "Nie można ukończyć żądania. Jeśli ten problem będzie się powtarzał, skontaktuj się z Administratorem Systemu.",
"user.settings.display.clockDisplay": "Wyświetlanie czasu",
"user.settings.display.custom_theme": "Niestandardowy motyw",
"user.settings.display.militaryClock": "24-godzinny (przykład: 16:00)",
"user.settings.display.normalClock": "12-godzinny (przykład: 4:00 pm)",
"user.settings.display.preferTime": "Wybierz, jak wyświetlany jest czas.",

View File

@@ -45,19 +45,19 @@
"combined_system_message.added_to_team.two": "{firstUser} e {secondUser} **adicionado a equipe** por {actor}.",
"combined_system_message.joined_channel.many_expanded": "{users} e {lastUser} **entraram no canal**.",
"combined_system_message.joined_channel.one": "{firstUser} **entrou no canal**.",
"combined_system_message.joined_channel.one_you": "**entrou no canal**.",
"combined_system_message.joined_channel.one_you": "Você **entrou no canal**.",
"combined_system_message.joined_channel.two": "{firstUser} e {secondUser} **entraram no canal**.",
"combined_system_message.joined_team.many_expanded": "{users} e {lastUser} **entraram na equipe**.",
"combined_system_message.joined_team.one": "{firstUser} **entraram na equipe**.",
"combined_system_message.joined_team.one_you": "**entrou na equipe**.",
"combined_system_message.joined_team.one_you": "Você **entrou na equipe**.",
"combined_system_message.joined_team.two": "{firstUser} e {secondUser} **entraram na equipe**.",
"combined_system_message.left_channel.many_expanded": "{users} e {lastUser} **deixaram o canal**.",
"combined_system_message.left_channel.one": "{firstUser} **deixou o canal**.",
"combined_system_message.left_channel.one_you": "**deixou o canal**.",
"combined_system_message.left_channel.one_you": "Você **deixou o canal**.",
"combined_system_message.left_channel.two": "{firstUser} e {secondUser} **deixaram o canal**.",
"combined_system_message.left_team.many_expanded": "{users} e {lastUser} **deixaram a equipe**.",
"combined_system_message.left_team.one": "{firstUser} **deixou a equipe**.",
"combined_system_message.left_team.one_you": "**deixou a equipe**.",
"combined_system_message.left_team.one_you": "Você **deixou a equipe**.",
"combined_system_message.left_team.two": "{firstUser} e {secondUser} **deixaram a equipe**.",
"combined_system_message.removed_from_channel.many_expanded": "{users} e {lastUser} foram **removidos do canal**.",
"combined_system_message.removed_from_channel.one": "{firstUser} foi **removido do canal**.",
@@ -70,7 +70,7 @@
"combined_system_message.you": "Você",
"create_comment.addComment": "Adicionar um comentário...",
"create_post.deactivated": "Você está vendo um canal arquivado com um usuário inativo.",
"create_post.write": "Write to {channelDisplayName}",
"create_post.write": "Escrever para {channelDisplayName}",
"edit_post.editPost": "Editar o post...",
"edit_post.save": "Salvar",
"error.team_not_found.title": "Equipe Não Encontrado",
@@ -80,16 +80,15 @@
"integrations.add": "Adicionar",
"intro_messages.anyMember": " Qualquer membro pode participar e ler este canal.",
"intro_messages.beginning": "Início do {name}",
"intro_messages.channel": "canal",
"intro_messages.creator": "Este é o início do {name} {type}, criado por {creator} em {date}.",
"intro_messages.group": "canal privado",
"intro_messages.creator": "Este é o início do canal {name}, criado por {creator} em {date}.",
"intro_messages.creatorPrivate": "Este é o início do canal privado {name}, criado por {creator} em {date}.",
"intro_messages.group_message": "Este é o início de seu histórico de mensagens em grupo com esta equipe. Mensagens e arquivos compartilhados aqui não são mostrados para pessoas fora dessa área.",
"intro_messages.noCreator": "Este é o início do {name} {type}, criado em {date}.",
"intro_messages.noCreator": "Este é o início do canal {name}, criado em {date}.",
"intro_messages.onlyInvited": " Somente membros convidados podem ver este canal privado.",
"last_users_message.added_to_channel.type": "foram **adicionado ao canal** por {actor}.",
"last_users_message.added_to_team.type": "foram **adicionados a equipe** por {actor}.",
"last_users_message.first": "{firstUser} e ",
"last_users_message.joined_channel.type": "**entrou no canal**.",
"last_users_message.joined_channel.type": "**entraram no canal**.",
"last_users_message.joined_team.type": "**entrou na equipe**.",
"last_users_message.left_channel.type": "**deixou o canal**.",
"last_users_message.left_team.type": "**deixou a equipe**.",
@@ -223,6 +222,7 @@
"mobile.drawer.teamsTitle": "Equipes",
"mobile.edit_channel": "Salvar",
"mobile.edit_post.title": "Editando a Mensagem",
"mobile.edit_profile.remove_profile_photo": "Remover Foto",
"mobile.emoji_picker.activity": "ATIVIDADE",
"mobile.emoji_picker.custom": "PERSONALIZADO",
"mobile.emoji_picker.flags": "BANDEIRAS",
@@ -251,6 +251,7 @@
"mobile.file_upload.camera_video": "Criar Vídeo",
"mobile.file_upload.library": "Biblioteca de Fotos",
"mobile.file_upload.max_warning": "Upload limitado ao máximo de 5 arquivos.",
"mobile.file_upload.unsupportedMimeType": "Somente arquivos do seguintes tipos de MIME que podem ser enviados:\n{mimeTypes}",
"mobile.file_upload.video": "Galeria de Videos",
"mobile.flagged_posts.empty_description": "As bandeiras são uma forma de marcar as mensagens para segui-la. Suas bandeiras são pessoais, e não podem ser vistas por outros usuários.",
"mobile.flagged_posts.empty_title": "Nenhum Post Marcado",
@@ -271,7 +272,10 @@
"mobile.managed.blocked_by": "Bloqueado por {vendor}",
"mobile.managed.exit": "Editar",
"mobile.managed.jailbreak": "Os dispositivos com Jailbroken não são confiáveis para {vendor}, por favor saia do aplicativo.",
"mobile.managed.not_secured.android": "Este dispositivo deve ser protegido com um bloqueio de tela para usar o Mattermost.",
"mobile.managed.not_secured.ios": "Este dispositivo deve ser protegido por uma senha para usar o Mattermost.\n\nVá para Configurações > Face ID & Passcode.",
"mobile.managed.secured_by": "Garantido por {vendor}",
"mobile.managed.settings": "Vá para configurações",
"mobile.markdown.code.copy_code": "Copiar Código",
"mobile.markdown.code.plusMoreLines": "+{count, number} mais {count, plural, one {linha} other {linhas}}",
"mobile.markdown.image.too_large": "A imagem excede as dimensões máximas de {maxWidth} por {maxHeight}:",
@@ -338,6 +342,7 @@
"mobile.post_info.copy_text": "Copiar Texto",
"mobile.post_info.flag": "Marcar",
"mobile.post_info.pin": "Fixar no Canal",
"mobile.post_info.reply": "Responder",
"mobile.post_info.unflag": "Desmarcar",
"mobile.post_info.unpin": "Desafixar do Canal",
"mobile.post_pre_header.flagged": "Marcado",
@@ -356,6 +361,8 @@
"mobile.post.failed_title": "Não foi possível enviar sua mensagem",
"mobile.post.retry": "Atualizar",
"mobile.posts_view.moreMsg": "Acima Mais Mensagens Novas",
"mobile.privacy_link": "Política de Privacidade",
"mobile.reaction_header.all_emojis": "Todos",
"mobile.recent_mentions.empty_description": "Mensagens contendo o seu nome de usuário e outras palavras de gatilho aparecerão aqui.",
"mobile.recent_mentions.empty_title": "Sem Menções Recentes",
"mobile.rename_channel.display_name_maxLength": "Nome do canal precisa ter menos de {maxLength, number} caracteres",
@@ -378,6 +385,7 @@
"mobile.routes.channelInfo.createdBy": "Criado por {creator} em ",
"mobile.routes.channelInfo.delete_channel": "Arquivar Canal",
"mobile.routes.channelInfo.favorite": "Favorito",
"mobile.routes.channelInfo.groupManaged": "Membros são gerenciados por grupos vinculados",
"mobile.routes.code": "{language} Código",
"mobile.routes.code.noLanguage": "Código",
"mobile.routes.edit_profile": "Editar Perfil",
@@ -432,6 +440,7 @@
"mobile.timezone_settings.automatically": "Definir automaticamente",
"mobile.timezone_settings.manual": "Mudar fuso horário",
"mobile.timezone_settings.select": "Selecionar Fuso horário",
"mobile.tos_link": "Termos de Serviço",
"mobile.user_list.deactivated": "Desativado",
"mobile.user.settings.notifications.email.fifteenMinutes": "A cada 15 minutos",
"mobile.video_playback.failed_description": "Ocorreu um erro ao tentar reproduzir o video.\n",
@@ -458,12 +467,13 @@
"password_send.link": "Se a conta existir, um email de redefinição de senha será enviado para:",
"password_send.reset": "Redefinir minha senha",
"permalink.error.access": "O permalink pertence a uma mensagem deletada ou a um canal o qual você não tem acesso.",
"post_body.check_for_out_of_channel_groups_mentions.message": "não foi notificado por esta menção porque não estão no canal. Eles não podem ser adicionados ao canal porque não são membros dos grupos vinculados. Para adicioná-los a este canal, eles devem ser adicionados aos grupos vinculados.",
"post_body.check_for_out_of_channel_mentions.link.and": " e ",
"post_body.check_for_out_of_channel_mentions.link.private": "adicionar a este canal privado",
"post_body.check_for_out_of_channel_mentions.link.public": "adicionar ao canal",
"post_body.check_for_out_of_channel_mentions.message_last": "? Terão acesso a todo o histórico de mensagens.",
"post_body.check_for_out_of_channel_mentions.message.multiple": "foram mencionados mas eles não estão no canal. Você gostaria de ",
"post_body.check_for_out_of_channel_mentions.message.one": "foi mencionado mas não está no canal. Você gostaria de ",
"post_body.check_for_out_of_channel_mentions.message.multiple": "não foi notificado por esta menção porque não estão no canal. Você gostaria de ",
"post_body.check_for_out_of_channel_mentions.message.one": "não foi notificado por esta menção porque não estão no canal. Você gostaria de ",
"post_body.commentedOn": "Comentou a mensagem de {name}: ",
"post_body.deleted": "(mensagem deletada)",
"post_info.auto_responder": "RESPOSTA AUTOMÁTICA",
@@ -498,10 +508,10 @@
"status_dropdown.set_offline": "Desconectado",
"status_dropdown.set_online": "Conectado",
"status_dropdown.set_ooo": "Fora do Escritório",
"suggestion.mention.all": "ATENÇÃO: Todos no canal serão mencionados.",
"suggestion.mention.channel": "Notifica todos no canal",
"suggestion.mention.all": "Notifica a todos no canal",
"suggestion.mention.channel": "Notifica a todos no canal",
"suggestion.mention.channels": "Meus Canais",
"suggestion.mention.here": "Notifica todos os conectados ao canal",
"suggestion.mention.here": "Notifica a todos online no canal",
"suggestion.mention.members": "Membros do Canal",
"suggestion.mention.morechannels": "Outros Canais",
"suggestion.mention.nonmembers": "Não no Canal",
@@ -512,6 +522,7 @@
"terms_of_service.agreeButton": "Eu Concordo",
"terms_of_service.api_error": "Não é possível concluir o pedido. Se esse problema persistir, entre em contato com o Administrador do Sistema.",
"user.settings.display.clockDisplay": "Exibição do Relógio",
"user.settings.display.custom_theme": "Tema Personalizado",
"user.settings.display.militaryClock": "Relógio de 24 horas (exemplo: 16:00)",
"user.settings.display.normalClock": "Relógio de 12 horas (exemplo: 4:00 PM)",
"user.settings.display.preferTime": "Selecione como você prefere que a hora seja mostrada.",

View File

@@ -44,20 +44,20 @@
"combined_system_message.added_to_team.one_you": "Ai fost ** adăugat la echipa ** de {actor}.",
"combined_system_message.added_to_team.two": "{firstUser} și {secondUser} ** au adăugat echipei ** de către {actor}.",
"combined_system_message.joined_channel.many_expanded": "{users} și {lastUser} ** s-au alăturat canalului **.",
"combined_system_message.joined_channel.one": "{firstUser} ** sa alăturat canalului **.",
"combined_system_message.joined_channel.one_you": "**s-au alăturat canalului**.",
"combined_system_message.joined_channel.one": "{firstUser} **s-a alăturat canalului**.",
"combined_system_message.joined_channel.one_you": "Tu **te-ai alăturat canalului**.",
"combined_system_message.joined_channel.two": "{firstUser} și {secondUser} ** s-au alăturat canalului **.",
"combined_system_message.joined_team.many_expanded": "{users} și {lastUser} **s-au alăturat echipei**.",
"combined_system_message.joined_team.one": "{firstUser} **sa alăturat echipei**.",
"combined_system_message.joined_team.one_you": "**sa alăturat echipei**.",
"combined_system_message.joined_team.one_you": "Tu **te-ai alăturat echipei**.",
"combined_system_message.joined_team.two": "{firstUser} și {secondUser} **s-au alăturat echipei**.",
"combined_system_message.left_channel.many_expanded": "{users} și {lastUser} ** au părăsit canalul **.",
"combined_system_message.left_channel.one": "{firstUser} ** a părăsit canalul **.",
"combined_system_message.left_channel.one_you": "**a părăsit canalul**.",
"combined_system_message.left_channel.one_you": "Tu **ai părăsit canalul**.",
"combined_system_message.left_channel.two": "{firstUser} și {secondUser} ** au părăsit canalul **.",
"combined_system_message.left_team.many_expanded": "{users} și {lastUser} ** au părăsit echipa **.",
"combined_system_message.left_team.one": "{firstUser} ** a părăsit echipa **.",
"combined_system_message.left_team.one_you": "**a părăsit echipa**.",
"combined_system_message.left_team.one_you": "Tu **ai părăsit echipa**.",
"combined_system_message.left_team.two": "{firstUser} și {secondUser} ** au părăsit echipa **.",
"combined_system_message.removed_from_channel.many_expanded": "{users} și {lastUser} au fost ** eliminate din canal **.",
"combined_system_message.removed_from_channel.one": "{firstUser} a fost ** eliminat din canal **.",
@@ -70,8 +70,8 @@
"combined_system_message.you": "Tu",
"create_comment.addComment": "Adauga un comentariu...",
"create_post.deactivated": "Vizualizați un canal arhivat cu un utilizator dezactivat.",
"create_post.write": "Write to {channelDisplayName}",
"edit_post.editPost": "Editați postarea ...",
"create_post.write": "Scrieți pe {channelDisplayName}",
"edit_post.editPost": "Editați postarea...",
"edit_post.save": "Salvați",
"error.team_not_found.title": "Echipa nu a fost găsită",
"file_attachment.download": "Descarca",
@@ -80,11 +80,10 @@
"integrations.add": "Adaugă",
"intro_messages.anyMember": " Orice membru poate să se alăture și să citească în acest canal.",
"intro_messages.beginning": "Începutul {name}",
"intro_messages.channel": "canal",
"intro_messages.creator": "Acesta este începutul {name} {type}, creat de {creator} la {date}.",
"intro_messages.group": "canal privat",
"intro_messages.creator": "Acesta este începutul canalului {name}, creat de {creator} pe {date}.",
"intro_messages.creatorPrivate": "Acesta este începutul canalului privat {name}, creat de {creator} pe {date}.",
"intro_messages.group_message": "Acesta este începutul istoricului mesajelor dvs. de grup cu acești coechipieri. Mesajele și fișierele partajate aici nu sunt afișate persoanelor din afara acestei zone.",
"intro_messages.noCreator": "Acesta este începutul {name} {type}, creat la {date}.",
"intro_messages.noCreator": "Acesta este începutul canalului {name}, creat pe {date}.",
"intro_messages.onlyInvited": " Numai membrii invitați pot vedea acest canal privat.",
"last_users_message.added_to_channel.type": "au fost **adăugați la canalul** de {actor}.",
"last_users_message.added_to_team.type": "au fost **adăugați echipei** de {actor}.",
@@ -223,6 +222,7 @@
"mobile.drawer.teamsTitle": "Echipe",
"mobile.edit_channel": "Salvați",
"mobile.edit_post.title": "Editare mesaj",
"mobile.edit_profile.remove_profile_photo": "Eliminați fotografia",
"mobile.emoji_picker.activity": "ACTIVITATE",
"mobile.emoji_picker.custom": "Personalizat",
"mobile.emoji_picker.flags": "Semnalizari",
@@ -251,6 +251,7 @@
"mobile.file_upload.camera_video": "Adauga video",
"mobile.file_upload.library": "Biblioteca foto",
"mobile.file_upload.max_warning": "Încărcările sunt limitate la maxim 5 fișiere.",
"mobile.file_upload.unsupportedMimeType": "Numai fișierele din următorul tip MIME pot fi încărcate:\n{mimeTypes}",
"mobile.file_upload.video": "Biblioteca video",
"mobile.flagged_posts.empty_description": "Steagurile reprezintă o modalitate de a marca mesajele de urmat. Steagurile dvs. sunt personale și nu pot fi văzute de alți utilizatori.",
"mobile.flagged_posts.empty_title": "Nu sunt postări semnate",
@@ -262,23 +263,26 @@
"mobile.intro_messages.DM": "Acesta este începutul istoricului mesajului dvs. direct cu {teammate}. Mesajele directe și fișierele partajate aici nu sunt afișate persoanelor din afara acestei zone.",
"mobile.ios.photos_permission_denied_description": "Pentru a salva imagini și videoclipuri în bibliotecă, modificați setările de permisiune.",
"mobile.join_channel.error": "Nu am putut să ne alăturăm canalului {displayName}. Verificați conexiunea dvs. și încercați din nou.",
"mobile.loading_channels": "Se încarcă canalele ...",
"mobile.loading_members": "Încărcarea membrilor ...",
"mobile.loading_channels": "Se încarcă canalele...",
"mobile.loading_members": "Încărcarea membrilor...",
"mobile.loading_options": "Încărcare Opțiuni...",
"mobile.loading_posts": "Se încarcă mesajele ...",
"mobile.loading_posts": "Se încarcă mesajele...",
"mobile.login_options.choose_title": "Alegeți metoda dvs. de conectare",
"mobile.long_post_title": "{channelName} - Postați",
"mobile.managed.blocked_by": "Blocat de {vendor}",
"mobile.managed.exit": "Iesi",
"mobile.managed.jailbreak": "Dispozitivele jailbroken nu au încredere în {vendor}, închideți aplicația.",
"mobile.managed.not_secured.android": "Acest dispozitiv trebuie să fie asigurat cu un dispozitiv de blocare a ecranului pentru a utiliza Mattermost.",
"mobile.managed.not_secured.ios": "Acest dispozitiv trebuie să fie securizat cu un cod de acces pentru a utiliza Mattermost.\n\nAccesați Setări > ID-ul feței și codul de acces.",
"mobile.managed.secured_by": "Securizat de {vendor}",
"mobile.managed.settings": "Mergi la Setări",
"mobile.markdown.code.copy_code": "Copiați codul",
"mobile.markdown.code.plusMoreLines": "+ {count, number} mai mult {count, plural, one {line} alte {lines}}",
"mobile.markdown.code.plusMoreLines": "+{count, number} mai multe {count, plural, one {rând} other {rânduri}}",
"mobile.markdown.image.too_large": "Imaginea depășește dimensiunile maxime ale {maxWidth} de {maxHeight}:",
"mobile.markdown.link.copy_url": "Copiază URL- ul",
"mobile.markdown.link.copy_url": "Copiază URL-ul",
"mobile.mention.copy_mention": "Copiați mențiunea",
"mobile.message_length.message": "Mesajul dvs. curent este prea lung. Număr curent de caractere: {max} / {count}",
"mobile.message_length.title": "Mesaj Lungime",
"mobile.message_length.message": "Mesajul dvs. curent este prea lung. Număr curent de caractere: {max}/{count}",
"mobile.message_length.title": "Lungime Mesaj",
"mobile.more_dms.add_more": "Puteți adăuga încă {remaining, number} mai mulți utilizatori",
"mobile.more_dms.cannot_add_more": "Nu puteți adăuga mai mulți utilizatori",
"mobile.more_dms.one_more": "Puteți adăuga încă 1 utilizator",
@@ -297,7 +301,7 @@
"mobile.notification_settings_mobile.no_sound": "Nici unul",
"mobile.notification_settings_mobile.push_activity": "TRIMITE NOTIFICARI",
"mobile.notification_settings_mobile.push_activity_android": "Trimiteți notificări",
"mobile.notification_settings_mobile.push_status": "NOTIFICĂRI PRIN TRIGGER PUSH WHEN",
"mobile.notification_settings_mobile.push_status": "NOTIFICĂRI PRIN TRIGGER PUSH CĂND",
"mobile.notification_settings_mobile.push_status_android": "Declanșați notificările push atunci când",
"mobile.notification_settings_mobile.sound": "Sunet",
"mobile.notification_settings_mobile.sounds_title": "Alerta de notificare",
@@ -338,6 +342,7 @@
"mobile.post_info.copy_text": "Copiază textul",
"mobile.post_info.flag": "Steag",
"mobile.post_info.pin": "Conectați la Canal",
"mobile.post_info.reply": "Răspuns",
"mobile.post_info.unflag": "Anulați semnalarea",
"mobile.post_info.unpin": "Eliberați-vă din canal",
"mobile.post_pre_header.flagged": "Marcat",
@@ -356,6 +361,8 @@
"mobile.post.failed_title": "Imposibil de trimis mesajul",
"mobile.post.retry": "Actualizeaza",
"mobile.posts_view.moreMsg": "Mai multe mesaje noi de mai sus",
"mobile.privacy_link": "Politica de confidentialitate",
"mobile.reaction_header.all_emojis": "Toate",
"mobile.recent_mentions.empty_description": "Mesajele care conțin numele dvs. de utilizator și alte cuvinte care declanșează mențiunile vor apărea aici.",
"mobile.recent_mentions.empty_title": "Nu există mențiuni recente",
"mobile.rename_channel.display_name_maxLength": "Numele canalului trebuie să fie mai mic de {maxLength, number} characters",
@@ -368,17 +375,18 @@
"mobile.reply_post.failed": "Mesajul nu a putut fi trimis.",
"mobile.request.invalid_response": "A primit răspuns rău de la server.",
"mobile.reset_status.alert_cancel": "Anulare",
"mobile.reset_status.alert_ok": "OK",
"mobile.reset_status.alert_ok": "Bun",
"mobile.reset_status.title_ooo": "Dezactivați \"Afară din birou\"?",
"mobile.retry_message": "Mesajele refăcute au eșuat. Trageți în sus pentru a încerca din nou.",
"mobile.routes.channel_members.action": "Eliminați membrii",
"mobile.routes.channel_members.action_message": "Trebuie să selectați cel puțin un membru pentru a fi eliminat din canal.",
"mobile.routes.channel_members.action_message_confirm": "Sigur doriți să eliminați membrii selectați din canal?",
"mobile.routes.channelInfo": "Info",
"mobile.routes.channelInfo": "Informații",
"mobile.routes.channelInfo.createdBy": "Creat de {creator} pe ",
"mobile.routes.channelInfo.delete_channel": "Arhiva canalului",
"mobile.routes.channelInfo.favorite": "Favorit",
"mobile.routes.code": "Limbaj-{language}",
"mobile.routes.channelInfo.groupManaged": "Membrii sunt gestionați de grupuri legate",
"mobile.routes.code": "{language} Cod",
"mobile.routes.code.noLanguage": "Cod",
"mobile.routes.edit_profile": "Editati Profil",
"mobile.routes.login": "Conectare",
@@ -410,7 +418,7 @@
"mobile.server_upgrade.button": "OK",
"mobile.server_upgrade.description": "\nEste necesară o actualizare a serverului pentru a utiliza aplicația Mattermost. Contactați administratorul de sistem pentru detalii.\n",
"mobile.server_upgrade.title": "Este necesară actualizarea serverului",
"mobile.server_url.invalid_format": "Adresa URL trebuie să înceapă cu http: // sau https: //",
"mobile.server_url.invalid_format": "Adresa URL trebuie să înceapă cu http:// sau https://",
"mobile.session_expired": "Sesiune expirată: vă rugăm să vă autentificați pentru a continua să primiți notificări.",
"mobile.set_status.away": "Plecat",
"mobile.set_status.dnd": "Nu deranjaţi",
@@ -432,6 +440,7 @@
"mobile.timezone_settings.automatically": "Setați automat",
"mobile.timezone_settings.manual": "Schimbați fusul orar",
"mobile.timezone_settings.select": "Selectați Fusul orar",
"mobile.tos_link": "Termenii serviciului",
"mobile.user_list.deactivated": "Dezactivat",
"mobile.user.settings.notifications.email.fifteenMinutes": "La fiecare 15 minute",
"mobile.video_playback.failed_description": "A apărut o eroare în timp ce încercați să redați videoclipul.\n",
@@ -447,8 +456,8 @@
"modal.manual_status.auto_responder.message_online": "Doriți să vă schimbați statusul la \"Online\" și să dezactivați răspunsurile automate?",
"more_channels.noMore": "Nu mai există canale care să se alăture",
"more_channels.title": "Mai multe canale",
"msg_typing.areTyping": "{users} și {last} introduc ...",
"msg_typing.isTyping": "{user} introduce textul ...",
"msg_typing.areTyping": "{users} și {last} scriu...",
"msg_typing.isTyping": "{user} scrie...",
"navbar_dropdown.logout": "Deconectare",
"navbar.leave": "Părăsiți canalul",
"password_form.title": "Resetare parolă",
@@ -458,18 +467,19 @@
"password_send.link": "Dacă contul există, un e-mail de resetare a parolei va fi trimis la:",
"password_send.reset": "Resetează-mi parola",
"permalink.error.access": "Permalink aparține unui mesaj șters sau unui canal la care nu aveți acces.",
"post_body.check_for_out_of_channel_groups_mentions.message": "nu au fost notificați de această mențiune deoarece nu se află în canal. Ele nu pot fi adăugate la canal deoarece nu fac parte din grupurile conectate. Pentru a le adăuga la acest canal, acestea trebuie adăugate la grupurile conectate.",
"post_body.check_for_out_of_channel_mentions.link.and": " şi ",
"post_body.check_for_out_of_channel_mentions.link.private": "adăugați-le la acest canal privat",
"post_body.check_for_out_of_channel_mentions.link.public": "adăugați-le pe canal",
"post_body.check_for_out_of_channel_mentions.message_last": "? Ei vor avea acces la toate istoricul mesajelor.",
"post_body.check_for_out_of_channel_mentions.message.multiple": "au fost menționate, dar nu sunt în canal. Aţi dori să ",
"post_body.check_for_out_of_channel_mentions.message.one": "a fost menționat, dar nu este în canal. Aţi dori să ",
"post_body.check_for_out_of_channel_mentions.message.multiple": "nu au fost notificați de această mențiune deoarece nu se află în canal. Ați dori să ",
"post_body.check_for_out_of_channel_mentions.message.one": "nu au fost notificați de această mențiune deoarece nu se află în canal. Ați dori să ",
"post_body.commentedOn": "A comentat mesajul {name}: ",
"post_body.deleted": "(mesaj sters)",
"post_info.auto_responder": "RASPUNS AUTOMAT",
"post_info.bot": "BOT",
"post_info.del": "Șterge",
"post_info.edit": "Editeaza",
"post_info.edit": "Editează",
"post_info.message.show_less": "Afișați mai puține",
"post_info.message.show_more": "Detalii",
"post_info.system": "Sistem",
@@ -498,13 +508,13 @@
"status_dropdown.set_offline": "Deconectat",
"status_dropdown.set_online": "Conectat",
"status_dropdown.set_ooo": "In afara serviciului",
"suggestion.mention.all": "ATENȚIE: Acest lucru menționează pe toți în canal",
"suggestion.mention.channel": "Notifică pe toată lumea din canal",
"suggestion.mention.all": "Notifică pe toată lumea din acest canal",
"suggestion.mention.channel": "Notifică pe toată lumea din acest canal",
"suggestion.mention.channels": "Canalele mele",
"suggestion.mention.here": "Notifică pe toată lumea din canal și on-line",
"suggestion.mention.here": "Notifică pe toți cei de pe acest canal online",
"suggestion.mention.members": "Am putut sa ma membrii canalului",
"suggestion.mention.morechannels": "Alte canale",
"suggestion.mention.nonmembers": "~[canal]",
"suggestion.mention.nonmembers": "Nu este în canal",
"suggestion.mention.special": "MENȚIUNI SPECIALE",
"suggestion.search.direct": "Mesaje Directe",
"suggestion.search.private": "Canale private",
@@ -512,10 +522,11 @@
"terms_of_service.agreeButton": "Sunt De Acord",
"terms_of_service.api_error": "Imposibil de completat cererea. Dacă această problemă persistă, contactați administratorul de sistem.",
"user.settings.display.clockDisplay": "Afișajul ceasului",
"user.settings.display.custom_theme": "Teme Personalizate",
"user.settings.display.militaryClock": "Ceas 24 de ore (exemplu: 16:00)",
"user.settings.display.normalClock": "Ceas de 12 ore (exemplu: 4:00 PM)",
"user.settings.display.preferTime": "Selectați modul în care preferați timpul afișat.",
"user.settings.general.email": "Email",
"user.settings.general.email": "E-mail",
"user.settings.general.emailCantUpdate": "Emailul trebuie actualizat folosind un client web sau o aplicație desktop.",
"user.settings.general.emailGitlabCantUpdate": "Conectarea are loc prin GitLab. E-mailul nu poate fi actualizat. Adresa de e-mail utilizată pentru notificări este {email}.",
"user.settings.general.emailGoogleCantUpdate": "Conectarea are loc prin Google. E-mailul nu poate fi actualizat. Adresa de e-mail utilizată pentru notificări este {email}.",

View File

@@ -80,9 +80,8 @@
"integrations.add": "Добавить",
"intro_messages.anyMember": " Любой участник может зайти и читать этот канал.",
"intro_messages.beginning": "Начало {name}",
"intro_messages.channel": "канал",
"intro_messages.creator": "{name} - {type}, созданный {creator} {date}",
"intro_messages.group": "приватный канал",
"intro_messages.creatorPrivate": "{name} - {type}, созданный {creator} {date}",
"intro_messages.group_message": "Начало истории групповых сообщений с участниками. Размещённые здесь сообщения и файлы не видны за пределами этой области.",
"intro_messages.noCreator": "{name} - {type}, созданный {date}",
"intro_messages.onlyInvited": " Только приглашенные пользователи могут видеть этот приватный канал.",
@@ -223,6 +222,7 @@
"mobile.drawer.teamsTitle": "Команды",
"mobile.edit_channel": "Сохранить",
"mobile.edit_post.title": "Редактирование сообщения",
"mobile.edit_profile.remove_profile_photo": "Remove Photo",
"mobile.emoji_picker.activity": "АКТИВНОСТЬ",
"mobile.emoji_picker.custom": "ПОЛЬЗОВАТЕЛЬСКИЕ",
"mobile.emoji_picker.flags": "ФЛАГИ",
@@ -251,6 +251,7 @@
"mobile.file_upload.camera_video": "Снять видео",
"mobile.file_upload.library": "Библиотека изображений",
"mobile.file_upload.max_warning": "Вы можете загрузить не более 5 файлов за раз.",
"mobile.file_upload.unsupportedMimeType": "Only files of the following MIME type can be uploaded:\n{mimeTypes}",
"mobile.file_upload.video": "Библиотека видео",
"mobile.flagged_posts.empty_description": "Флаги - один из способов, пометки сообщений для последующей деятельности. Ваши флаги не могут быть просмотрены другими пользователями.",
"mobile.flagged_posts.empty_title": "Отмеченные сообщения",
@@ -271,7 +272,10 @@
"mobile.managed.blocked_by": "Заблокирован {vendor}",
"mobile.managed.exit": "Выход",
"mobile.managed.jailbreak": "Устройства с джейлбрейком не являются доверенными {vendor}, выйдите из приложения.",
"mobile.managed.not_secured.android": "This device must be secured with a screen lock to use Mattermost.",
"mobile.managed.not_secured.ios": "This device must be secured with a passcode to use Mattermost.\n\nGo to Settings > Face ID & Passcode.",
"mobile.managed.secured_by": "Защищено {vendor}",
"mobile.managed.settings": "Go to settings",
"mobile.markdown.code.copy_code": "Скопировать код",
"mobile.markdown.code.plusMoreLines": "+{count, number} еще {count, plural, one {line} other {lines}}",
"mobile.markdown.image.too_large": "Изображение превышает максимальное разрешение {maxWidth} на {maxHeight}:",
@@ -338,6 +342,7 @@
"mobile.post_info.copy_text": "Копировать текст",
"mobile.post_info.flag": "Отметить",
"mobile.post_info.pin": "Прикрепить сообщение",
"mobile.post_info.reply": "Ответить",
"mobile.post_info.unflag": "Снять отметку",
"mobile.post_info.unpin": "Открепить сообщение",
"mobile.post_pre_header.flagged": "Отмеченные",
@@ -356,6 +361,8 @@
"mobile.post.failed_title": "Не удалось отправить сообщение",
"mobile.post.retry": "Обновить",
"mobile.posts_view.moreMsg": "Загрузить еще сообщения",
"mobile.privacy_link": "Политика Конфиденциальности",
"mobile.reaction_header.all_emojis": "All",
"mobile.recent_mentions.empty_description": "Здесь будут сообщения, которые содержат ваше имя или другие слова, отмеченные для уведомлений.",
"mobile.recent_mentions.empty_title": "Недавние упоминания",
"mobile.rename_channel.display_name_maxLength": "Название канала не должно превышать {maxLength, number} символов",
@@ -378,6 +385,7 @@
"mobile.routes.channelInfo.createdBy": "Создан {creator} в ",
"mobile.routes.channelInfo.delete_channel": "Архивировать канал",
"mobile.routes.channelInfo.favorite": "Избранные",
"mobile.routes.channelInfo.groupManaged": "Members are managed by linked groups",
"mobile.routes.code": "{language} код",
"mobile.routes.code.noLanguage": "Код",
"mobile.routes.edit_profile": "Изменить профиль",
@@ -432,6 +440,7 @@
"mobile.timezone_settings.automatically": "Выбрать автоматически",
"mobile.timezone_settings.manual": "Сменить часовой пояс",
"mobile.timezone_settings.select": "Выберите часовой пояс",
"mobile.tos_link": "Условия использования",
"mobile.user_list.deactivated": "Отключены",
"mobile.user.settings.notifications.email.fifteenMinutes": "Каждые 15 минут",
"mobile.video_playback.failed_description": "Возникла ошибка проигрывания видео.\n",
@@ -458,12 +467,13 @@
"password_send.link": "Если акаунт с таким email существует, ты вы получите письмо со ссылкой для сброса пароля на:",
"password_send.reset": "Сбросить пароль",
"permalink.error.access": "Постоянная ссылка принадлежит к удалённому сообщению или каналу, к которому у вас нет доступа.",
"post_body.check_for_out_of_channel_groups_mentions.message": "did not get notified by this mention because they are not in the channel. They cannot be added to the channel because they are not a member of the linked groups. To add them to this channel, they must be added to the linked groups.",
"post_body.check_for_out_of_channel_mentions.link.and": " и ",
"post_body.check_for_out_of_channel_mentions.link.private": "добавить пользователей в приватный канал",
"post_body.check_for_out_of_channel_mentions.link.public": "добавить на канал",
"post_body.check_for_out_of_channel_mentions.message_last": "? Они увидят всю предыдущую историю сообщений.",
"post_body.check_for_out_of_channel_mentions.message.multiple": "были упомянуты, но они не на канале. Не хотите ли вы ",
"post_body.check_for_out_of_channel_mentions.message.one": "упомянули, но пользователь не на канале. Не хотите ли вы ",
"post_body.check_for_out_of_channel_mentions.message.multiple": "did not get notified by this mention because they are not in the channel. Would you like to ",
"post_body.check_for_out_of_channel_mentions.message.one": "did not get notified by this mention because they are not in the channel. Would you like to ",
"post_body.commentedOn": "Commented on {name}'s message: ",
"post_body.deleted": "(сообщение удалено)",
"post_info.auto_responder": "Автоматический Ответ",
@@ -498,10 +508,10 @@
"status_dropdown.set_offline": "Не в сети",
"status_dropdown.set_online": "В сети",
"status_dropdown.set_ooo": "Не на работе",
"suggestion.mention.all": "ВНИМАНИЕ: это приведёт к уведомлению всех в этом канале",
"suggestion.mention.all": "Уведомляет всех на канале",
"suggestion.mention.channel": "Уведомляет всех на канале",
"suggestion.mention.channels": "Мои каналы",
"suggestion.mention.here": "Уведомлять всех в канале и на сайте",
"suggestion.mention.here": "Уведомляет всех на канале",
"suggestion.mention.members": "Участники канала",
"suggestion.mention.morechannels": "Другие каналы",
"suggestion.mention.nonmembers": "Не в канале",
@@ -512,6 +522,7 @@
"terms_of_service.agreeButton": "Я согласен",
"terms_of_service.api_error": "Не могу выполнить запрос. Если ошибка не исчезнет, обратитесь к своему системному администратору.",
"user.settings.display.clockDisplay": "Отображение времени",
"user.settings.display.custom_theme": "Пользовательская тема",
"user.settings.display.militaryClock": "24-часовой формат (пример: 16:00)",
"user.settings.display.normalClock": "12-часовой формат (пример: 4:00 PM)",
"user.settings.display.preferTime": "Выберите предпочитаемый формат времени.",

View File

@@ -1,14 +1,14 @@
{
"about.date": "Yapım Tarihi:",
"about.enterpriseEditione1": "Kurumsal Sürüm",
"about.enterpriseEditionLearn": "Enterprise Sürüm hakkında şuradan ayrıntılı bilgi alabilirsiniz:",
"about.enterpriseEditionLearn": "Enterprise Sürümü hakkında ayrıntılı bilgiyi şuradan alabilirsiniz:",
"about.enterpriseEditionSt": "Güvenlik duvarının arkasından modern iletişim.",
"about.hash": "Yapım Karması:",
"about.hashee": "Kurumsal Yapım Karması:",
"about.teamEditionLearn": "Mattermost topluluğuna katılın: ",
"about.teamEditionSt": "Tüm takım iletişimi tek bir yerde, anında aranabilir ve her yerden erişilebilir.",
"about.teamEditiont0": "Team Sürümü",
"about.teamEditiont1": "Enterprise Sürüm",
"about.teamEditiont1": "Enterprise Sürümü",
"about.title": "Mattermost Hakkında",
"announcment_banner.dont_show_again": "Yeniden görüntülenmesin",
"api.channel.add_member.added": "{addedUsername} kullanıcısı {username} tarafından kanala eklendi.",
@@ -45,19 +45,19 @@
"combined_system_message.added_to_team.two": "{firstUser} ve {secondUser} {actor} tarafından **takıma eklendi**.",
"combined_system_message.joined_channel.many_expanded": "{users} ve {lastUser} **kanala katıldı**.",
"combined_system_message.joined_channel.one": "{firstUser} **kanala katıldı**.",
"combined_system_message.joined_channel.one_you": "**kanala katıldı**.",
"combined_system_message.joined_channel.one_you": "**Kanala katıldınız**.",
"combined_system_message.joined_channel.two": "{firstUser} ve {secondUser} **kanala katıldı**.",
"combined_system_message.joined_team.many_expanded": "{users} ve {lastUser} **takıma katıldı**.",
"combined_system_message.joined_team.one": "{firstUser} **takıma katıldı**.",
"combined_system_message.joined_team.one_you": "**takıma katıldı**.",
"combined_system_message.joined_team.one_you": "**Takıma katıldınız**.",
"combined_system_message.joined_team.two": "{firstUser} ve {secondUser} **takıma katıldı**.",
"combined_system_message.left_channel.many_expanded": "{users} ve {lastUser} **kanaldan ayrıldı**.",
"combined_system_message.left_channel.one": "{firstUser} **kanaldan ayrıldı**.",
"combined_system_message.left_channel.one_you": "**kanaldan ayrıldı**.",
"combined_system_message.left_channel.one_you": "**Kanaldan ayrıldınız**.",
"combined_system_message.left_channel.two": "{firstUser} ve {secondUser} **kanaldan ayrıldı**.",
"combined_system_message.left_team.many_expanded": "{users} ve {lastUser} **takımdan ayrıldı**.",
"combined_system_message.left_team.one": "{firstUser} **takımdan ayrıldı**.",
"combined_system_message.left_team.one_you": "**takımdan ayrıldı**.",
"combined_system_message.left_team.one_you": "**Takımdan ayrıldınız**.",
"combined_system_message.left_team.two": "{firstUser} ve {secondUser} **takımdan ayrıldı**.",
"combined_system_message.removed_from_channel.many_expanded": "{users} ve {lastUser} **kanaldan çıkarıldı**.",
"combined_system_message.removed_from_channel.one": "{firstUser} **kanaldan çıkarıldı**.",
@@ -70,7 +70,7 @@
"combined_system_message.you": "Siz",
"create_comment.addComment": "Yorum yazın...",
"create_post.deactivated": "Devre dışı bırakılmış bir kullanıcı ile arşivlenmiş bir kanala bakıyorsunuz.",
"create_post.write": "Write to {channelDisplayName}",
"create_post.write": "{channelDisplayName} kanalına yazın",
"edit_post.editPost": "İletiyi düzenle...",
"edit_post.save": "Kaydet",
"error.team_not_found.title": "Takım Bulunamadı",
@@ -80,11 +80,10 @@
"integrations.add": "Ekle",
"intro_messages.anyMember": " Tüm üyeler bu kanala üye olup iletileri okuyabilir.",
"intro_messages.beginning": "{name} başlangıcı",
"intro_messages.channel": "kanal",
"intro_messages.creator": "{name} {type} başlangıcı, oluşturan: {creator} tarih: {date}.",
"intro_messages.group": "özel kanal",
"intro_messages.creator": "{name} kanalı, {creator} tarafından {date} tarihinde başlatılmış.",
"intro_messages.creatorPrivate": "{name} özel kanalı, {creator} tarafından {date} tarihinde başlatılmış.",
"intro_messages.group_message": "Bu takım arkadaşlarınız ile doğrudan ileti geçmişinizin başlangıcı. Bu bölüm dışındaki kişiler burada paylaşılan doğrudan ileti ve dosyaları göremez.",
"intro_messages.noCreator": "{name} {type} başlangıcı, oluşturulma tarihi: {date}.",
"intro_messages.noCreator": "{name} kanalı, {date} tarihinde başlatılmış.",
"intro_messages.onlyInvited": " Bu özel kanalı yalnız çağrılmış üyeler görüntüleyebilir.",
"last_users_message.added_to_channel.type": "{actor} tarafından **kanala eklendiniz**.",
"last_users_message.added_to_team.type": "{actor} tarafından **takıma eklendiniz**.",
@@ -223,6 +222,7 @@
"mobile.drawer.teamsTitle": "Takımlar",
"mobile.edit_channel": "Kaydet",
"mobile.edit_post.title": "İleti Düzenleniyor",
"mobile.edit_profile.remove_profile_photo": "Fotoğrafı Kaldır",
"mobile.emoji_picker.activity": "ETKİNLİK",
"mobile.emoji_picker.custom": "ÖZEL",
"mobile.emoji_picker.flags": "İŞARETLER",
@@ -251,6 +251,7 @@
"mobile.file_upload.camera_video": "Görüntü Kaydet",
"mobile.file_upload.library": "Fotoğraf Kitaplığı",
"mobile.file_upload.max_warning": "En fazla 5 dosya yüklenebilir.",
"mobile.file_upload.unsupportedMimeType": "Yalnız belirtilen MIME türündeki dosyalar yüklenebilir:\n{mimeTypes}",
"mobile.file_upload.video": "Görüntü Kitaplığı",
"mobile.flagged_posts.empty_description": "İşaretler iletileri izlemek için kullanılır. İşaretleriniz kişiseldir ve diğer kullanıcılar tarafından görülemez.",
"mobile.flagged_posts.empty_title": "Henüz İşaretlenmiş Bir İleti Yok",
@@ -271,7 +272,10 @@
"mobile.managed.blocked_by": "{vendor} tarafından engellenmiş",
"mobile.managed.exit": ık",
"mobile.managed.jailbreak": "Jailbreak uygulanmış aygıtlara {vendor} tarafından güvenilmiyor, lütfen uygulamadan çıkın.",
"mobile.managed.not_secured.android": "Bu aygıtta Mattermost kullanılabilmesi için ekran kilidi güvenliğinin etkinleştirilmesi gerekir.",
"mobile.managed.not_secured.ios": "Bu aygıtta Mattermost kullanılabilmesi için parola güvenliğinin etkinleştirilmesi gerekir.\n\nAyarlar > Yüz Tanıma ve Parola bölümünden ayarlayabilirsiniz.",
"mobile.managed.secured_by": "{vendor} tarafından korunuyor",
"mobile.managed.settings": "Ayarlara git",
"mobile.markdown.code.copy_code": "Kodu Kopyala",
"mobile.markdown.code.plusMoreLines": "+ {count, number} diğer {count, plural, one {line} other {lines}}",
"mobile.markdown.image.too_large": "Görsel {maxWidth} x {maxHeight} boyutundan büyük:",
@@ -315,7 +319,7 @@
"mobile.notification_settings.email.send": "E-POSTA BİLDİRİMLERİNİ GÖNDER",
"mobile.notification_settings.mentions_replies": "Anmalar ve Yanıtlar",
"mobile.notification_settings.mentions.channelWide": "Tüm kanal anmaları",
"mobile.notification_settings.mentions.reply_title": "Şunun için yanıt bildirimleri gönderilsin",
"mobile.notification_settings.mentions.reply_title": "Şunlar için yanıt bildirimleri gönderilsin",
"mobile.notification_settings.mentions.sensitiveName": "Büyük küçük harfe duyarlı olan adınız",
"mobile.notification_settings.mentions.sensitiveUsername": "Büyük küçük harfe duyarlı olmayan kullanıcı adınız",
"mobile.notification_settings.mobile": "Cep Telefonu",
@@ -338,6 +342,7 @@
"mobile.post_info.copy_text": "Metni Kopyala",
"mobile.post_info.flag": "İşaretle",
"mobile.post_info.pin": "Kanala Sabitle",
"mobile.post_info.reply": "Yanıtla",
"mobile.post_info.unflag": "İşareti Kaldır",
"mobile.post_info.unpin": "Kanal Sabitlemesini Kaldır",
"mobile.post_pre_header.flagged": "İşaretlenmiş",
@@ -356,6 +361,8 @@
"mobile.post.failed_title": "İletinizi gönderilemedi",
"mobile.post.retry": "Yenile",
"mobile.posts_view.moreMsg": "Yukarıdaki Diğer Yeni İletiler",
"mobile.privacy_link": "Kişisel Verilerin Gizliliği İlkesi",
"mobile.reaction_header.all_emojis": "Tümü",
"mobile.recent_mentions.empty_description": "Kullanıcı adınızı ve diğer sözcükleri içeren iletilerin tetiklediği anmalar burada görüntülenir.",
"mobile.recent_mentions.empty_title": "Yakınlarda Bir Anılma Yok",
"mobile.rename_channel.display_name_maxLength": "Kanal adı en fazla {maxLength, number} karakter uzunluğunda olmalıdır",
@@ -378,6 +385,7 @@
"mobile.routes.channelInfo.createdBy": "{creator} tarafından şu zamanda oluşturuldu ",
"mobile.routes.channelInfo.delete_channel": "Kanalı Arşivle",
"mobile.routes.channelInfo.favorite": "Beğendiklerime Ekle",
"mobile.routes.channelInfo.groupManaged": "Üyeler ilişkilendirilmiş gruplar tarafından yönetilir",
"mobile.routes.code": "{language} Kodu",
"mobile.routes.code.noLanguage": "Kod",
"mobile.routes.edit_profile": "Profili Düzenle",
@@ -432,6 +440,7 @@
"mobile.timezone_settings.automatically": "Otomatik olarak ayarlansın",
"mobile.timezone_settings.manual": "Saat dilimini değiştir",
"mobile.timezone_settings.select": "Saat Dilimini Seçin",
"mobile.tos_link": "Hizmet Koşulları",
"mobile.user_list.deactivated": "Devre Dışı",
"mobile.user.settings.notifications.email.fifteenMinutes": "15 dakikada bir",
"mobile.video_playback.failed_description": "Görüntü oynatılmaya çalışılırken bir sorun çıktı.\n",
@@ -458,12 +467,13 @@
"password_send.link": "Hesap varsa şuraya bir parola sıfırlama e-postası gönderilecek:",
"password_send.reset": "Parolamı sıfırla",
"permalink.error.access": "Kalıcı bağlantı silinmiş ya da erişiminiz olmayan bir kanaldaki bir iletiye ait.",
"post_body.check_for_out_of_channel_groups_mentions.message": "kanalda bulunmadıkları için bu anma nedeniyle bildirim gönderilmeyecek. İlişkilendirilmiş grupların üyesi olmadıklarından bu kanala eklenemezler. Onları bu kanala eklemek için ilişkilendirilmiş gruplara eklenmeleri gerekir.",
"post_body.check_for_out_of_channel_mentions.link.and": " ve ",
"post_body.check_for_out_of_channel_mentions.link.private": "bu özel kanala eklemek ister misiniz",
"post_body.check_for_out_of_channel_mentions.link.public": "kanala eklemek ister misiniz",
"post_body.check_for_out_of_channel_mentions.message_last": "? Tüm ileti geçmişini görebilirler.",
"post_body.check_for_out_of_channel_mentions.message.multiple": "anılmış ancak kanalda değiller. Onları ",
"post_body.check_for_out_of_channel_mentions.message.one": "anılmış ancak kanalda değil. Onu ",
"post_body.check_for_out_of_channel_mentions.message.multiple": "kanalda bulunmadıkları için bu anma nedeniyle bildirim gönderilmeyecek. Şunu yapmak ister misiniz ",
"post_body.check_for_out_of_channel_mentions.message.one": "kanalda bulunmadıkları için bu anma nedeniyle bildirim gönderilmeyecek. Şunu yapmak ister misiniz ",
"post_body.commentedOn": "{name} iletisine yorum yapıldı: ",
"post_body.deleted": "(ileti silindi)",
"post_info.auto_responder": "OTOMATİK YANIT",
@@ -498,10 +508,10 @@
"status_dropdown.set_offline": "Çevrimdışı",
"status_dropdown.set_online": "Çevrimiçi",
"status_dropdown.set_ooo": "Ofis Dışında",
"suggestion.mention.all": "DİKKAT: Bu işlem kanaldaki herkesi anacak",
"suggestion.mention.channel": "Kanaldaki herkese bildirilir",
"suggestion.mention.all": "Bu kanaldaki herkese bildirilir",
"suggestion.mention.channel": "Bu kanaldaki herkese bildirilir",
"suggestion.mention.channels": "Kanallarım",
"suggestion.mention.here": "Kanalda ve çevrimiçi olan herkese bildirilir",
"suggestion.mention.here": "Bu kanalda çevrimiçi olan herkese bildirilir",
"suggestion.mention.members": "Kanal Üyeleri",
"suggestion.mention.morechannels": "Diğer Kanallar",
"suggestion.mention.nonmembers": "Kanalda Değil",
@@ -512,6 +522,7 @@
"terms_of_service.agreeButton": "Onaylıyorum",
"terms_of_service.api_error": "İstek yerine getirilemedi. Sorun sürerse Sistem Yöneticiniz ile görüşün.",
"user.settings.display.clockDisplay": "Saat Görünümü",
"user.settings.display.custom_theme": "Özel Tema",
"user.settings.display.militaryClock": "24 saat (16:00 gibi)",
"user.settings.display.normalClock": "12 saat (4:00 PM gibi)",
"user.settings.display.preferTime": "Saatin nasıl görüntüleneceğini ayarlayın.",
@@ -536,11 +547,11 @@
"user.settings.notifications.email.disabled": "E-posta bildirimleri devre dışı bırakılmış",
"user.settings.notifications.email.everyHour": "Saat başı",
"user.settings.notifications.email.immediately": "Hemen",
"user.settings.notifications.email.never": "Asla",
"user.settings.notifications.email.never": "Gönderilmesin",
"user.settings.notifications.email.send": "E-posta bildirimleri gönderilsin",
"user.settings.notifications.emailInfo": "{siteName} sitesinde 5 dakikadan uzun süre uzak ya da çevrimdışı olduğunuzda anma ve doğrudan iletiler için e-posta bildirimi gönderilir.",
"user.settings.notifications.never": "Asla",
"user.settings.notifications.onlyMentions": "Yalnız anma ve doğrudan iletiler",
"user.settings.notifications.never": "Gönderilmesin",
"user.settings.notifications.onlyMentions": "Yalnız anmalar ve doğrudan iletiler için",
"user.settings.push_notification.away": "Uzakta ya da çevrimdışı",
"user.settings.push_notification.disabled": "Anında bildirimler devre dışı bırakılmış",
"user.settings.push_notification.disabled_long": "Anında bildirimler Sistem Yöneticiniz tarafından devre dışı bırakılmış.",

View File

@@ -80,9 +80,8 @@
"integrations.add": "Додати",
"intro_messages.anyMember": "Будь-який учасник може зайти і читати цей канал.",
"intro_messages.beginning": "Початок {name}",
"intro_messages.channel": "Канал",
"intro_messages.creator": "{name} {type}, створений {creator} {date}.",
"intro_messages.group": "Приватний канал ",
"intro_messages.creatorPrivate": "{name} {type}, створений {creator} {date}.",
"intro_messages.group_message": "Початок історії групових повідомлень з учасниками. Розміщені тут повідомлення і файли не видно за межами цієї області.",
"intro_messages.noCreator": "{name} - {type}, створене {date}",
"intro_messages.onlyInvited": "Тільки запрошені користувачі можуть бачити цей приватний канал.",
@@ -223,6 +222,7 @@
"mobile.drawer.teamsTitle": "Команди ",
"mobile.edit_channel": "Зберегти ",
"mobile.edit_post.title": "Редагування повідомлення",
"mobile.edit_profile.remove_profile_photo": "Remove Photo",
"mobile.emoji_picker.activity": "АКТИВНІСТЬ ",
"mobile.emoji_picker.custom": "ПОСЛУГИ",
"mobile.emoji_picker.flags": "ФЛАГИ",
@@ -251,6 +251,7 @@
"mobile.file_upload.camera_video": "Візьміть відео",
"mobile.file_upload.library": "Бібліотека фотозображень ",
"mobile.file_upload.max_warning": "Максимальна кількість завантажень - до 5 файлів.",
"mobile.file_upload.unsupportedMimeType": "Only files of the following MIME type can be uploaded:\n{mimeTypes}",
"mobile.file_upload.video": "Відео бібліотека",
"mobile.flagged_posts.empty_description": "Прапори - це спосіб позначення повідомлень для подальшого спостереження. Ваші прапори є особистими, і їх не можуть бачити інші користувачі.",
"mobile.flagged_posts.empty_title": "Немає позначених повідомлень",
@@ -271,7 +272,10 @@
"mobile.managed.blocked_by": "Заблоковано {vendor}",
"mobile.managed.exit": "Вхід",
"mobile.managed.jailbreak": "Пристрої з Jailbroken не є довіреними {vendor}, вийдіть з програми.",
"mobile.managed.not_secured.android": "This device must be secured with a screen lock to use Mattermost.",
"mobile.managed.not_secured.ios": "This device must be secured with a passcode to use Mattermost.\n\nGo to Settings > Face ID & Passcode.",
"mobile.managed.secured_by": "Захищено {vendor}",
"mobile.managed.settings": "Go to settings",
"mobile.markdown.code.copy_code": "Копія коду",
"mobile.markdown.code.plusMoreLines": "+{count, number} more {count, plural, one {line} other {lines}}",
"mobile.markdown.image.too_large": "Зображення перевищує макс. розміри {maxWidth} до {maxHeight}:",
@@ -338,6 +342,7 @@
"mobile.post_info.copy_text": "Копіювати текст",
"mobile.post_info.flag": "Відзначити ",
"mobile.post_info.pin": "Прикріпити в каналі",
"mobile.post_info.reply": "Відповідь",
"mobile.post_info.unflag": "Не позначено ",
"mobile.post_info.unpin": "Від'єднати від каналу ",
"mobile.post_pre_header.flagged": "Позначено",
@@ -356,6 +361,8 @@
"mobile.post.failed_title": "Не вдалося відправити повідомлення",
"mobile.post.retry": "Оновити",
"mobile.posts_view.moreMsg": "Більше нових повідомлень ",
"mobile.privacy_link": "Privacy Policy",
"mobile.reaction_header.all_emojis": "All",
"mobile.recent_mentions.empty_description": "Тут відображатимуться повідомлення, що містять ваше ім'я користувача та інші слова, які викликають згадування.",
"mobile.recent_mentions.empty_title": "Немає останніх згадок",
"mobile.rename_channel.display_name_maxLength": "Назва каналу має бути меншою за символи {maxLength, number}",
@@ -378,6 +385,7 @@
"mobile.routes.channelInfo.createdBy": "Створено {creator} в",
"mobile.routes.channelInfo.delete_channel": "Архів каналів ",
"mobile.routes.channelInfo.favorite": "Вибрані",
"mobile.routes.channelInfo.groupManaged": "Members are managed by linked groups",
"mobile.routes.code": "{language} код",
"mobile.routes.code.noLanguage": "Код",
"mobile.routes.edit_profile": "Редагувати профіль",
@@ -432,6 +440,7 @@
"mobile.timezone_settings.automatically": "Встановити автоматично",
"mobile.timezone_settings.manual": "Змінити часовий пояс",
"mobile.timezone_settings.select": "Виберіть часовий пояс",
"mobile.tos_link": "Умови обслуговування",
"mobile.user_list.deactivated": "Деактивувати ",
"mobile.user.settings.notifications.email.fifteenMinutes": "Кожні 15 хвилин",
"mobile.video_playback.failed_description": "Під час спроби відтворити відео сталася помилка.\n",
@@ -458,12 +467,13 @@
"password_send.link": "Якщо обліковий запис існує, електронний лист для зміни пароля буде надіслано на адресу:",
"password_send.reset": "Скинути пароль ",
"permalink.error.access": "Постійне посилання належить до видаленого повідомлення або до каналу, на який ви не маєте доступу.",
"post_body.check_for_out_of_channel_groups_mentions.message": "did not get notified by this mention because they are not in the channel. They cannot be added to the channel because they are not a member of the linked groups. To add them to this channel, they must be added to the linked groups.",
"post_body.check_for_out_of_channel_mentions.link.and": "і",
"post_body.check_for_out_of_channel_mentions.link.private": "додайте їх до цього приватного каналу",
"post_body.check_for_out_of_channel_mentions.link.public": "додати їх до каналу",
"post_body.check_for_out_of_channel_mentions.message_last": "? Вони матимуть доступ до всієї історії повідомлень.",
"post_body.check_for_out_of_channel_mentions.message.multiple": "були згадані, але вони не перебувають у каналі. Чи хотіли б Ви",
"post_body.check_for_out_of_channel_mentions.message.one": "було згадано, але не в каналі. Чи хотіли б Ви",
"post_body.check_for_out_of_channel_mentions.message.multiple": "did not get notified by this mention because they are not in the channel. Would you like to ",
"post_body.check_for_out_of_channel_mentions.message.one": "did not get notified by this mention because they are not in the channel. Would you like to ",
"post_body.commentedOn": "Прокоментував {name} повідомлення:",
"post_body.deleted": "(повідомлення видалено)",
"post_info.auto_responder": "АВТОМАТИЧНА ВІДПОВІДЬ",
@@ -498,10 +508,10 @@
"status_dropdown.set_offline": "Offline ",
"status_dropdown.set_online": "Online ",
"status_dropdown.set_ooo": "Не на робочому місці",
"suggestion.mention.all": "УВАГА: це призведе до повідомлення всіх в цьому каналі",
"suggestion.mention.all": "Повідомляє всіх у каналі",
"suggestion.mention.channel": "Повідомляє всіх у каналі",
"suggestion.mention.channels": "Мої канали",
"suggestion.mention.here": "Повідомляє всіх у каналі та в Інтернеті",
"suggestion.mention.here": "Повідомляє всіх у каналі",
"suggestion.mention.members": "Учасники каналу ",
"suggestion.mention.morechannels": "Інші канали",
"suggestion.mention.nonmembers": "Не в каналі",
@@ -512,6 +522,7 @@
"terms_of_service.agreeButton": "Я згоден",
"terms_of_service.api_error": "Не вдається завантажити умови надання послуг. Якщо ця проблема не зникне, зверніться до свого системного адміністратора.",
"user.settings.display.clockDisplay": "Дисплей годинника ",
"user.settings.display.custom_theme": "Тема користувача",
"user.settings.display.militaryClock": "24-годинний формат (приклад: 16:00)",
"user.settings.display.normalClock": "12-годинний формат (приклад: 4:00 PM)",
"user.settings.display.preferTime": "Виберіть бажаний формат часу.",

View File

@@ -45,19 +45,19 @@
"combined_system_message.added_to_team.two": "{actor} 添加了 {firstUser} 和 {secondUser} 至**此团队**。",
"combined_system_message.joined_channel.many_expanded": "{users} 以及 {lastUser} **加入了此频道**。",
"combined_system_message.joined_channel.one": "{firstUser} **加入了此频道**。",
"combined_system_message.joined_channel.one_you": "**加入了频道**。",
"combined_system_message.joined_channel.one_you": "**加入了频道**。",
"combined_system_message.joined_channel.two": "{firstUser} 和 {secondUser} **加入了此频道**。",
"combined_system_message.joined_team.many_expanded": "{users} 以及 {lastUser} **加入了此团队**。",
"combined_system_message.joined_team.one": "{firstUser} **加入了此团队**。",
"combined_system_message.joined_team.one_you": "**加入了团队**。",
"combined_system_message.joined_team.one_you": "**加入了团队**。",
"combined_system_message.joined_team.two": "{firstUser} 和 {secondUser} **加入了此团队**。",
"combined_system_message.left_channel.many_expanded": "{users} 以及 {lastUser} **离开了此频道**。",
"combined_system_message.left_channel.one": "{firstUser} **离开了此频道**。",
"combined_system_message.left_channel.one_you": "**离开了频道**。",
"combined_system_message.left_channel.one_you": "**离开了频道**。",
"combined_system_message.left_channel.two": "{firstUser} 和 {secondUser} **离开了此频道**。",
"combined_system_message.left_team.many_expanded": "{users} 以及 {lastUser} **离开了此团队**。",
"combined_system_message.left_team.one": "{firstUser} **离开了此团队**。",
"combined_system_message.left_team.one_you": "**离开了团队**。",
"combined_system_message.left_team.one_you": "**离开了团队**。",
"combined_system_message.left_team.two": "{firstUser} 以及 {secondUser} **离开了此团队**。",
"combined_system_message.removed_from_channel.many_expanded": "{users} 以及 {lastUser} **被移出此频道**。",
"combined_system_message.removed_from_channel.one": "{firstUser} **被移出此频道**。",
@@ -70,7 +70,7 @@
"combined_system_message.you": "您",
"create_comment.addComment": "添加一个评论...",
"create_post.deactivated": "您正在查看已注销用户的归档频道。",
"create_post.write": "Write to {channelDisplayName}",
"create_post.write": "写入到{channelDisplayName}",
"edit_post.editPost": "编辑信息...",
"edit_post.save": "保存",
"error.team_not_found.title": "未找到团队",
@@ -80,11 +80,10 @@
"integrations.add": "添加",
"intro_messages.anyMember": " 任何成员可以加入和查看这个频道。",
"intro_messages.beginning": "{name} 的开端",
"intro_messages.channel": "频道",
"intro_messages.creator": "这是{name}{type}的开端,由{creator}于{date}建立。",
"intro_messages.group": "私有频道",
"intro_messages.creator": "这是{name}频道的开端,由{creator}于{date}建立。",
"intro_messages.creatorPrivate": "这是{name}私有频道的开端,由{creator}建立于{date}。",
"intro_messages.group_message": "这是您与此团队成员团体记录的开端。在这里的直接消息和文件共享除了在此的人外无法看到。",
"intro_messages.noCreator": "这是{name}{type}的开端,于{date}建立。",
"intro_messages.noCreator": "这是{name}的开端,建立于{date}。",
"intro_messages.onlyInvited": "只有受邀的成员才能看到这个私有频道。",
"last_users_message.added_to_channel.type": "被 {actor} **添加至此频道**。",
"last_users_message.added_to_team.type": "被 {actor} **添加至此团队**。",
@@ -154,7 +153,7 @@
"mobile.calendar.dayNamesShort": "周日,周一,周二,周三,周四,周五,周六",
"mobile.calendar.monthNames": "一月,二月,三月,四月,五月,六月,七月,八月,九月,十月,十一月,十二月",
"mobile.calendar.monthNamesShort": "一月,二月,三月,四月,五月,六月,七月,八月,九月,十月,十一月,十二月",
"mobile.channel_drawer.search": "转条到...",
"mobile.channel_drawer.search": "转...",
"mobile.channel_info.alertMessageDeleteChannel": "您确定要归档 {term} {name}",
"mobile.channel_info.alertMessageLeaveChannel": "您确定要离开{term} {name}",
"mobile.channel_info.alertNo": "否",
@@ -223,6 +222,7 @@
"mobile.drawer.teamsTitle": "团队",
"mobile.edit_channel": "保存",
"mobile.edit_post.title": "编辑消息",
"mobile.edit_profile.remove_profile_photo": "移除照片",
"mobile.emoji_picker.activity": "活动",
"mobile.emoji_picker.custom": "自定义",
"mobile.emoji_picker.flags": "旗帜",
@@ -251,6 +251,7 @@
"mobile.file_upload.camera_video": "录像",
"mobile.file_upload.library": "照片库",
"mobile.file_upload.max_warning": "最多上传 5 个文件。",
"mobile.file_upload.unsupportedMimeType": "只有拥有以下 MIME 类型的文件可上传:\n{mimeTypes}",
"mobile.file_upload.video": "视频库",
"mobile.flagged_posts.empty_description": "标记信息以便之后更进。您的标记是个人的,不会被他人看到。",
"mobile.flagged_posts.empty_title": "无已标记的信息",
@@ -271,7 +272,10 @@
"mobile.managed.blocked_by": "被 {vendor} 封锁",
"mobile.managed.exit": "退出",
"mobile.managed.jailbreak": "越狱的设备不被 {vendor} 信任,请退出应用。",
"mobile.managed.not_secured.android": "此设备必须开启屏幕锁才能使用 Mattermost。",
"mobile.managed.not_secured.ios": "此设备必须开启密码锁才能使用 Mattermost。\n\n前往设置 > 面容 ID 与密码。",
"mobile.managed.secured_by": "被 {vendor} 安全保护",
"mobile.managed.settings": "前往设置",
"mobile.markdown.code.copy_code": "复制代码",
"mobile.markdown.code.plusMoreLines": "+{count, number} 行",
"mobile.markdown.image.too_large": "图片超过最大尺寸 {maxWidth}x{maxHeight}",
@@ -307,7 +311,7 @@
"mobile.notification_settings.auto_responder_short": "自动回复",
"mobile.notification_settings.auto_responder.default_message": "你好,我现在已离开办公室并无法回复消息。",
"mobile.notification_settings.auto_responder.enabled": "已启用",
"mobile.notification_settings.auto_responder.footer_message": "设定在私信里自动回复的消息。在公共或私有频道下的提及不会触发自动回复。开启自动回复将设您的状态离开办公室并停用邮件和推送通知。",
"mobile.notification_settings.auto_responder.footer_message": "设定在私信里自动回复的消息。在公共或私有频道下的提及不会触发自动回复。开启自动回复将设您的状态离开办公室并停用邮件和推送通知。",
"mobile.notification_settings.auto_responder.message_placeholder": "消息",
"mobile.notification_settings.auto_responder.message_title": "自定义消息",
"mobile.notification_settings.email": "电子邮件",
@@ -338,6 +342,7 @@
"mobile.post_info.copy_text": "复制文字",
"mobile.post_info.flag": "标记",
"mobile.post_info.pin": "置顶到频道",
"mobile.post_info.reply": "回复",
"mobile.post_info.unflag": "取消标记",
"mobile.post_info.unpin": "从频道取消置顶",
"mobile.post_pre_header.flagged": "已标记",
@@ -356,6 +361,8 @@
"mobile.post.failed_title": "无法发送您的消息",
"mobile.post.retry": "刷新",
"mobile.posts_view.moreMsg": "以上有更多新消息",
"mobile.privacy_link": "隐私政策",
"mobile.reaction_header.all_emojis": "全部",
"mobile.recent_mentions.empty_description": "包含您的用户名或其他触发提及的消息将会显示在此。",
"mobile.recent_mentions.empty_title": "无最近提及",
"mobile.rename_channel.display_name_maxLength": "频道名必须小于 {maxLength, number} 个字符",
@@ -378,6 +385,7 @@
"mobile.routes.channelInfo.createdBy": "{creator} 创建于 ",
"mobile.routes.channelInfo.delete_channel": "归档频道",
"mobile.routes.channelInfo.favorite": "收藏",
"mobile.routes.channelInfo.groupManaged": "成员由连动组管理",
"mobile.routes.code": "{language} 代码",
"mobile.routes.code.noLanguage": "代码",
"mobile.routes.edit_profile": "编辑个人资料",
@@ -432,6 +440,7 @@
"mobile.timezone_settings.automatically": "自动设定",
"mobile.timezone_settings.manual": "更改时区",
"mobile.timezone_settings.select": "选择时区",
"mobile.tos_link": "服务条款",
"mobile.user_list.deactivated": "已停用",
"mobile.user.settings.notifications.email.fifteenMinutes": "每 15 分钟",
"mobile.video_playback.failed_description": "尝试播放视频时发送错误。\n",
@@ -458,12 +467,13 @@
"password_send.link": "如果账户存在,密码重置邮件将会被发送到:",
"password_send.reset": "重置我的密码",
"permalink.error.access": "此永久链接指向已删除的消息或者您没有权限访问的频道。",
"post_body.check_for_out_of_channel_groups_mentions.message": "未收到此提及因为他们不在此频道。他们无法加入此频道因为他们不是连动组成员。他们必须添加到连动组才能添加到此频道。",
"post_body.check_for_out_of_channel_mentions.link.and": " 以及 ",
"post_body.check_for_out_of_channel_mentions.link.private": "添加他们到此私有频道",
"post_body.check_for_out_of_channel_mentions.link.public": "添加他们到频道",
"post_body.check_for_out_of_channel_mentions.message_last": "? 他们将可以查看所有消息历史。",
"post_body.check_for_out_of_channel_mentions.message.multiple": "被提及但他们不在频道。您是否要",
"post_body.check_for_out_of_channel_mentions.message.one": "被提及但不在频道。您是否要",
"post_body.check_for_out_of_channel_mentions.message.multiple": "未收到此提及因为他们不在频道。您要",
"post_body.check_for_out_of_channel_mentions.message.one": "未收到此提及因为他们不在频道。您要",
"post_body.commentedOn": "在{name}的消息评论了:",
"post_body.deleted": "(消息被删除)",
"post_info.auto_responder": "自动回复",
@@ -498,10 +508,10 @@
"status_dropdown.set_offline": "离线",
"status_dropdown.set_online": "在线",
"status_dropdown.set_ooo": "离开办公室",
"suggestion.mention.all": "注意:这将提及此频道所有人",
"suggestion.mention.channel": "通知每个频道",
"suggestion.mention.all": "通知此频道所有人",
"suggestion.mention.channel": "通知此频道所有人",
"suggestion.mention.channels": "我的频道",
"suggestion.mention.here": "通知所有在此频道在线的人",
"suggestion.mention.here": "通知此频道所有在线的人",
"suggestion.mention.members": "频道成员",
"suggestion.mention.morechannels": "其他频道",
"suggestion.mention.nonmembers": "不在频道中",
@@ -512,6 +522,7 @@
"terms_of_service.agreeButton": "我同意",
"terms_of_service.api_error": "无法完成请求。如果此问题持续,请联系您的系统管理员。",
"user.settings.display.clockDisplay": "时钟显示",
"user.settings.display.custom_theme": "自定义主题",
"user.settings.display.militaryClock": "24小时格式例如16:00",
"user.settings.display.normalClock": "12小时格式例如4:00 PM",
"user.settings.display.preferTime": "选择您喜欢的时间显示格式。",

View File

@@ -45,19 +45,19 @@
"combined_system_message.added_to_team.two": "{firstUser}與{secondUser}已由{actor}**加入至此團隊**。",
"combined_system_message.joined_channel.many_expanded": "{users}與{lastUser}已**加入至此頻道**。",
"combined_system_message.joined_channel.one": "{firstUser}已**加入至此頻道**。",
"combined_system_message.joined_channel.one_you": "**加入頻道**",
"combined_system_message.joined_channel.one_you": "**加入頻道**",
"combined_system_message.joined_channel.two": "{firstUser}與{secondUser}已**加入至此頻道**。",
"combined_system_message.joined_team.many_expanded": "{users}與{lastUser}已**加入至此團隊**。",
"combined_system_message.joined_team.one": "{firstUser}已**加入至此團隊**。",
"combined_system_message.joined_team.one_you": "**加入團隊**",
"combined_system_message.joined_team.one_you": "**加入團隊**",
"combined_system_message.joined_team.two": "{firstUser}與{secondUser}已**加入至此團隊**。",
"combined_system_message.left_channel.many_expanded": "{users}與{lastUser}已**離開此頻道**。",
"combined_system_message.left_channel.one": "{firstUser}已**離開此頻道**。",
"combined_system_message.left_channel.one_you": "**離開頻道**",
"combined_system_message.left_channel.one_you": "**離開頻道**",
"combined_system_message.left_channel.two": "{firstUser}與{secondUser}已**離開此頻道**。",
"combined_system_message.left_team.many_expanded": "{users}與{lastUser}已**離開此團隊**。",
"combined_system_message.left_team.one": "{firstUser}已**離開此團隊**。",
"combined_system_message.left_team.one_you": "**離開團隊**。",
"combined_system_message.left_team.one_you": "**離開團隊**。",
"combined_system_message.left_team.two": "{firstUser}與{secondUser}已**離開此團隊**。",
"combined_system_message.removed_from_channel.many_expanded": "{users}與{lastUser}已**被移出此頻道**。",
"combined_system_message.removed_from_channel.one": "{firstUser}已**被移出此頻道**。",
@@ -70,7 +70,7 @@
"combined_system_message.you": "您",
"create_comment.addComment": "新增註解...",
"create_post.deactivated": "正以被停用的使用者觀看被封存的頻道。",
"create_post.write": "Write to {channelDisplayName}",
"create_post.write": "寫給 {channelDisplayName}",
"edit_post.editPost": "修改訊息...",
"edit_post.save": "儲存",
"error.team_not_found.title": "找不到團隊",
@@ -80,11 +80,10 @@
"integrations.add": "新增",
"intro_messages.anyMember": " 任何成員可以加入並閱讀此頻道。",
"intro_messages.beginning": "{name}的開頭",
"intro_messages.channel": "頻道",
"intro_messages.creator": "這是{name}{type}的開頭,由{creator}建立於{date}。",
"intro_messages.group": "私人頻道",
"intro_messages.creator": "這是{name}頻道的開頭,由{creator}建立於{date}。",
"intro_messages.creatorPrivate": "這是{name}私人頻道的開頭,由{creator}建立於{date}。",
"intro_messages.group_message": "這是跟這些團隊成員之間群組訊息的起頭。直接訊息跟在這邊分享的檔案除了在此處以外的人都看不到。",
"intro_messages.noCreator": "這是{name}{type}的開頭,建立於{date}。",
"intro_messages.noCreator": "這是{name}頻道的開頭,建立於{date}。",
"intro_messages.onlyInvited": " 只有受邀請的成員能看見此私人頻道",
"last_users_message.added_to_channel.type": "已由{actor}**加入至此頻道**。",
"last_users_message.added_to_team.type": "已由{actor}**加入至此團隊**。",
@@ -223,6 +222,7 @@
"mobile.drawer.teamsTitle": "團隊",
"mobile.edit_channel": "儲存",
"mobile.edit_post.title": "編輯訊息",
"mobile.edit_profile.remove_profile_photo": "移除相片",
"mobile.emoji_picker.activity": "活動",
"mobile.emoji_picker.custom": "自訂",
"mobile.emoji_picker.flags": "旗幟",
@@ -251,6 +251,7 @@
"mobile.file_upload.camera_video": "錄影",
"mobile.file_upload.library": "相簿",
"mobile.file_upload.max_warning": "上傳最多 5 個檔案。",
"mobile.file_upload.unsupportedMimeType": "只能上傳下列類型的檔案:{mimeTypes}",
"mobile.file_upload.video": "媒體櫃",
"mobile.flagged_posts.empty_description": "標記是標注訊息以追蹤後續的功能。您的標記是屬於個人的,不會被其他使用者看到。",
"mobile.flagged_posts.empty_title": "被標記的訊息",
@@ -271,7 +272,10 @@
"mobile.managed.blocked_by": "被 {vendor} 阻擋",
"mobile.managed.exit": "離開",
"mobile.managed.jailbreak": "{vendor} 不信任越獄後的裝置,請關閉應用程式。",
"mobile.managed.not_secured.android": "這裝置必須要設定螢幕鎖以使用 Mattermost",
"mobile.managed.not_secured.ios": "這裝置必須要設定密碼以使用 Mattermost前往 設定 > Face ID 與密碼",
"mobile.managed.secured_by": "受到 {vendor} 保護",
"mobile.managed.settings": "前往設定",
"mobile.markdown.code.copy_code": "複製代碼",
"mobile.markdown.code.plusMoreLines": "以及其他 {count, number} 個檔案",
"mobile.markdown.image.too_large": "圖片超過最大尺寸 {maxWidth}x{maxHeight}",
@@ -338,6 +342,7 @@
"mobile.post_info.copy_text": "複製文字",
"mobile.post_info.flag": "標記",
"mobile.post_info.pin": "釘選至頻道",
"mobile.post_info.reply": "回覆",
"mobile.post_info.unflag": "取消標記",
"mobile.post_info.unpin": "解除釘選",
"mobile.post_pre_header.flagged": "已被標記",
@@ -356,6 +361,8 @@
"mobile.post.failed_title": "無法傳送訊息",
"mobile.post.retry": "重新整理",
"mobile.posts_view.moreMsg": "上面還有更多的新訊息",
"mobile.privacy_link": "隱私政策",
"mobile.reaction_header.all_emojis": "全部",
"mobile.recent_mentions.empty_description": "包含您的使用者名稱或其他觸發提及關鍵字的訊息將會顯示於此。",
"mobile.recent_mentions.empty_title": "最近提及",
"mobile.rename_channel.display_name_maxLength": "頻道名稱必須少於 {maxLength, number} 字",
@@ -378,6 +385,7 @@
"mobile.routes.channelInfo.createdBy": "由 {creator} 建立於",
"mobile.routes.channelInfo.delete_channel": "封存頻道",
"mobile.routes.channelInfo.favorite": "我的最愛",
"mobile.routes.channelInfo.groupManaged": "成員由連結群組管理",
"mobile.routes.code": "{language} 代碼",
"mobile.routes.code.noLanguage": "代碼",
"mobile.routes.edit_profile": "編輯個人資訊",
@@ -432,6 +440,7 @@
"mobile.timezone_settings.automatically": "自動設定",
"mobile.timezone_settings.manual": "更改時區",
"mobile.timezone_settings.select": "選擇時區",
"mobile.tos_link": "服務條款",
"mobile.user_list.deactivated": "停用",
"mobile.user.settings.notifications.email.fifteenMinutes": "每 15 分鐘",
"mobile.video_playback.failed_description": "嘗試播放影片時發生錯誤。",
@@ -458,12 +467,13 @@
"password_send.link": "如果帳號存在,將會寄送密碼重置郵件至:",
"password_send.reset": "重置我的密碼",
"permalink.error.access": "此永久連結通往被刪除的訊息或是您沒有觀看權限的頻道。",
"post_body.check_for_out_of_channel_groups_mentions.message": "沒有因這個提及而收到通知,因為他們不在頻道中。由於他們不是連結群組的成員,無法將其加入此頻道。如要將使用者加入此頻道,必須將他們加入連結群組。",
"post_body.check_for_out_of_channel_mentions.link.and": "與",
"post_body.check_for_out_of_channel_mentions.link.private": "將他們加進此私人頻道",
"post_body.check_for_out_of_channel_mentions.link.public": "將他們加進此頻道",
"post_body.check_for_out_of_channel_mentions.message_last": "?他們將可以瀏覽所有的訊息紀錄。",
"post_body.check_for_out_of_channel_mentions.message.multiple": "被提及但是不在頻道。請問是否要",
"post_body.check_for_out_of_channel_mentions.message.one": "被提及但是不在頻道。請問是否要",
"post_body.check_for_out_of_channel_mentions.message.multiple": "沒有因這個提及而收到通知,因為他們不在頻道。請問是否要",
"post_body.check_for_out_of_channel_mentions.message.one": "沒有因這個提及而收到通知,因為他們不在頻道。請問是否要",
"post_body.commentedOn": " 已在{name}的訊息上註記:",
"post_body.deleted": "(訊息已刪除)",
"post_info.auto_responder": "自動回覆",
@@ -498,10 +508,10 @@
"status_dropdown.set_offline": "離線",
"status_dropdown.set_online": "上線",
"status_dropdown.set_ooo": "不在辦公室",
"suggestion.mention.all": "注意:這將會提及頻道中的所有人",
"suggestion.mention.all": "通知頻道全員",
"suggestion.mention.channel": "通知頻道全員",
"suggestion.mention.channels": "我的頻道",
"suggestion.mention.here": "通知頻道所有在線的人",
"suggestion.mention.here": "通知頻道全體在線成員",
"suggestion.mention.members": "頻道成員",
"suggestion.mention.morechannels": "其他頻道",
"suggestion.mention.nonmembers": "不在頻道中",
@@ -512,6 +522,7 @@
"terms_of_service.agreeButton": "同意",
"terms_of_service.api_error": "無法完成請求。如果此問題持續發生,請聯絡系統管理員。",
"user.settings.display.clockDisplay": "顯示時間",
"user.settings.display.custom_theme": "自訂佈景主題",
"user.settings.display.militaryClock": "24 小時制(如16:00)",
"user.settings.display.normalClock": "12 小時制(如4:00 PM)",
"user.settings.display.preferTime": "選擇時間顯示方式。",

View File

@@ -3,10 +3,12 @@ fastlane_version '2.71.0'
fastlane_require 'aws-sdk-s3'
fastlane_require 'erb'
fastlane_require 'json'
fastlane_require 'pathname'
skip_docs
configured = false
is_build_pr = false
# Executes before anything else use to setup the script
before_all do |lane, options|
@@ -14,6 +16,7 @@ before_all do |lane, options|
pr = options[:pr]
UI.success("Building #{pr}")
ENV['BRANCH_TO_BUILD'] = pr
is_build_pr = true
end
# Raises an error is git is not clean
@@ -22,7 +25,7 @@ before_all do |lane, options|
end
# Block to ensure we are on the right branch
branch = ENV['BRANCH_TO_BUILD'] || 'master'
branch = ENV['BRANCH_TO_BUILD'] || ENV['GIT_BRANCH']
begin
ensure_git_branch(
branch: branch
@@ -347,6 +350,7 @@ platform :ios do
def build_ios()
app_name = ENV['APP_NAME'] || 'Mattermost Beta'
app_name_sub = app_name.gsub(" ", "_")
config_mode = ENV['BUILD_FOR_RELEASE'] == 'true' ? 'Release' : 'Debug'
method = ENV['IOS_BUILD_EXPORT_METHOD'].nil? || ENV['IOS_BUILD_EXPORT_METHOD'].empty? ? 'ad-hoc' : ENV['IOS_BUILD_EXPORT_METHOD']
@@ -357,7 +361,7 @@ platform :ios do
workspace: './ios/Mattermost.xcworkspace',
export_method: method,
skip_profile_detection: true,
output_name: "#{app_name}.ipa",
output_name: "#{app_name_sub}.ipa",
export_options: {
signingStyle: 'manual',
iCloudContainerEnvironment: 'Production'
@@ -372,8 +376,8 @@ platform :android do
lane :build do
unless configured
configure
configure_telemetry_android
end
configure_telemetry_android
update_identifiers
replace_assets
link_sentry_android
@@ -511,10 +515,13 @@ platform :android do
def move_apk_to_root
app_name = ENV['APP_NAME'] || 'Mattermost Beta'
app_name_sub = app_name.gsub(" ", "_")
new_apk_path = "#{Pathname.new(File.expand_path(File.dirname(__FILE__))).parent.to_s}/#{app_name_sub}.apk"
apk_path = lane_context[SharedValues::GRADLE_APK_OUTPUT_PATH]
unless apk_path.nil?
sh "mv #{apk_path} \"../#{app_name}.apk\""
sh "mv #{apk_path} #{new_apk_path}"
lane_context[SharedValues::GRADLE_APK_OUTPUT_PATH] = new_apk_path
end
end
@@ -584,6 +591,9 @@ def submit_to_store
apk_path = lane_context[SharedValues::GRADLE_APK_OUTPUT_PATH]
ipa_path = lane_context[SharedValues::IPA_OUTPUT_PATH]
app_name = ENV['APP_NAME'] || 'Mattermost Beta'
app_name_sub = app_name.gsub(" ", "_")
s3_region = ENV['AWS_REGION']
s3_bucket = ENV['AWS_BUCKET_NAME']
@@ -600,7 +610,7 @@ def submit_to_store
build_number = android_get_version_code(gradle_file: './android/app/build.gradle')
s3_folder = "#{ENV['AWS_FOLDER_NAME']}/#{version_number}/#{build_number}"
public_link = "https://#{s3_bucket}/#{s3_folder}/Mattermost.apk"
public_link = "https://#{s3_bucket}/#{s3_folder}/#{app_name_sub}.apk"
# Send a build message to Mattermost
pretext = '#### New Android released ready to be published'
@@ -608,7 +618,7 @@ def submit_to_store
if ENV['BETA_BUILD'] == 'true'
pretext = '#### New Android beta published to Google Play'
msg = 'Sign up as a beta tester [here](https://play.google.com/apps/testing/com.mattermost.rnbeta).\nDownload link: #{public_link}'
msg = "Sign up as a beta tester [here](https://play.google.com/apps/testing/com.mattermost.rnbeta).\nDownload link: #{public_link}"
end
send_message_to_mattermost({
@@ -633,7 +643,7 @@ def submit_to_store
build_number = get_build_number(xcodeproj: './ios/Mattermost.xcodeproj')
s3_folder = "#{ENV['AWS_FOLDER_NAME']}/#{version_number}/#{build_number}"
public_link = "https://#{s3_bucket}/#{s3_folder}/Mattermost.ipa"
public_link = "https://#{s3_bucket}/#{s3_folder}/#{app_name_sub}.ipa"
# Send a build message to Mattermost
pretext = '#### New iOS released ready to be published'
@@ -680,36 +690,37 @@ end
desc 'Upload file to s3'
lane :upload_file_to_s3 do |options|
os_type = options[:os_type]
filename = options[:filename]
filename_plist = ""
file = options[:file]
file_plist = ""
if filename.nil? || filename.empty?
UI.message("Filename empty")
if file.nil? || file.empty?
app_name = ENV['APP_NAME'] || 'Mattermost Beta'
filename = app_name.gsub(" ", "_")
if os_type == 'Android'
filename = "#{app_name}.apk"
file = "#{filename}.apk"
elsif os_type == 'iOS'
filename = "#{app_name}.ipa"
filename_plist = "#{app_name}.plist"
file = "#{filename}.ipa"
file_plist = "#{filename}.plist"
end
end
build_folder_path = Dir[File.expand_path('..')].first
file_path = "#{build_folder_path}/#{filename}"
file_path = "#{build_folder_path}/#{file}"
unless ENV['AWS_BUCKET_NAME'].nil? || ENV['AWS_BUCKET_NAME'].empty? || ENV['AWS_REGION'].nil? || ENV['AWS_REGION'].empty? || file_path.nil?
s3_region = ENV['AWS_REGION']
s3_bucket = ENV['AWS_BUCKET_NAME']
s3_folder = ''
if ENV['BRANCH_TO_BUILD'] == 'pr'
if is_build_pr
s3_folder = "#{ENV['AWS_FOLDER_NAME']}/#{ENV['BRANCH_TO_BUILD']}"
else
if os_type == 'Android'
version_number = android_get_version_name(gradle_file: './android/app/build.gradle')
build_number = android_get_version_code(gradle_file: './android/app/build.gradle')
else
elsif os_type == 'iOS'
version_number = get_version_number(xcodeproj: './ios/Mattermost.xcodeproj', target: 'Mattermost')
build_number = get_build_number(xcodeproj: './ios/Mattermost.xcodeproj')
end
@@ -718,25 +729,30 @@ lane :upload_file_to_s3 do |options|
end
s3 = Aws::S3::Resource.new(region: s3_region)
file_obj = s3.bucket(s3_bucket).object("#{s3_folder}/#{filename}")
file_obj = s3.bucket(s3_bucket).object("#{s3_folder}/#{file}")
file_obj.upload_file("#{file_path}")
if ENV['BRANCH_TO_BUILD'] == 'pr'
plist_template = File.read('plist.erb')
plist_body = ERB.new(plist_template).result(binding)
if is_build_pr
if os_type == 'Android'
install_url = "https://#{s3_bucket}/#{s3_folder}/#{file}"
elsif os_type == 'iOS'
current_build_number = get_build_number(xcodeproj: './ios/Mattermost.xcodeproj')
plist_template = File.read('plist.erb')
plist_body = ERB.new(plist_template).result(binding)
plist_obj = s3.bucket(s3_bucket).object("#{s3_folder}/#{filename_plist}")
plist_obj.put(body: plist_body)
plist_obj = s3.bucket(s3_bucket).object("#{s3_folder}/#{file_plist}")
plist_obj.put(body: plist_body)
ios_plist_install_url = "itms-services://?action=download-manifest&url=https://#{s3_bucket}/#{s3_folder}/#{filename}.plist"
install_url = "itms-services://?action=download-manifest&url=https://#{s3_bucket}/#{s3_folder}/#{file_plist}"
end
qa_build_message({
:os_type => os_type,
:install_url => ios_plist_install_url
:install_url => install_url
})
end
UI.success("S3 bucket @#{s3_bucket}, object @#{s3_folder}/#{filename}")
UI.success("S3 public path: https://#{s3_bucket}/#{s3_folder}/#{filename}")
UI.success("S3 bucket @#{s3_bucket}, object @#{s3_folder}/#{file}")
UI.success("S3 public path: https://#{s3_bucket}/#{s3_folder}/#{file}")
end
end

View File

@@ -6,19 +6,19 @@ GEM
public_suffix (>= 2.0.2, < 4.0)
atomos (0.1.3)
aws-eventstream (1.0.3)
aws-partitions (1.158.0)
aws-sdk-core (3.50.0)
aws-partitions (1.172.0)
aws-sdk-core (3.54.2)
aws-eventstream (~> 1.0, >= 1.0.2)
aws-partitions (~> 1.0)
aws-sigv4 (~> 1.1)
jmespath (~> 1.0)
aws-sdk-kms (1.18.0)
aws-sdk-core (~> 3, >= 3.48.2)
aws-sdk-kms (1.21.0)
aws-sdk-core (~> 3, >= 3.53.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.36.1)
aws-sdk-core (~> 3, >= 3.48.2)
aws-sdk-s3 (1.42.0)
aws-sdk-core (~> 3, >= 3.53.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.0)
aws-sigv4 (~> 1.1)
aws-sigv4 (1.1.0)
aws-eventstream (~> 1.0, >= 1.0.2)
babosa (1.0.2)
@@ -43,7 +43,7 @@ GEM
faraday_middleware (0.13.1)
faraday (>= 0.7.4, < 1.0)
fastimage (2.1.5)
fastlane (2.122.0)
fastlane (2.125.2)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.3, < 3.0.0)
babosa (>= 1.0.2, < 2.0.0)
@@ -62,8 +62,8 @@ GEM
google-cloud-storage (>= 1.15.0, < 2.0.0)
highline (>= 1.7.2, < 2.0.0)
json (< 3.0.0)
jwt (~> 2.1.0)
mini_magick (~> 4.5.1)
multi_json
multi_xml (~> 0.5)
multipart-post (~> 2.0.0)
plist (>= 3.1.0, < 4.0.0)
@@ -96,7 +96,7 @@ GEM
signet (~> 0.9)
google-cloud-core (1.3.0)
google-cloud-env (~> 1.0)
google-cloud-env (1.0.5)
google-cloud-env (1.1.0)
faraday (~> 0.11)
google-cloud-storage (1.16.0)
digest-crc (~> 0.4)
@@ -139,7 +139,7 @@ GEM
uber (< 0.2.0)
retriable (3.1.2)
rouge (2.0.7)
rubyzip (1.2.2)
rubyzip (1.2.3)
security (0.1.3)
signet (0.11.0)
addressable (~> 2.3)
@@ -153,15 +153,15 @@ GEM
terminal-notifier (2.0.0)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
tty-cursor (0.6.1)
tty-screen (0.6.5)
tty-spinner (0.9.0)
tty-cursor (~> 0.6.0)
tty-cursor (0.7.0)
tty-screen (0.7.0)
tty-spinner (0.9.1)
tty-cursor (~> 0.7)
uber (0.1.0)
unf (0.1.4)
unf_ext
unf_ext (0.0.7.6)
unicode-display_width (1.5.0)
unicode-display_width (1.6.0)
word_wrap (1.0.0)
xcodeproj (1.9.0)
CFPropertyList (>= 2.3.3, < 4.0)

View File

@@ -5,6 +5,7 @@
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */; };
00C302E71ABCBA2D00DB3ED1 /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302BA1ABCB90400DB3ED1 /* libRCTGeolocation.a */; };
@@ -51,6 +52,7 @@
7BD159C40A68467FB5A17141 /* FontAwesome5_Solid.ttf in Resources */ = {isa = PBXBuildFile; fileRef = DC1D660B55BE462A9C3B8028 /* FontAwesome5_Solid.ttf */; };
7F151D3E221B062700FAD8F3 /* RuntimeUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F151D3D221B062700FAD8F3 /* RuntimeUtils.swift */; };
7F151D41221B069200FAD8F3 /* 0155-keys.png in Resources */ = {isa = PBXBuildFile; fileRef = 7F151D40221B069200FAD8F3 /* 0155-keys.png */; };
7F1967FF228E379000D19270 /* libKeyboardTrackingView.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F11AA10228848D8001C9540 /* libKeyboardTrackingView.a */; };
7F1A56B4227E38B600EF7A90 /* libRNCAsyncStorage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F1A569B227E389600EF7A90 /* libRNCAsyncStorage.a */; };
7F240A1C220D3A2300637665 /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F240A1B220D3A2300637665 /* ShareViewController.swift */; };
7F240A1F220D3A2300637665 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7F240A1D220D3A2300637665 /* MainInterface.storyboard */; };
@@ -74,10 +76,11 @@
7F43D6061F6BF9EB001FC614 /* libPods-Mattermost.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F43D6051F6BF9EB001FC614 /* libPods-Mattermost.a */; };
7F43D63F1F6BFA19001FC614 /* libBVLinearGradient.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 37D8FEC21E80B5230091F3BD /* libBVLinearGradient.a */; };
7F43D6401F6BFA82001FC614 /* libRCTPushNotification.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F63D2811E6C957C001FAE12 /* libRCTPushNotification.a */; };
7F4C2598227E3B11009144EF /* libRNCNetInfo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F4C2595227E3AE9009144EF /* libRNCNetInfo.a */; };
7F581D35221ED5C60099E66B /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F581D34221ED5C60099E66B /* NotificationService.swift */; };
7F581D39221ED5C60099E66B /* NotificationService.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 7F581D32221ED5C60099E66B /* NotificationService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
7F581F78221EEA7C0099E66B /* libUploadAttachments.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FABE04522137F2A00D0F595 /* libUploadAttachments.a */; };
7F4C2598227E3B11009144EF /* libRNCNetInfo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F4C2595227E3AE9009144EF /* libRNCNetInfo.a */; };
7F5BA34722B99B7B005B05D3 /* Mattermost+RCTUITextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F5BA34622B99B7B005B05D3 /* Mattermost+RCTUITextView.m */; };
7F5CA9A0208FE3B9004F91CE /* libRNDocumentPicker.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F5CA991208FE38F004F91CE /* libRNDocumentPicker.a */; };
7F642DF02093533300F3165E /* libRNDeviceInfo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F642DED2093530B00F3165E /* libRNDeviceInfo.a */; };
7F72F2EE2211220500F98FFF /* GenericPreview.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7F72F2ED2211220500F98FFF /* GenericPreview.xib */; };
@@ -98,6 +101,7 @@
7FF2AF8B2086483E00FFBDF4 /* KeyShareConsumer.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7FF2AF8A2086483E00FFBDF4 /* KeyShareConsumer.storyboard */; };
7FF31C1421330B7900680B75 /* libRNFetchBlob.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FF31C1321330B4200680B75 /* libRNFetchBlob.a */; };
832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 832341B51AAA6A8300B99B32 /* libRCTText.a */; };
84E3264B229834C30055068A /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E325FF229834C30055068A /* Config.swift */; };
895C9A56B94A45C1BAF568FE /* Entypo.ttf in Resources */ = {isa = PBXBuildFile; fileRef = AC6EB561E1F64C17A69D2FAD /* Entypo.ttf */; };
8D26455C994F46C39B1392F2 /* libRNSafeArea.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9263CF9B16054263B13EA23B /* libRNSafeArea.a */; };
9358B95F95184EE0A4DCE629 /* OpenSans-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = D4B1B363C2414DA19C1AC521 /* OpenSans-Bold.ttf */; };
@@ -366,6 +370,13 @@
remoteGlobalIDString = 134814201AA4EA6300B7C361;
remoteInfo = RCTLinking;
};
7F11AA0F228848D8001C9540 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 7F11AA06228848D8001C9540 /* KeyboardTrackingView.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = D834CED81CC64F2400FA5668;
remoteInfo = KeyboardTrackingView;
};
7F1A569A227E389600EF7A90 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 7F1A5663227E389600EF7A90 /* RNCAsyncStorage.xcodeproj */;
@@ -779,6 +790,7 @@
78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = "../node_modules/react-native/Libraries/LinkingIOS/RCTLinking.xcodeproj"; sourceTree = "<group>"; };
79CB6EBA24FE4ABFB0C155F0 /* YTPlayerView-iframe-player.html */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "YTPlayerView-iframe-player.html"; path = "../node_modules/react-native-youtube/assets/YTPlayerView-iframe-player.html"; sourceTree = "<group>"; };
7DCC3D826CE640AF8F491692 /* BVLinearGradient.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = BVLinearGradient.xcodeproj; path = "../node_modules/react-native-linear-gradient/BVLinearGradient.xcodeproj"; sourceTree = "<group>"; };
7F11AA06228848D8001C9540 /* KeyboardTrackingView.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = KeyboardTrackingView.xcodeproj; path = "../node_modules/react-native-keyboard-tracking-view/lib/KeyboardTrackingView.xcodeproj"; sourceTree = "<group>"; };
7F151D3D221B062700FAD8F3 /* RuntimeUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = RuntimeUtils.swift; path = Mattermost/RuntimeUtils.swift; sourceTree = "<group>"; };
7F151D40221B069200FAD8F3 /* 0155-keys.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "0155-keys.png"; path = "Mattermost/0155-keys.png"; sourceTree = "<group>"; };
7F151D42221B07F700FAD8F3 /* MattermostShare-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MattermostShare-Bridging-Header.h"; sourceTree = "<group>"; };
@@ -808,6 +820,8 @@
7F581D34221ED5C60099E66B /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = "<group>"; };
7F581D36221ED5C60099E66B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
7F581F77221EEA5A0099E66B /* NotificationService.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NotificationService.entitlements; sourceTree = "<group>"; };
7F5BA34522B99B7B005B05D3 /* Mattermost+RCTUITextView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "Mattermost+RCTUITextView.h"; path = "Mattermost/Mattermost+RCTUITextView.h"; sourceTree = "<group>"; };
7F5BA34622B99B7B005B05D3 /* Mattermost+RCTUITextView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = "Mattermost+RCTUITextView.m"; path = "Mattermost/Mattermost+RCTUITextView.m"; sourceTree = "<group>"; };
7F5CA956208FE38F004F91CE /* RNDocumentPicker.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RNDocumentPicker.xcodeproj; path = "../node_modules/react-native-document-picker/ios/RNDocumentPicker.xcodeproj"; sourceTree = "<group>"; };
7F63D27B1E6C957C001FAE12 /* RCTPushNotification.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTPushNotification.xcodeproj; path = "../node_modules/react-native/Libraries/PushNotificationIOS/RCTPushNotification.xcodeproj"; sourceTree = "<group>"; };
7F63D2C21E6DD98A001FAE12 /* Mattermost.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = Mattermost.entitlements; path = Mattermost/Mattermost.entitlements; sourceTree = "<group>"; };
@@ -843,6 +857,7 @@
7FFE32BF1FD9CCAA0038C7A0 /* Sentry.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Sentry.framework; sourceTree = BUILT_PRODUCTS_DIR; };
832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = "../node_modules/react-native/Libraries/Text/RCTText.xcodeproj"; sourceTree = "<group>"; };
849D881A0372465294DE7315 /* RNSafeArea.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNSafeArea.xcodeproj; path = "../node_modules/react-native-safe-area/ios/RNSafeArea.xcodeproj"; sourceTree = "<group>"; };
84E325FF229834C30055068A /* Config.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Config.swift; sourceTree = "<group>"; };
8F0B22D2C9924FAFA7FB681C /* Roboto-LightItalic.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Roboto-LightItalic.ttf"; path = "../assets/fonts/Roboto-LightItalic.ttf"; sourceTree = "<group>"; };
920B7301B6F84DDD80677487 /* RNGestureHandler.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNGestureHandler.xcodeproj; path = "../node_modules/react-native-gesture-handler/ios/RNGestureHandler.xcodeproj"; sourceTree = "<group>"; };
9263CF9B16054263B13EA23B /* libRNSafeArea.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNSafeArea.a; sourceTree = "<group>"; };
@@ -889,6 +904,7 @@
files = (
7FABE04622137F5C00D0F595 /* libUploadAttachments.a in Frameworks */,
74D116AD208A8D5600CF8A79 /* libRNKeychain.a in Frameworks */,
7F1967FF228E379000D19270 /* libKeyboardTrackingView.a in Frameworks */,
37ABD3C81F4CE142001FDE6B /* libART.a in Frameworks */,
375218501F4B9EE70035444B /* libRCTCameraRoll.a in Frameworks */,
7F43D5E01F6BF994001FC614 /* libRNSVG.a in Frameworks */,
@@ -1099,6 +1115,8 @@
7FEB109C1F61019C0039A015 /* UIImage+ImageEffects.m */,
7F151D40221B069200FAD8F3 /* 0155-keys.png */,
7F292AA51E8ABB1100A450A3 /* splash.png */,
7F5BA34522B99B7B005B05D3 /* Mattermost+RCTUITextView.h */,
7F5BA34622B99B7B005B05D3 /* Mattermost+RCTUITextView.m */,
);
name = Mattermost;
sourceTree = "<group>";
@@ -1241,6 +1259,14 @@
name = Products;
sourceTree = "<group>";
};
7F11AA07228848D8001C9540 /* Products */ = {
isa = PBXGroup;
children = (
7F11AA10228848D8001C9540 /* libKeyboardTrackingView.a */,
);
name = Products;
sourceTree = "<group>";
};
7F1A5664227E389600EF7A90 /* Products */ = {
isa = PBXGroup;
children = (
@@ -1252,6 +1278,7 @@
7F240A1A220D3A2300637665 /* MattermostShare */ = {
isa = PBXGroup;
children = (
84E325FF229834C30055068A /* Config.swift */,
7F72F2E4221113DF00F98FFF /* Images */,
7F240A20220D3A2300637665 /* Info.plist */,
7F240ACF220D4A6100637665 /* KeyChainDataSource.h */,
@@ -1484,6 +1511,7 @@
isa = PBXGroup;
children = (
7FABE04022137F2900D0F595 /* UploadAttachments.xcodeproj */,
7F11AA06228848D8001C9540 /* KeyboardTrackingView.xcodeproj */,
7F1A5663227E389600EF7A90 /* RNCAsyncStorage.xcodeproj */,
7F4C258F227E3AE9009144EF /* RNCNetInfo.xcodeproj */,
7F26C1C6219C463300FEB42D /* RNCWebView.xcodeproj */,
@@ -1732,6 +1760,10 @@
ProductGroup = 7F8C4A5D1F3E21FB003A22BA /* Products */;
ProjectRef = 27A6EA89298440439DA9F98D /* JailMonkey.xcodeproj */;
},
{
ProductGroup = 7F11AA07228848D8001C9540 /* Products */;
ProjectRef = 7F11AA06228848D8001C9540 /* KeyboardTrackingView.xcodeproj */;
},
{
ProductGroup = 00C302A81ABCB8CE00DB3ED1 /* Products */;
ProjectRef = 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */;
@@ -2130,6 +2162,13 @@
remoteRef = 78C398B81ACF4ADC00677621 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
7F11AA10228848D8001C9540 /* libKeyboardTrackingView.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libKeyboardTrackingView.a;
remoteRef = 7F11AA0F228848D8001C9540 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
7F1A569B227E389600EF7A90 /* libRNCAsyncStorage.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
@@ -2584,6 +2623,7 @@
7FEB10981F6101710039A015 /* BlurAppScreen.m in Sources */,
7FEB109D1F61019C0039A015 /* MattermostManaged.m in Sources */,
7F240ACD220D460300637665 /* MattermostBucketModule.m in Sources */,
7F5BA34722B99B7B005B05D3 /* Mattermost+RCTUITextView.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -2596,6 +2636,7 @@
7F240ADB220E089300637665 /* Item.swift in Sources */,
7F240A1C220D3A2300637665 /* ShareViewController.swift in Sources */,
7FABDFC22211A39000D0F595 /* Section.swift in Sources */,
84E3264B229834C30055068A /* Config.swift in Sources */,
7FABE00A2212650600D0F595 /* ChannelsViewController.swift in Sources */,
7F240ADD220E094A00637665 /* TeamsViewController.swift in Sources */,
);
@@ -2727,7 +2768,7 @@
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 193;
CURRENT_PROJECT_VERSION = 204;
DEAD_CODE_STRIPPING = NO;
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
ENABLE_BITCODE = NO;
@@ -2787,7 +2828,7 @@
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 193;
CURRENT_PROJECT_VERSION = 204;
DEAD_CODE_STRIPPING = NO;
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
ENABLE_BITCODE = NO;

View File

@@ -19,7 +19,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.19.0</string>
<string>1.20.2</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
@@ -34,7 +34,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>193</string>
<string>204</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSRequiresIPhoneOS</key>

View File

@@ -0,0 +1,17 @@
//
// Mattermost+RCTUITextView.h
// Mattermost
//
// Created by Elias Nahum on 6/18/19.
// Copyright © 2019 Facebook. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Mattermost_RCTUITextView : NSObject
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,36 @@
//
// Mattermost+RCTUITextView.m
// Mattermost
//
// Created by Elias Nahum on 6/18/19.
// Copyright © 2019 Facebook. All rights reserved.
//
#import "Mattermost+RCTUITextView.h"
#import "RCTUITextView.h"
@implementation Mattermost_RCTUITextView
@end
@implementation RCTUITextView (DisableCopyPaste)
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
NSDictionary *response = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"com.apple.configuration.managed"];
if(response) {
NSString *copyPasteProtection = response[@"copyAndPasteProtection"];
BOOL prevent = action == @selector(paste:) ||
action == @selector(copy:) ||
action == @selector(cut:) ||
action == @selector(_share:);
if ([copyPasteProtection isEqual: @"true"] && prevent) {
return NO;
}
}
return [super canPerformAction:action withSender:sender];
}
@end

View File

@@ -11,7 +11,7 @@
#import <React/RCTUtils.h>
@interface MattermostManaged : RCTEventEmitter <RCTBridgeModule>
- (NSUserDefaults *)bucketByName:(NSString*)name;
@property (nonatomic) NSUserDefaults *sharedUserDefaults;
+ (void)sendConfigChangedEvent;
@end

View File

@@ -6,8 +6,8 @@
// See License.txt for license information.
//
#import "RCTUITextView.h"
#import "MattermostManaged.h"
#import <UploadAttachments/Constants.h>
@implementation MattermostManaged {
bool hasListeners;
@@ -85,6 +85,8 @@ RCT_EXPORT_MODULE();
- (instancetype)init {
self = [super init];
if (self) {
_sharedUserDefaults = [[NSUserDefaults alloc] initWithSuiteName:APP_GROUP_ID];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(managedConfigDidChange:) name:@"managedConfigDidChange" object:nil];
[[NSNotificationCenter defaultCenter] addObserverForName:NSUserDefaultsDidChangeNotification
object:nil
@@ -122,6 +124,13 @@ static NSString * const feedbackKey = @"com.apple.feedback.managed";
- (void) remoteConfigChanged {
NSDictionary *response = [[NSUserDefaults standardUserDefaults] dictionaryForKey:configurationKey];
NSDictionary *group = [self.sharedUserDefaults dictionaryForKey:configurationKey];
if (response && ![response isEqualToDictionary:group]) {
// copies the managed configuration so it is accessible in the Extensions
[self.sharedUserDefaults setObject:response forKey:configurationKey];
}
if (hasListeners) {
@try {
[self sendEventWithName:@"managedConfigDidChange" body:response];
@@ -142,31 +151,19 @@ RCT_EXPORT_METHOD(getConfig:(RCTPromiseResolveBlock)resolve
}
}
RCT_EXPORT_METHOD(isRunningInSplitView:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject) {
BOOL isRunningInFullScreen = CGRectEqualToRect(
[UIApplication sharedApplication].delegate.window.frame,
[UIApplication sharedApplication].delegate.window.screen.bounds);
resolve(@{
@"isSplitView": @(!isRunningInFullScreen)
});
}
RCT_EXPORT_METHOD(quitApp)
{
exit(0);
}
@end
@implementation RCTUITextView (DisableCopyPaste)
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
NSDictionary *response = [[NSUserDefaults standardUserDefaults] dictionaryForKey:configurationKey];
if(response) {
NSString *copyPasteProtection = response[@"copyAndPasteProtection"];
BOOL prevent = action == @selector(paste:) ||
action == @selector(copy:) ||
action == @selector(cut:) ||
action == @selector(_share:);
if ([copyPasteProtection isEqual: @"true"] && prevent) {
return NO;
}
}
return [super canPerformAction:action withSender:sender];
}
@end

Some files were not shown because too many files have changed in this diff Show More