forked from Ivasoft/mattermost-mobile
Compare commits
66 Commits
release-1.
...
release-1.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
06c6139ed6 | ||
|
|
4f4ed42c32 | ||
|
|
20e3ee14ec | ||
|
|
69a20e3299 | ||
|
|
209f650b7c | ||
|
|
558897c75a | ||
|
|
e6401da017 | ||
|
|
ff29788749 | ||
|
|
7729cf2d10 | ||
|
|
4c621890b0 | ||
|
|
c418703dba | ||
|
|
22dfaf1e0f | ||
|
|
937209c6d7 | ||
|
|
ff1f0a741d | ||
|
|
9ecc755c65 | ||
|
|
c11b92b64e | ||
|
|
b51f7a0bb2 | ||
|
|
9db54fb462 | ||
|
|
bcd5397300 | ||
|
|
053798814e | ||
|
|
04e158fa81 | ||
|
|
11f24a76a1 | ||
|
|
8c5b493405 | ||
|
|
fa605ae6e5 | ||
|
|
2666c7c541 | ||
|
|
537acefb2d | ||
|
|
dccdf1a238 | ||
|
|
5f8910004b | ||
|
|
c56b223fa6 | ||
|
|
d45268d877 | ||
|
|
d50923e841 | ||
|
|
449d7272d2 | ||
|
|
a47b2a3831 | ||
|
|
a0b272016b | ||
|
|
a100ed4ad2 | ||
|
|
f306cef236 | ||
|
|
089d158b87 | ||
|
|
355f7a6b6e | ||
|
|
075b99d48d | ||
|
|
41a1d55713 | ||
|
|
504072d01b | ||
|
|
55511e1fdb | ||
|
|
c4e23af279 | ||
|
|
031bac9863 | ||
|
|
1082f08757 | ||
|
|
49bd5a13ac | ||
|
|
122676ef5d | ||
|
|
7e9c20e191 | ||
|
|
4d027f4e6e | ||
|
|
0112b96366 | ||
|
|
c387d2661c | ||
|
|
034a9400c5 | ||
|
|
1f47ba71cf | ||
|
|
dc87e20bcb | ||
|
|
abb93e9b3d | ||
|
|
57a11783c3 | ||
|
|
ea0b24149c | ||
|
|
dbf96ac8eb | ||
|
|
34389c70c9 | ||
|
|
b807140fe1 | ||
|
|
9ed9c763e2 | ||
|
|
6a9300afb7 | ||
|
|
d1c81ef2d4 | ||
|
|
1805874740 | ||
|
|
10572d17ad | ||
|
|
4ded73b927 |
@@ -26,12 +26,13 @@
|
||||
"no-undefined": 0,
|
||||
"react/display-name": [2, { "ignoreTranspilerName": false }],
|
||||
"react/jsx-filename-extension": 0,
|
||||
"@typescript-eslint/camelcase": [
|
||||
2,
|
||||
"camelcase": [
|
||||
0,
|
||||
{
|
||||
"properties": "never"
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/ban-types": 0,
|
||||
"@typescript-eslint/no-non-null-assertion": 0,
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
2,
|
||||
|
||||
@@ -71,4 +71,4 @@ untyped-import
|
||||
untyped-type-import
|
||||
|
||||
[version]
|
||||
^0.113.0
|
||||
^0.122.0
|
||||
|
||||
70
NOTICE.txt
70
NOTICE.txt
@@ -113,6 +113,41 @@ SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
## @react-native-community/clipboard
|
||||
|
||||
This product contains '@react-native-community/clipboard' by React Native Community.
|
||||
|
||||
React Native Clipboard API for both iOS and Android
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/react-native-community/clipboard
|
||||
|
||||
* 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/masked-view
|
||||
|
||||
This product contains '@react-native-community/masked-view' by React Native Community.
|
||||
@@ -1920,41 +1955,6 @@ SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
## react-native-keyboard-aware-scroll-view
|
||||
|
||||
This product contains a modified version of 'react-native-keyboard-aware-scroll-view' by APSL.
|
||||
|
||||
A ScrollView component that handles keyboard appearance and automatically scrolls to focused TextInput.
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/APSL/react-native-keyboard-aware-scroll-view
|
||||
|
||||
* LICENSE: MIT
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 APSL
|
||||
|
||||
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-keyboard-tracking-view
|
||||
|
||||
This product contains a modified version of 'react-native-keyboard-tracking-view' by Artal Druk.
|
||||
|
||||
@@ -108,8 +108,7 @@ def enableSeparateBuildPerCPUArchitecture = false
|
||||
*/
|
||||
def enableProguardInReleaseBuilds = false
|
||||
|
||||
// Add v8-android - prebuilt libv8android.so into APK
|
||||
def jscFlavor = 'org.chromium:v8-android:+'
|
||||
def jscFlavor = 'org.webkit:android-jsc-intl:+'
|
||||
|
||||
/**
|
||||
* Whether to enable the Hermes VM.
|
||||
@@ -133,8 +132,8 @@ android {
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
missingDimensionStrategy "RNNotifications.reactNativeVersion", "reactNative60"
|
||||
versionCode 318
|
||||
versionName "1.34.0"
|
||||
versionCode 326
|
||||
versionName "1.35.1"
|
||||
multiDexEnabled = true
|
||||
ndk {
|
||||
abiFilters 'armeabi-v7a','arm64-v8a','x86','x86_64'
|
||||
@@ -194,14 +193,6 @@ android {
|
||||
targetCompatibility 1.8
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
// Make sure libjsc.so does not packed in APK
|
||||
exclude "**/libjsc.so"
|
||||
pickFirst "lib/armeabi-v7a/libc++_shared.so"
|
||||
pickFirst "lib/arm64-v8a/libc++_shared.so"
|
||||
pickFirst "lib/x86/libc++_shared.so"
|
||||
pickFirst "lib/x86_64/libc++_shared.so"
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
android:theme="@style/AppTheme"
|
||||
android:installLocation="auto"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
>
|
||||
<meta-data android:name="firebase_analytics_collection_deactivated" android:value="true" />
|
||||
<meta-data android:name="android.content.APP_RESTRICTIONS"
|
||||
|
||||
@@ -172,7 +172,9 @@ public class CustomPushNotification extends PushNotification {
|
||||
break;
|
||||
}
|
||||
|
||||
notifyReceivedToJS();
|
||||
if (mAppLifecycleFacade.isReactInitialized()) {
|
||||
notifyReceivedToJS();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -532,7 +534,12 @@ public class CustomPushNotification extends PushNotification {
|
||||
}
|
||||
|
||||
private String removeSenderNameFromMessage(String message, String senderName) {
|
||||
return message.replaceFirst(senderName, "").replaceFirst(": ", "").trim();
|
||||
Integer index = message.indexOf(senderName);
|
||||
if (index == 0) {
|
||||
message = message.substring(senderName.length());
|
||||
}
|
||||
|
||||
return message.replaceFirst(": ", "").trim();
|
||||
}
|
||||
|
||||
private void notificationReceiptDelivery(String ackId, String postId, String type, boolean isIdLoaded, ResolvePromise promise) {
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
<string name="allowOtherServers_description">Allow the user to change the above server URL.</string>
|
||||
<string name="username_title">Default Username</string>
|
||||
<string name="username_description">Set the username or email address to use to authenticate against the Mattermost Server.</string>
|
||||
<string name="timeout_title">Default Request Timeout</string>
|
||||
<string name="timeout_description">How long in milliseconds the mobile app should wait for the server to respond.</string>
|
||||
<string name="vendor_title">EMM Vendor or Company Name</string>
|
||||
<string name="vendor_description">Name of the EMM vendor or company deploying the app. Used in help text when prompting for passcodes so users are aware why the app is being protected.</string>
|
||||
</resources>
|
||||
|
||||
@@ -43,6 +43,12 @@
|
||||
android:description="@string/username_description"
|
||||
android:restrictionType="string"
|
||||
android:defaultValue="" />
|
||||
<restriction
|
||||
android:key="timeout"
|
||||
android:title="@string/timeout_title"
|
||||
android:description="@string/timeout_description"
|
||||
android:restrictionType="string"
|
||||
android:defaultValue="10000" />
|
||||
<restriction
|
||||
android:key="vendor"
|
||||
android:title="@string/vendor_title"
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
buildscript {
|
||||
ext {
|
||||
buildToolsVersion = "28.0.3"
|
||||
buildToolsVersion = "29.0.2"
|
||||
minSdkVersion = 24
|
||||
compileSdkVersion = 28
|
||||
targetSdkVersion = 28
|
||||
compileSdkVersion = 29
|
||||
targetSdkVersion = 29
|
||||
supportLibVersion = "28.0.0"
|
||||
kotlinVersion = "1.3.61"
|
||||
RNNKotlinVersion = kotlinVersion
|
||||
@@ -18,7 +18,7 @@ buildscript {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.5.2'
|
||||
classpath 'com.android.tools.build:gradle:3.5.3'
|
||||
classpath 'com.google.gms:google-services:4.2.0'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
|
||||
|
||||
@@ -48,17 +48,17 @@ allprojects {
|
||||
jcenter()
|
||||
maven {
|
||||
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
|
||||
// url "$rootDir/../node_modules/react-native/android"
|
||||
url("$rootDir/../node_modules/react-native/android")
|
||||
|
||||
// Replace AAR from original RN with AAR from react-native-v8
|
||||
url("$rootDir/../node_modules/react-native-v8/dist")
|
||||
// url("$rootDir/../node_modules/react-native-v8/dist")
|
||||
}
|
||||
maven {
|
||||
// Local Maven repo containing AARs with JSC library built for Android
|
||||
// url "$rootDir/../node_modules/jsc-android/dist"
|
||||
url("$rootDir/../node_modules/jsc-android/dist")
|
||||
|
||||
// prebuilt libv8android.so
|
||||
url("$rootDir/../node_modules/v8-android/dist")
|
||||
// url("$rootDir/../node_modules/v8-android/dist")
|
||||
}
|
||||
maven {
|
||||
url "https://www.jitpack.io"
|
||||
|
||||
@@ -30,4 +30,4 @@ android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
|
||||
# Version of flipper SDK to use with React Native
|
||||
FLIPPER_VERSION=0.33.1
|
||||
FLIPPER_VERSION=0.37.0
|
||||
|
||||
BIN
android/gradle/wrapper/gradle-wrapper.jar
vendored
BIN
android/gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.2-all.zip
|
||||
|
||||
29
android/gradlew
vendored
29
android/gradlew
vendored
@@ -154,19 +154,19 @@ if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
@@ -175,14 +175,9 @@ save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=$(save "$@")
|
||||
APP_ARGS=`save "$@"`
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
fi
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
27
android/gradlew.bat
vendored
27
android/gradlew.bat
vendored
@@ -13,64 +13,91 @@
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
@@ -13,8 +13,6 @@ import EphemeralStore from '@store/ephemeral_store';
|
||||
import Store from '@store/store';
|
||||
import {NavigationTypes} from '@constants';
|
||||
|
||||
const CHANNEL_SCREEN = 'Channel';
|
||||
|
||||
function getThemeFromState() {
|
||||
const state = Store.redux?.getState() || {};
|
||||
|
||||
@@ -29,8 +27,8 @@ export function resetToChannel(passProps = {}) {
|
||||
const stack = {
|
||||
children: [{
|
||||
component: {
|
||||
id: CHANNEL_SCREEN,
|
||||
name: CHANNEL_SCREEN,
|
||||
id: NavigationTypes.CHANNEL_SCREEN,
|
||||
name: NavigationTypes.CHANNEL_SCREEN,
|
||||
passProps,
|
||||
options: {
|
||||
layout: {
|
||||
@@ -88,6 +86,8 @@ export function resetToChannel(passProps = {}) {
|
||||
export function resetToSelectServer(allowOtherServers) {
|
||||
const theme = Preferences.THEMES.default;
|
||||
|
||||
EphemeralStore.clearNavigationComponents();
|
||||
|
||||
Navigation.setRoot({
|
||||
root: {
|
||||
stack: {
|
||||
@@ -150,6 +150,8 @@ export function resetToTeams(name, title, passProps = {}, options = {}) {
|
||||
},
|
||||
};
|
||||
|
||||
EphemeralStore.clearNavigationComponents();
|
||||
|
||||
Navigation.setRoot({
|
||||
root: {
|
||||
stack: {
|
||||
@@ -427,7 +429,7 @@ export function closeMainSideMenu() {
|
||||
}
|
||||
|
||||
Keyboard.dismiss();
|
||||
Navigation.mergeOptions(CHANNEL_SCREEN, {
|
||||
Navigation.mergeOptions(NavigationTypes.CHANNEL_SCREEN, {
|
||||
sideMenu: {
|
||||
left: {visible: false},
|
||||
},
|
||||
@@ -439,7 +441,7 @@ export function enableMainSideMenu(enabled, visible = true) {
|
||||
return;
|
||||
}
|
||||
|
||||
Navigation.mergeOptions(CHANNEL_SCREEN, {
|
||||
Navigation.mergeOptions(NavigationTypes.CHANNEL_SCREEN, {
|
||||
sideMenu: {
|
||||
left: {enabled, visible},
|
||||
},
|
||||
@@ -452,7 +454,7 @@ export function openSettingsSideMenu() {
|
||||
}
|
||||
|
||||
Keyboard.dismiss();
|
||||
Navigation.mergeOptions(CHANNEL_SCREEN, {
|
||||
Navigation.mergeOptions(NavigationTypes.CHANNEL_SCREEN, {
|
||||
sideMenu: {
|
||||
right: {visible: true},
|
||||
},
|
||||
@@ -465,7 +467,7 @@ export function closeSettingsSideMenu() {
|
||||
}
|
||||
|
||||
Keyboard.dismiss();
|
||||
Navigation.mergeOptions(CHANNEL_SCREEN, {
|
||||
Navigation.mergeOptions(NavigationTypes.CHANNEL_SCREEN, {
|
||||
sideMenu: {
|
||||
right: {visible: false},
|
||||
},
|
||||
|
||||
@@ -31,7 +31,7 @@ import {getChannelByName as selectChannelByName, getChannelsIdForTeam} from '@mm
|
||||
import EventEmitter from '@mm-redux/utils/event_emitter';
|
||||
|
||||
import {lastChannelIdForTeam, loadSidebarDirectMessagesProfiles} from '@actions/helpers/channels';
|
||||
import {getPosts, getPostsBefore, getPostsSince, getPostThread, loadUnreadChannelPosts} from '@actions/views/post';
|
||||
import {getPosts, getPostsBefore, getPostsSince, loadUnreadChannelPosts} from '@actions/views/post';
|
||||
import {INSERT_TO_COMMENT, INSERT_TO_DRAFT} from '@constants/post_draft';
|
||||
import {getChannelReachable} from '@selectors/channel';
|
||||
import telemetry from '@telemetry';
|
||||
@@ -111,18 +111,6 @@ export function fetchPostActionWithRetry(action, maxTries = MAX_RETRIES) {
|
||||
};
|
||||
}
|
||||
|
||||
export function loadThreadIfNecessary(rootId) {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const {posts, postsInThread} = state.entities.posts;
|
||||
const threadPosts = postsInThread[rootId];
|
||||
|
||||
if (!posts[rootId] || !threadPosts) {
|
||||
dispatch(getPostThread(rootId));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function selectInitialChannel(teamId) {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
|
||||
@@ -152,6 +152,7 @@ export function handleUserRemovedEvent(msg: WebSocketMessage) {
|
||||
|
||||
dispatch(batchActions(actions, 'BATCH_WS_USER_REMOVED'));
|
||||
if (redirectToDefaultChannel) {
|
||||
EventEmitter.emit(General.REMOVED_FROM_CHANNEL, channel.display_name);
|
||||
EventEmitter.emit(General.SWITCH_TO_DEFAULT_CHANNEL, currentTeamId);
|
||||
}
|
||||
} catch {
|
||||
|
||||
@@ -1,26 +1,3 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`FormattedTime should render correctly 1`] = `
|
||||
<View
|
||||
pointerEvents="box-none"
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
collapsable={true}
|
||||
pointerEvents="box-none"
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text>
|
||||
7:02 PM
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
`;
|
||||
exports[`FormattedTime should render correctly 1`] = `undefined`;
|
||||
|
||||
@@ -18,7 +18,7 @@ exports[`AnnouncementBanner should match snapshot 1`] = `
|
||||
]
|
||||
}
|
||||
>
|
||||
<Component
|
||||
<ForwardRef
|
||||
onPress={[Function]}
|
||||
style={
|
||||
Array [
|
||||
@@ -57,7 +57,7 @@ exports[`AnnouncementBanner should match snapshot 1`] = `
|
||||
name="info"
|
||||
size={16}
|
||||
/>
|
||||
</Component>
|
||||
</ForwardRef>
|
||||
</ForwardRef(AnimatedComponentWrapper)>
|
||||
`;
|
||||
|
||||
|
||||
@@ -1,8 +1,20 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`AtMention should match snapshot, no highlight 1`] = `
|
||||
<Text>
|
||||
@John.Smith
|
||||
<Text
|
||||
style={Object {}}
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"backgroundColor": "yellow",
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
@John.Smith
|
||||
</Text>
|
||||
</Text>
|
||||
`;
|
||||
|
||||
@@ -10,13 +22,22 @@ exports[`AtMention should match snapshot, with highlight 1`] = `
|
||||
<Text
|
||||
onLongPress={[Function]}
|
||||
onPress={[Function]}
|
||||
style={Object {}}
|
||||
>
|
||||
<Text
|
||||
style={null}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"color": "#ff0000",
|
||||
},
|
||||
Object {
|
||||
"backgroundColor": "yellow",
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
@John.Smith
|
||||
</Text>
|
||||
|
||||
</Text>
|
||||
`;
|
||||
|
||||
@@ -24,16 +45,18 @@ exports[`AtMention should match snapshot, without highlight 1`] = `
|
||||
<Text
|
||||
onLongPress={[Function]}
|
||||
onPress={[Function]}
|
||||
style={Object {}}
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"color": "#ff0000",
|
||||
}
|
||||
Array [
|
||||
Object {
|
||||
"color": "#ff0000",
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
@Victor.Welch
|
||||
</Text>
|
||||
|
||||
</Text>
|
||||
`;
|
||||
|
||||
@@ -3,16 +3,16 @@
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {Clipboard, Text} from 'react-native';
|
||||
import {StyleSheet, Text} from 'react-native';
|
||||
import Clipboard from '@react-native-community/clipboard';
|
||||
import {intlShape} from 'react-intl';
|
||||
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
|
||||
|
||||
import {showModal} from '@actions/navigation';
|
||||
import {displayUsername} from '@mm-redux/utils/user_utils';
|
||||
|
||||
import CustomPropTypes from 'app/constants/custom_prop_types';
|
||||
import CustomPropTypes from '@constants/custom_prop_types';
|
||||
import mattermostManaged from 'app/mattermost_managed';
|
||||
import BottomSheet from 'app/utils/bottom_sheet';
|
||||
import {showModal} from 'app/actions/navigation';
|
||||
import BottomSheet from '@utils/bottom_sheet';
|
||||
|
||||
export default class AtMention extends React.PureComponent {
|
||||
static propTypes = {
|
||||
@@ -141,39 +141,85 @@ export default class AtMention extends React.PureComponent {
|
||||
render() {
|
||||
const {isSearchResult, mentionName, mentionStyle, onPostPress, teammateNameDisplay, textStyle, mentionKeys} = this.props;
|
||||
const {user} = this.state;
|
||||
const mentionTextStyle = [];
|
||||
|
||||
let backgroundColor;
|
||||
let canPress = false;
|
||||
let highlighted;
|
||||
let isMention = false;
|
||||
let mention;
|
||||
let onLongPress;
|
||||
let onPress;
|
||||
let suffix;
|
||||
let suffixElement;
|
||||
let styleText;
|
||||
|
||||
if (!user.username) {
|
||||
const group = this.getGroupFromMentionName();
|
||||
if (group.allow_reference) {
|
||||
highlighted = mentionKeys.some((item) => item.key === group.name);
|
||||
return (
|
||||
<Text
|
||||
style={textStyle}
|
||||
>
|
||||
<Text style={highlighted ? null : mentionStyle}>
|
||||
{`@${group.name}`}
|
||||
</Text>
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
return <Text style={textStyle}>{'@' + mentionName}</Text>;
|
||||
if (textStyle) {
|
||||
const {backgroundColor: bg, ...otherStyles} = StyleSheet.flatten(textStyle);
|
||||
backgroundColor = bg;
|
||||
styleText = otherStyles;
|
||||
}
|
||||
|
||||
const suffix = this.props.mentionName.substring(user.username.length);
|
||||
highlighted = mentionKeys.some((item) => item.key === user.username);
|
||||
if (user?.username) {
|
||||
suffix = this.props.mentionName.substring(user.username.length);
|
||||
highlighted = mentionKeys.some((item) => item.key.includes(user.username));
|
||||
mention = displayUsername(user, teammateNameDisplay);
|
||||
isMention = true;
|
||||
canPress = true;
|
||||
} else {
|
||||
const group = this.getGroupFromMentionName();
|
||||
if (group.allow_reference) {
|
||||
highlighted = mentionKeys.some((item) => item.key === `@${group.name}`);
|
||||
isMention = true;
|
||||
mention = group.name;
|
||||
suffix = this.props.mentionName.substring(group.name.length);
|
||||
} else {
|
||||
const pattern = new RegExp(/\b(all|channel|here)(?:\.\B|_\b|\b)/, 'i');
|
||||
const mentionMatch = pattern.exec(mentionName);
|
||||
highlighted = true;
|
||||
|
||||
if (mentionMatch) {
|
||||
mention = mentionMatch.length > 1 ? mentionMatch[1] : mentionMatch[0];
|
||||
suffix = mentionName.replace(mention, '');
|
||||
isMention = true;
|
||||
} else {
|
||||
mention = mentionName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (canPress) {
|
||||
onLongPress = this.handleLongPress;
|
||||
onPress = isSearchResult ? onPostPress : this.goToUserProfile;
|
||||
}
|
||||
|
||||
if (suffix) {
|
||||
const suffixStyle = {...styleText, color: this.props.theme.centerChannelColor};
|
||||
suffixElement = (
|
||||
<Text style={suffixStyle}>
|
||||
{suffix}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
if (isMention) {
|
||||
mentionTextStyle.push(mentionStyle);
|
||||
}
|
||||
|
||||
if (highlighted) {
|
||||
mentionTextStyle.push({backgroundColor});
|
||||
}
|
||||
|
||||
return (
|
||||
<Text
|
||||
style={textStyle}
|
||||
onPress={isSearchResult ? onPostPress : this.goToUserProfile}
|
||||
onLongPress={this.handleLongPress}
|
||||
style={styleText}
|
||||
onPress={onPress}
|
||||
onLongPress={onLongPress}
|
||||
>
|
||||
<Text style={highlighted ? null : mentionStyle}>
|
||||
{'@' + displayUsername(user, teammateNameDisplay)}
|
||||
<Text style={mentionTextStyle}>
|
||||
{'@' + mention}
|
||||
</Text>
|
||||
{suffix}
|
||||
{suffixElement}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ describe('AtMention', () => {
|
||||
teammateNameDisplay: '',
|
||||
mentionName: 'John.Smith',
|
||||
mentionStyle: {color: '#ff0000'},
|
||||
textStyle: {backgroundColor: 'yellow'},
|
||||
theme: {},
|
||||
};
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import {getAllUserMentionKeys} from '@mm-redux/selectors/entities/search';
|
||||
|
||||
import {getTeammateNameDisplaySetting, getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
|
||||
import {getGroupsByName} from '@mm-redux/selectors/entities/groups';
|
||||
import {getAllGroupsForReferenceByName} from '@mm-redux/selectors/entities/groups';
|
||||
|
||||
import AtMention from './at_mention';
|
||||
|
||||
@@ -19,7 +19,7 @@ function mapStateToProps(state, ownProps) {
|
||||
usersByUsername: getUsersByUsername(state),
|
||||
mentionKeys: ownProps.mentionKeys || getAllUserMentionKeys(state),
|
||||
teammateNameDisplay: getTeammateNameDisplaySetting(state),
|
||||
groupsByName: getGroupsByName(state),
|
||||
groupsByName: getAllGroupsForReferenceByName(state),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ function mapStateToProps(state, ownProps) {
|
||||
state,
|
||||
{
|
||||
channel: currentChannelId,
|
||||
team: currentTeamId,
|
||||
permission: Permissions.USE_CHANNEL_MENTIONS,
|
||||
default: true,
|
||||
},
|
||||
@@ -57,7 +58,7 @@ function mapStateToProps(state, ownProps) {
|
||||
outChannel = filterMembersNotInChannel(state, matchTerm);
|
||||
}
|
||||
|
||||
if (hasLicense && isMinimumServerVersion(state.entities.general.serverVersion, 5, 24)) {
|
||||
if (haveIChannelPermission(state, {channel: currentChannelId, team: currentTeamId, permission: Permissions.USE_GROUP_MENTIONS, default: true}) && hasLicense && isMinimumServerVersion(state.entities.general.serverVersion, 5, 24)) {
|
||||
if (matchTerm) {
|
||||
groups = searchAssociatedGroupsForReferenceLocal(state, matchTerm, currentTeamId, currentChannelId);
|
||||
} else {
|
||||
|
||||
@@ -165,6 +165,8 @@ const getDateFontSize = () => {
|
||||
};
|
||||
|
||||
const calendarTheme = memoizeResult((theme) => ({
|
||||
arrowHeight: Platform.select({ios: 13, android: 26}),
|
||||
arrowWidth: Platform.select({ios: 8, android: 22}),
|
||||
calendarBackground: theme.centerChannelBg,
|
||||
monthTextColor: changeOpacity(theme.centerChannelColor, 0.8),
|
||||
dayTextColor: theme.centerChannelColor,
|
||||
|
||||
@@ -12,10 +12,6 @@ describe('ChannelLoader', () => {
|
||||
const baseProps = {
|
||||
channelIsLoading: true,
|
||||
theme: Preferences.THEMES.default,
|
||||
actions: {
|
||||
handleSelectChannel: jest.fn(),
|
||||
setChannelLoading: jest.fn(),
|
||||
},
|
||||
isLandscape: false,
|
||||
};
|
||||
|
||||
|
||||
@@ -5,20 +5,18 @@ exports[`EditChannelInfo should match snapshot 1`] = `
|
||||
<Connect(StatusBar) />
|
||||
<KeyboardAwareScrollView
|
||||
enableAutomaticScroll={true}
|
||||
enableOnAndroid={false}
|
||||
enableResetScrollToCoords={true}
|
||||
extraHeight={75}
|
||||
extraScrollHeight={0}
|
||||
keyboardOpeningTime={250}
|
||||
getTextInputRefs={[Function]}
|
||||
keyboardShouldPersistTaps="always"
|
||||
onKeyboardDidHide={[Function]}
|
||||
onKeyboardDidShow={[Function]}
|
||||
scrollToBottomOnKBShow={false}
|
||||
scrollToInputAdditionalOffset={75}
|
||||
startScrolledToBottom={false}
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
viewIsInsideTabBar={false}
|
||||
>
|
||||
<TouchableWithoutFeedback
|
||||
onPress={[Function]}
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
TouchableWithoutFeedback,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view';
|
||||
import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scrollview';
|
||||
import {KeyboardTrackingView} from 'react-native-keyboard-tracking-view';
|
||||
|
||||
import {General} from '@mm-redux/constants';
|
||||
@@ -92,7 +92,7 @@ export default class EditChannelInfo extends PureComponent {
|
||||
}
|
||||
|
||||
if (this.scroll?.current) {
|
||||
this.scroll.current.scrollToPosition(0, 0, true);
|
||||
this.scroll.current.scrollTo({x: 0, y: 0, animated: true});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -184,7 +184,7 @@ export default class EditChannelInfo extends PureComponent {
|
||||
|
||||
scrollHeaderToTop = () => {
|
||||
if (this.scroll.current) {
|
||||
this.scroll.current.scrollToPosition(0, this.state.headerPosition);
|
||||
this.scroll.current.scrollTo({x: 0, y: this.state.headerPosition});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ function mapStateToProps(state, ownProps) {
|
||||
const config = getConfig(state);
|
||||
const emojiName = ownProps.emojiName;
|
||||
const customEmojis = getCustomEmojisByName(state);
|
||||
const serverUrl = Client4.getUrl();
|
||||
|
||||
let imageUrl = '';
|
||||
let unicode;
|
||||
@@ -26,9 +27,13 @@ function mapStateToProps(state, ownProps) {
|
||||
const emoji = Emojis[EmojiIndicesByAlias.get(emojiName)];
|
||||
unicode = emoji.filename;
|
||||
if (BuiltInEmojis.includes(emojiName)) {
|
||||
imageUrl = Client4.getSystemEmojiImageUrl(emoji.filename);
|
||||
if (serverUrl) {
|
||||
imageUrl = Client4.getSystemEmojiImageUrl(emoji.filename);
|
||||
} else {
|
||||
displayTextOnly = true;
|
||||
}
|
||||
}
|
||||
} else if (customEmojis.has(emojiName)) {
|
||||
} else if (customEmojis.has(emojiName) && serverUrl) {
|
||||
const emoji = customEmojis.get(emojiName);
|
||||
imageUrl = Client4.getCustomEmojiImageUrl(emoji.id);
|
||||
isCustomEmoji = true;
|
||||
|
||||
@@ -8,7 +8,7 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
|
||||
<KeyboardAvoidingView
|
||||
behavior="padding"
|
||||
enabled={false}
|
||||
keyboardVerticalOffset={50}
|
||||
keyboardVerticalOffset={80}
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
@@ -9997,7 +9997,7 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
|
||||
}
|
||||
}
|
||||
>
|
||||
<Component
|
||||
<ForwardRef
|
||||
onPress={[Function]}
|
||||
style={
|
||||
Object {
|
||||
@@ -10023,8 +10023,8 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
|
||||
]
|
||||
}
|
||||
/>
|
||||
</Component>
|
||||
<Component
|
||||
</ForwardRef>
|
||||
<ForwardRef
|
||||
onPress={[Function]}
|
||||
style={
|
||||
Object {
|
||||
@@ -10048,8 +10048,8 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
|
||||
]
|
||||
}
|
||||
/>
|
||||
</Component>
|
||||
<Component
|
||||
</ForwardRef>
|
||||
<ForwardRef
|
||||
onPress={[Function]}
|
||||
style={
|
||||
Object {
|
||||
@@ -10073,8 +10073,8 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
|
||||
]
|
||||
}
|
||||
/>
|
||||
</Component>
|
||||
<Component
|
||||
</ForwardRef>
|
||||
<ForwardRef
|
||||
onPress={[Function]}
|
||||
style={
|
||||
Object {
|
||||
@@ -10098,8 +10098,8 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
|
||||
]
|
||||
}
|
||||
/>
|
||||
</Component>
|
||||
<Component
|
||||
</ForwardRef>
|
||||
<ForwardRef
|
||||
onPress={[Function]}
|
||||
style={
|
||||
Object {
|
||||
@@ -10123,8 +10123,8 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
|
||||
]
|
||||
}
|
||||
/>
|
||||
</Component>
|
||||
<Component
|
||||
</ForwardRef>
|
||||
<ForwardRef
|
||||
onPress={[Function]}
|
||||
style={
|
||||
Object {
|
||||
@@ -10148,8 +10148,8 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
|
||||
]
|
||||
}
|
||||
/>
|
||||
</Component>
|
||||
<Component
|
||||
</ForwardRef>
|
||||
<ForwardRef
|
||||
onPress={[Function]}
|
||||
style={
|
||||
Object {
|
||||
@@ -10173,8 +10173,8 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
|
||||
]
|
||||
}
|
||||
/>
|
||||
</Component>
|
||||
<Component
|
||||
</ForwardRef>
|
||||
<ForwardRef
|
||||
onPress={[Function]}
|
||||
style={
|
||||
Object {
|
||||
@@ -10198,8 +10198,8 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
|
||||
]
|
||||
}
|
||||
/>
|
||||
</Component>
|
||||
<Component
|
||||
</ForwardRef>
|
||||
<ForwardRef
|
||||
onPress={[Function]}
|
||||
style={
|
||||
Object {
|
||||
@@ -10223,7 +10223,7 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
|
||||
]
|
||||
}
|
||||
/>
|
||||
</Component>
|
||||
</ForwardRef>
|
||||
</View>
|
||||
</View>
|
||||
</KeyboardTrackingView>
|
||||
|
||||
@@ -25,7 +25,7 @@ export default class EmojiPicker extends EmojiPickerBase {
|
||||
|
||||
const shorten = DeviceTypes.IS_IPHONE_WITH_INSETS && isLandscape ? 6 : 2;
|
||||
|
||||
let keyboardOffset = DeviceTypes.IS_IPHONE_WITH_INSETS ? 50 : 30;
|
||||
let keyboardOffset = DeviceTypes.IS_IPHONE_WITH_INSETS ? 80 : 60;
|
||||
if (isLandscape) {
|
||||
keyboardOffset = DeviceTypes.IS_IPHONE_WITH_INSETS ? 0 : 10;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ import {
|
||||
View,
|
||||
} from 'react-native';
|
||||
import FontAwesomeIcon from 'react-native-vector-icons/FontAwesome';
|
||||
import Octicons from 'react-native-vector-icons/Octicons';
|
||||
|
||||
import sectionListGetItemLayout from 'react-native-section-list-get-item-layout';
|
||||
|
||||
import Emoji from 'app/components/emoji';
|
||||
@@ -249,6 +251,8 @@ export default class EmojiPicker extends PureComponent {
|
||||
|
||||
let listComponent;
|
||||
if (searchTerm) {
|
||||
const contentContainerStyle = filteredEmojis.length ? null : styles.flex;
|
||||
|
||||
listComponent = (
|
||||
<FlatList
|
||||
data={filteredEmojis}
|
||||
@@ -258,6 +262,8 @@ export default class EmojiPicker extends PureComponent {
|
||||
nativeID={SCROLLVIEW_NATIVE_ID}
|
||||
pageSize={10}
|
||||
renderItem={this.flatListRenderItem}
|
||||
ListEmptyComponent={this.renderEmptyList}
|
||||
contentContainerStyle={contentContainerStyle}
|
||||
style={styles.flatList}
|
||||
/>
|
||||
);
|
||||
@@ -449,6 +455,42 @@ export default class EmojiPicker extends PureComponent {
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
renderEmptyList = () => {
|
||||
const {theme} = this.props;
|
||||
const {formatMessage} = this.context.intl;
|
||||
const {searchTerm} = this.state;
|
||||
const styles = getStyleSheetFromTheme(theme);
|
||||
const title = formatMessage({
|
||||
id: 'mobile.emoji_picker.search.not_found_title',
|
||||
defaultMessage: 'No results found for "{searchTerm}"',
|
||||
}, {
|
||||
searchTerm,
|
||||
});
|
||||
const description = formatMessage({
|
||||
id: 'mobile.emoji_picker.search.not_found_description',
|
||||
defaultMessage: 'Check the spelling or try another search.',
|
||||
});
|
||||
return (
|
||||
<View style={[styles.flex, styles.flexCenter]}>
|
||||
<View style={styles.flexCenter}>
|
||||
<View style={styles.notFoundIcon}>
|
||||
<Octicons
|
||||
name='search'
|
||||
size={60}
|
||||
color={theme.buttonBg}
|
||||
/>
|
||||
</View>
|
||||
<Text style={[styles.notFoundText, styles.notFoundText20]}>
|
||||
{title}
|
||||
</Text>
|
||||
<Text style={[styles.notFoundText, styles.notFoundText15]}>
|
||||
{description}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const getStyleSheetFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
@@ -515,6 +557,31 @@ export const getStyleSheetFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
borderRightColor: changeOpacity(theme.centerChannelColor, 0.2),
|
||||
overflow: 'hidden',
|
||||
},
|
||||
flexCenter: {
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
notFoundIcon: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.04),
|
||||
width: 120,
|
||||
height: 120,
|
||||
borderRadius: 60,
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
notFoundText: {
|
||||
color: theme.centerChannelColor,
|
||||
marginTop: 16,
|
||||
},
|
||||
notFoundText20: {
|
||||
fontSize: 20,
|
||||
fontWeight: '600',
|
||||
},
|
||||
notFoundText15: {
|
||||
fontSize: 15,
|
||||
},
|
||||
searchBar: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.2),
|
||||
paddingVertical: 5,
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`FileAttachmentImage should match snapshot 1`] = `
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"borderRadius": 5,
|
||||
"overflow": "hidden",
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"paddingBottom": "100%",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Connect(ProgressiveImage)
|
||||
onError={[Function]}
|
||||
resizeMethod="resize"
|
||||
resizeMode="cover"
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"bottom": 0,
|
||||
"left": 0,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
tintDefaultSource={true}
|
||||
/>
|
||||
</View>
|
||||
`;
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
import brokenImageIcon from '@assets/images/icons/brokenimage.png';
|
||||
import ProgressiveImage from '@components/progressive_image';
|
||||
import {Client4} from '@mm-redux/client';
|
||||
import {isGif} from '@utils/file';
|
||||
import {changeOpacity} from '@utils/theme';
|
||||
|
||||
const SMALL_IMAGE_MAX_HEIGHT = 48;
|
||||
@@ -81,7 +80,7 @@ export default class FileAttachmentImage extends PureComponent {
|
||||
imageProps.defaultSource = {uri: file.localPath};
|
||||
} else if (file.id) {
|
||||
imageProps.thumbnailUri = Client4.getFileThumbnailUrl(file.id);
|
||||
imageProps.imageUri = isGif(file) ? Client4.getFilePreviewUrl(file.id) : Client4.getFileUrl(file.id);
|
||||
imageProps.imageUri = Client4.getFilePreviewUrl(file.id);
|
||||
}
|
||||
return imageProps;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import React from 'react';
|
||||
import {shallow} from 'enzyme';
|
||||
|
||||
import {Client4} from '@mm-redux/client';
|
||||
import brokenImageIcon from '@assets/images/icons/brokenimage.png';
|
||||
|
||||
import FileAttachmentImage from './file_attachment_image.js';
|
||||
|
||||
describe('FileAttachmentImage', () => {
|
||||
const baseProps = {
|
||||
file: {},
|
||||
};
|
||||
|
||||
test('should match snapshot', () => {
|
||||
const wrapper = shallow(
|
||||
<FileAttachmentImage {...baseProps}/>,
|
||||
);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe('imageProps', () => {
|
||||
const wrapper = shallow(
|
||||
<FileAttachmentImage {...baseProps}/>,
|
||||
);
|
||||
const instance = wrapper.instance();
|
||||
|
||||
it('should have brokenImageIcon as defaultSource if state.failed is true', () => {
|
||||
wrapper.setState({failed: true});
|
||||
const file = {};
|
||||
const imageProps = instance.imageProps(file);
|
||||
expect(imageProps.defaultSource).toStrictEqual(brokenImageIcon);
|
||||
expect(imageProps.thumbnailUri).toBeUndefined();
|
||||
expect(imageProps.imageUri).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should have file.localPath as defaultSource if localPath is set', () => {
|
||||
wrapper.setState({failed: false});
|
||||
const file = {localPath: '/localPath.png'};
|
||||
const imageProps = instance.imageProps(file);
|
||||
expect(imageProps.defaultSource).toStrictEqual({uri: file.localPath});
|
||||
expect(imageProps.thumbnailUri).toBeUndefined();
|
||||
expect(imageProps.imageUri).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should have thumbnailUri and imageUri if the file has an ID', () => {
|
||||
const getFileThumbnailUrl = jest.spyOn(Client4, 'getFileThumbnailUrl');
|
||||
const getFilePreviewUrl = jest.spyOn(Client4, 'getFilePreviewUrl');
|
||||
|
||||
wrapper.setState({failed: false});
|
||||
const file = {id: 'id'};
|
||||
const imageProps = instance.imageProps(file);
|
||||
expect(getFileThumbnailUrl).toHaveBeenCalled();
|
||||
expect(getFilePreviewUrl).toHaveBeenCalled();
|
||||
expect(imageProps.defaultSource).toBeUndefined();
|
||||
expect(imageProps.thumbnailUri).toBeDefined();
|
||||
expect(imageProps.imageUri).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -86,7 +86,7 @@ export default class FileAttachmentList extends ImageViewPort {
|
||||
if (file.localPath) {
|
||||
uri = file.localPath;
|
||||
} else {
|
||||
uri = Client4.getFileUrl(file.id);
|
||||
uri = isGif(file) ? Client4.getFileUrl(file.id) : Client4.getFilePreviewUrl(file.id);
|
||||
}
|
||||
|
||||
results.push({
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {render} from '@testing-library/react-native';
|
||||
import TestRenderer from 'react-test-renderer';
|
||||
import {IntlProvider} from 'react-intl';
|
||||
import moment from 'moment-timezone';
|
||||
|
||||
@@ -21,9 +21,10 @@ describe('FormattedTime', () => {
|
||||
let wrapper = renderWithIntl(
|
||||
<FormattedTime {...baseProps}/>,
|
||||
);
|
||||
let element = wrapper.root.find((el) => el.type === 'Text' && el.children && el.children[0] === '7:02 PM');
|
||||
|
||||
expect(wrapper.baseElement).toMatchSnapshot();
|
||||
expect(wrapper.getByText('7:02 PM')).toBeTruthy();
|
||||
expect(element).toBeTruthy();
|
||||
|
||||
wrapper = renderWithIntl(
|
||||
<FormattedTime
|
||||
@@ -32,7 +33,8 @@ describe('FormattedTime', () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(wrapper.getByText('19:02')).toBeTruthy();
|
||||
element = wrapper.root.find((el) => el.type === 'Text' && el.children && el.children[0] === '19:02');
|
||||
expect(element).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should support localization', () => {
|
||||
@@ -42,7 +44,8 @@ describe('FormattedTime', () => {
|
||||
'es',
|
||||
);
|
||||
|
||||
expect(wrapper.getByText('7:02 PM')).toBeTruthy();
|
||||
let element = wrapper.root.find((el) => el.type === 'Text' && el.children && el.children[0] === '7:02 PM');
|
||||
expect(element).toBeTruthy();
|
||||
|
||||
moment.locale('ko');
|
||||
wrapper = renderWithIntl(
|
||||
@@ -50,7 +53,8 @@ describe('FormattedTime', () => {
|
||||
'ko',
|
||||
);
|
||||
|
||||
expect(wrapper.getByText('오후 7:02')).toBeTruthy();
|
||||
element = wrapper.root.find((el) => el.type === 'Text' && el.children && el.children[0] === '오후 7:02');
|
||||
expect(element).toBeTruthy();
|
||||
|
||||
wrapper = renderWithIntl(
|
||||
<FormattedTime
|
||||
@@ -60,7 +64,8 @@ describe('FormattedTime', () => {
|
||||
'ko',
|
||||
);
|
||||
|
||||
expect(wrapper.getByText('19:02')).toBeTruthy();
|
||||
element = wrapper.root.find((el) => el.type === 'Text' && el.children && el.children[0] === '19:02');
|
||||
expect(element).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should fallback to default short format for unsupported locale of react-intl ', () => {
|
||||
@@ -73,7 +78,8 @@ describe('FormattedTime', () => {
|
||||
'es',
|
||||
);
|
||||
|
||||
expect(wrapper.getByText('8:47 AM')).toBeTruthy();
|
||||
let element = wrapper.root.find((el) => el.type === 'Text' && el.children && el.children[0] === '8:47 AM');
|
||||
expect(element).toBeTruthy();
|
||||
|
||||
wrapper = renderWithIntl(
|
||||
<FormattedTime
|
||||
@@ -84,10 +90,11 @@ describe('FormattedTime', () => {
|
||||
'es',
|
||||
);
|
||||
|
||||
expect(wrapper.getByText('8:47')).toBeTruthy();
|
||||
element = wrapper.root.find((el) => el.type === 'Text' && el.children && el.children[0] === '8:47');
|
||||
expect(element).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
function renderWithIntl(component, locale = 'en') {
|
||||
return render(<IntlProvider locale={locale}>{component}</IntlProvider>);
|
||||
return TestRenderer.create(<IntlProvider locale={locale}>{component}</IntlProvider>);
|
||||
}
|
||||
|
||||
@@ -5,12 +5,12 @@ import {PropTypes} from 'prop-types';
|
||||
import React from 'react';
|
||||
import {intlShape} from 'react-intl';
|
||||
import {
|
||||
Clipboard,
|
||||
Keyboard,
|
||||
StyleSheet,
|
||||
Text,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import Clipboard from '@react-native-community/clipboard';
|
||||
|
||||
import CustomPropTypes from 'app/constants/custom_prop_types';
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
|
||||
@@ -38,6 +38,7 @@ export default class MarkdownEmoji extends PureComponent {
|
||||
editedIndicator: this.renderEditedIndicator,
|
||||
emoji: this.renderEmoji,
|
||||
paragraph: this.renderParagraph,
|
||||
document: this.renderParagraph,
|
||||
text: this.renderText,
|
||||
},
|
||||
});
|
||||
@@ -66,7 +67,7 @@ export default class MarkdownEmoji extends PureComponent {
|
||||
renderParagraph = ({children}) => {
|
||||
const style = getStyleSheet(this.props.theme);
|
||||
return (
|
||||
<View style={style.block}>{children}</View>
|
||||
<View style={style.block}><Text>{children}</Text></View>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import {intlShape} from 'react-intl';
|
||||
import {
|
||||
Clipboard,
|
||||
Linking,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
@@ -13,6 +12,7 @@ import {
|
||||
View,
|
||||
} from 'react-native';
|
||||
import FastImage from 'react-native-fast-image';
|
||||
import Clipboard from '@react-native-community/clipboard';
|
||||
|
||||
import brokenImageIcon from '@assets/images/icons/brokenimage.png';
|
||||
import ImageViewPort from '@components/image_viewport';
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
|
||||
import React, {Children, PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {Alert, Clipboard, Linking, Text} from 'react-native';
|
||||
import {Alert, Linking, Text} from 'react-native';
|
||||
import Clipboard from '@react-native-community/clipboard';
|
||||
import urlParse from 'url-parse';
|
||||
import {intlShape} from 'react-intl';
|
||||
|
||||
|
||||
@@ -281,12 +281,10 @@ export function highlightMentions(ast, mentionKeys) {
|
||||
} else if (node.type === 'at_mention') {
|
||||
const matches = mentionKeys.some((mention) => {
|
||||
const mentionName = '@' + node.mentionName;
|
||||
const flags = mention.caseSensitive ? '' : 'i';
|
||||
const pattern = new RegExp(`${escapeRegex(mention.key)}\\.?`, flags);
|
||||
|
||||
if (mention.caseSensitive) {
|
||||
return mention.key === mentionName;
|
||||
}
|
||||
|
||||
return mention.key.toLowerCase() === mentionName.toLowerCase();
|
||||
return pattern.test(mentionName);
|
||||
});
|
||||
|
||||
if (!matches) {
|
||||
|
||||
@@ -2751,6 +2751,30 @@ describe('Components.Markdown.transform', () => {
|
||||
}],
|
||||
}],
|
||||
},
|
||||
}, {
|
||||
name: 'Mention followed by a period',
|
||||
input: 'This is a mention for @channel.',
|
||||
mentionKeys: [{key: 'channel'}],
|
||||
expected: {
|
||||
type: 'document',
|
||||
children: [{
|
||||
type: 'paragraph',
|
||||
children: [{
|
||||
type: 'text',
|
||||
literal: 'This is a mention for ',
|
||||
}, {
|
||||
type: 'mention_highlight',
|
||||
children: [{
|
||||
type: 'at_mention',
|
||||
_mentionName: 'channel.',
|
||||
children: [{
|
||||
type: 'text',
|
||||
literal: '@channel.',
|
||||
}],
|
||||
}],
|
||||
}],
|
||||
}],
|
||||
},
|
||||
}];
|
||||
|
||||
for (const test of tests) {
|
||||
@@ -3055,7 +3079,7 @@ function nodeToString(node) {
|
||||
}
|
||||
|
||||
const ignoredKeys = {_sourcepos: true, _lastLineBlank: true, _open: true, _string_content: true, _info: true, _isFenced: true, _fenceChar: true, _fenceLength: true, _fenceOffset: true, _onEnter: true, _onExit: true};
|
||||
function astToJson(node, visited = [], indent = '') { // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
function astToJson(node, visited = [], indent = '') {
|
||||
let out = '{';
|
||||
|
||||
const myVisited = [...visited];
|
||||
@@ -3081,7 +3105,7 @@ function astToJson(node, visited = [], indent = '') { // eslint-disable-line @ty
|
||||
} else if (typeof value === 'boolean') {
|
||||
out += String(value);
|
||||
} else if (typeof value === 'object') {
|
||||
out += astToJson(value, myVisited, indent + ' ');
|
||||
out += astToJson(value, myVisited, indent + ' '); // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
}
|
||||
|
||||
if (i !== keys.length - 1) {
|
||||
|
||||
@@ -11,12 +11,14 @@ import {ViewTypes} from '@constants';
|
||||
|
||||
import NetworkIndicator from './network_indicator';
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
describe('AttachmentFooter', () => {
|
||||
Animated.sequence = jest.fn(() => ({
|
||||
start: jest.fn((cb) => cb()),
|
||||
}));
|
||||
Animated.timing = jest.fn(() => ({
|
||||
start: jest.fn(),
|
||||
start: jest.fn((cb) => cb()),
|
||||
}));
|
||||
|
||||
const baseProps = {
|
||||
@@ -43,7 +45,7 @@ describe('AttachmentFooter', () => {
|
||||
const wrapper = shallow(<NetworkIndicator {...baseProps}/>);
|
||||
const instance = wrapper.instance();
|
||||
|
||||
it('emits INDICATOR_BAR_VISIBLE with true only if not already visible', () => {
|
||||
it('emits INDICATOR_BAR_VISIBLE with true only if not already visible', async () => {
|
||||
instance.visible = true;
|
||||
instance.show();
|
||||
expect(EventEmitter.emit).not.toHaveBeenCalled();
|
||||
@@ -52,6 +54,7 @@ describe('AttachmentFooter', () => {
|
||||
instance.show();
|
||||
expect(EventEmitter.emit).toHaveBeenCalledWith(ViewTypes.INDICATOR_BAR_VISIBLE, true);
|
||||
expect(instance.visible).toBe(true);
|
||||
expect(wrapper.state('opacity')).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -60,7 +63,7 @@ describe('AttachmentFooter', () => {
|
||||
const wrapper = shallow(<NetworkIndicator {...baseProps}/>);
|
||||
const instance = wrapper.instance();
|
||||
|
||||
it('emits INDICATOR_BAR_VISIBLE with false only if visible', () => {
|
||||
it('emits INDICATOR_BAR_VISIBLE with false only if visible', async () => {
|
||||
instance.visible = false;
|
||||
instance.connected();
|
||||
expect(EventEmitter.emit).not.toHaveBeenCalled();
|
||||
@@ -69,6 +72,7 @@ describe('AttachmentFooter', () => {
|
||||
instance.connected();
|
||||
expect(EventEmitter.emit).toHaveBeenCalledWith(ViewTypes.INDICATOR_BAR_VISIBLE, false);
|
||||
expect(instance.visible).toBe(false);
|
||||
expect(wrapper.state('opacity')).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`PasteableTextInput should render pasteable text input 1`] = `
|
||||
<TextInput
|
||||
<Component
|
||||
allowFontScaling={true}
|
||||
onPaste={[Function]}
|
||||
rejectResponderTermination={true}
|
||||
underlineColorAndroid="transparent"
|
||||
>
|
||||
My Text
|
||||
</TextInput>
|
||||
</Component>
|
||||
`;
|
||||
|
||||
@@ -33,6 +33,7 @@ const POST_TIMEOUT = 20000;
|
||||
export function makeMapStateToProps() {
|
||||
const memoizeHasEmojisOnly = memoizeResult((message, customEmojis) => hasEmojisOnly(message, customEmojis));
|
||||
const getReactionsForPost = makeGetReactionsForPost();
|
||||
const getMentionKeysForPost = makeGetMentionKeysForPost();
|
||||
|
||||
return (state, ownProps) => {
|
||||
const post = ownProps.post;
|
||||
@@ -104,7 +105,7 @@ export function makeMapStateToProps() {
|
||||
isEmojiOnly,
|
||||
shouldRenderJumboEmoji,
|
||||
theme: getTheme(state),
|
||||
mentionKeys: makeGetMentionKeysForPost(state, postProps?.disable_group_highlight, postProps?.mentionHighlightDisabled),
|
||||
mentionKeys: getMentionKeysForPost(state, channel, postProps?.disable_group_highlight, postProps?.mentionHighlightDisabled),
|
||||
canDelete,
|
||||
...getDimensions(state),
|
||||
};
|
||||
|
||||
@@ -238,9 +238,10 @@ export default class PostBodyAdditionalContent extends ImageViewPort {
|
||||
};
|
||||
|
||||
playYouTubeVideo = () => {
|
||||
const {link} = this.props;
|
||||
const videoId = getYouTubeVideoId(link);
|
||||
const startTime = this.getYouTubeTime(link);
|
||||
const {expandedLink, link} = this.props;
|
||||
const videoLink = expandedLink || link;
|
||||
const videoId = getYouTubeVideoId(videoLink);
|
||||
const startTime = this.getYouTubeTime(videoLink);
|
||||
|
||||
if (Platform.OS === 'ios') {
|
||||
YouTubeStandaloneIOS.
|
||||
@@ -258,7 +259,7 @@ export default class PostBodyAdditionalContent extends ImageViewPort {
|
||||
startTime,
|
||||
}).catch(this.playYouTubeVideoError);
|
||||
} else {
|
||||
Linking.openURL(link);
|
||||
Linking.openURL(videoLink);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -69,6 +69,7 @@ export function mapStateToProps(state, ownProps) {
|
||||
state,
|
||||
{
|
||||
channel: currentChannel.id,
|
||||
team: currentChannel.team_id,
|
||||
permission: Permissions.USE_CHANNEL_MENTIONS,
|
||||
default: true,
|
||||
},
|
||||
@@ -82,6 +83,7 @@ export function mapStateToProps(state, ownProps) {
|
||||
channel: currentChannel.id,
|
||||
team: currentChannel.team_id,
|
||||
permission: Permissions.USE_GROUP_MENTIONS,
|
||||
default: true,
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -238,9 +238,12 @@ export default class PostDraft extends PureComponent {
|
||||
);
|
||||
};
|
||||
|
||||
doSubmitMessage = () => {
|
||||
doSubmitMessage = (message = null) => {
|
||||
const {createPost, currentUserId, channelId, files, handleClearFiles, rootId} = this.props;
|
||||
const value = this.input.current?.getValue() || '';
|
||||
let value = message;
|
||||
if (!value) {
|
||||
value = this.input.current?.getValue() || '';
|
||||
}
|
||||
const postFiles = files.filter((f) => !f.failed);
|
||||
const post = {
|
||||
user_id: currentUserId,
|
||||
@@ -329,10 +332,10 @@ export default class PostDraft extends PureComponent {
|
||||
return;
|
||||
}
|
||||
|
||||
const value = this.input.current.getValue();
|
||||
this.input.current.resetTextInput();
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
const value = this.input.current.getValue();
|
||||
if (!this.isSendButtonEnabled()) {
|
||||
this.input.current.setValue(value);
|
||||
return;
|
||||
@@ -373,12 +376,12 @@ export default class PostDraft extends PureComponent {
|
||||
onPress: () => {
|
||||
// Remove only failed files
|
||||
handleClearFailedFiles(channelId, rootId);
|
||||
this.sendMessage();
|
||||
this.sendMessage(value);
|
||||
},
|
||||
}],
|
||||
);
|
||||
} else {
|
||||
this.sendMessage();
|
||||
this.sendMessage(value);
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -487,8 +490,7 @@ export default class PostDraft extends PureComponent {
|
||||
return {groupMentionsSet, memberNotifyCount, channelTimezoneCount};
|
||||
}
|
||||
|
||||
sendMessage = () => {
|
||||
const value = this.input.current?.getValue() || '';
|
||||
sendMessage = (value = '') => {
|
||||
const {enableConfirmNotificationsToChannel, membersCount, useGroupMentions, useChannelMentions} = this.props;
|
||||
const notificationsToChannel = enableConfirmNotificationsToChannel && useChannelMentions;
|
||||
const notificationsToGroups = enableConfirmNotificationsToChannel && useGroupMentions;
|
||||
@@ -504,10 +506,10 @@ export default class PostDraft extends PureComponent {
|
||||
if (memberNotifyCount > 0) {
|
||||
this.showSendToGroupsAlert(Array.from(groupMentionsSet), memberNotifyCount, channelTimezoneCount, value);
|
||||
} else {
|
||||
this.doSubmitMessage();
|
||||
this.doSubmitMessage(value);
|
||||
}
|
||||
} else {
|
||||
this.doSubmitMessage();
|
||||
this.doSubmitMessage(value);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -13,6 +13,8 @@ jest.mock('react-native-image-picker', () => ({
|
||||
launchCamera: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
describe('PostDraft', () => {
|
||||
const baseProps = {
|
||||
addReactionToLatestPost: jest.fn(),
|
||||
@@ -95,10 +97,12 @@ describe('PostDraft', () => {
|
||||
getValue: () => message,
|
||||
setValue: jest.fn(),
|
||||
changeDraft: jest.fn(),
|
||||
resetTextInput: jest.fn(),
|
||||
},
|
||||
};
|
||||
|
||||
instance.sendMessage();
|
||||
instance.handleSendMessage();
|
||||
jest.runOnlyPendingTimers();
|
||||
expect(Alert.alert).toBeCalled();
|
||||
expect(Alert.alert).toHaveBeenCalledWith('Confirm sending notifications to entire channel', expect.anything(), expect.anything());
|
||||
});
|
||||
@@ -118,9 +122,11 @@ describe('PostDraft', () => {
|
||||
getValue: () => message,
|
||||
setValue: jest.fn(),
|
||||
changeDraft: jest.fn(),
|
||||
resetTextInput: jest.fn(),
|
||||
},
|
||||
};
|
||||
instance.sendMessage();
|
||||
instance.handleSendMessage();
|
||||
jest.runOnlyPendingTimers();
|
||||
expect(Alert.alert).toBeCalled();
|
||||
});
|
||||
|
||||
@@ -139,10 +145,12 @@ describe('PostDraft', () => {
|
||||
getValue: () => message,
|
||||
setValue: jest.fn(),
|
||||
changeDraft: jest.fn(),
|
||||
resetTextInput: jest.fn(),
|
||||
},
|
||||
};
|
||||
|
||||
instance.sendMessage();
|
||||
instance.handleSendMessage();
|
||||
jest.runOnlyPendingTimers();
|
||||
expect(Alert.alert).not.toBeCalled();
|
||||
});
|
||||
|
||||
@@ -162,10 +170,12 @@ describe('PostDraft', () => {
|
||||
getValue: () => message,
|
||||
setValue: jest.fn(),
|
||||
changeDraft: jest.fn(),
|
||||
resetTextInput: jest.fn(),
|
||||
},
|
||||
};
|
||||
|
||||
instance.sendMessage();
|
||||
instance.handleSendMessage();
|
||||
jest.runOnlyPendingTimers();
|
||||
expect(Alert.alert).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -185,10 +195,12 @@ describe('PostDraft', () => {
|
||||
getValue: () => message,
|
||||
setValue: jest.fn(),
|
||||
changeDraft: jest.fn(),
|
||||
resetTextInput: jest.fn(),
|
||||
},
|
||||
};
|
||||
|
||||
instance.sendMessage();
|
||||
instance.handleSendMessage();
|
||||
jest.runOnlyPendingTimers();
|
||||
expect(Alert.alert).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
||||
@@ -68,8 +68,10 @@ export default class FileQuickAction extends PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
// Decode file uri to get the actual path
|
||||
res.uri = decodeURIComponent(res.uri);
|
||||
if (Platform.OS === 'ios') {
|
||||
// Decode file uri to get the actual path
|
||||
res.uri = decodeURIComponent(res.uri);
|
||||
}
|
||||
|
||||
this.props.onUploadFiles([res]);
|
||||
} catch (error) {
|
||||
|
||||
@@ -46,7 +46,7 @@ exports[`PostList setting channel deep link 1`] = `
|
||||
onScrollToIndexFailed={[Function]}
|
||||
onViewableItemsChanged={null}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
<RefreshControlMock
|
||||
colors={
|
||||
Array [
|
||||
"#3d3c40",
|
||||
@@ -123,7 +123,7 @@ exports[`PostList setting permalink deep link 1`] = `
|
||||
onScrollToIndexFailed={[Function]}
|
||||
onViewableItemsChanged={null}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
<RefreshControlMock
|
||||
colors={
|
||||
Array [
|
||||
"#3d3c40",
|
||||
@@ -200,7 +200,7 @@ exports[`PostList should match snapshot 1`] = `
|
||||
onScrollToIndexFailed={[Function]}
|
||||
onViewableItemsChanged={null}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
<RefreshControlMock
|
||||
colors={
|
||||
Array [
|
||||
"#3d3c40",
|
||||
|
||||
@@ -30,6 +30,7 @@ export default class ProgressiveImage extends PureComponent {
|
||||
|
||||
static defaultProps = {
|
||||
style: {},
|
||||
defaultSource: undefined,
|
||||
resizeMode: 'contain',
|
||||
};
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ describe('ProgressiveImage', () => {
|
||||
resizeMode: 'contain',
|
||||
theme: Preferences.THEMES.default,
|
||||
tintDefaultSource: false,
|
||||
defaultSource: null,
|
||||
};
|
||||
|
||||
const wrapper = shallow(<ProgressiveImage {...baseProps}/>);
|
||||
@@ -50,6 +51,7 @@ describe('ProgressiveImage', () => {
|
||||
resizeMode: 'contain',
|
||||
theme: Preferences.THEMES.default,
|
||||
tintDefaultSource: false,
|
||||
defaultSource: null,
|
||||
};
|
||||
|
||||
const wrapper = shallow(<ProgressiveImage {...baseProps}/>);
|
||||
|
||||
@@ -10,10 +10,12 @@ import Preferences from '@mm-redux/constants/preferences';
|
||||
|
||||
describe('Reactions', () => {
|
||||
const baseProps = {
|
||||
theme: Preferences.THEMES.default,
|
||||
recentEmojis: [],
|
||||
addReaction: jest.fn(),
|
||||
deviceWidth: undefined,
|
||||
isLandscape: false,
|
||||
openReactionScreen: jest.fn(),
|
||||
recentEmojis: [],
|
||||
theme: Preferences.THEMES.default,
|
||||
};
|
||||
|
||||
test('Should match snapshot with default emojis', () => {
|
||||
|
||||
@@ -125,7 +125,7 @@ describe('SafeAreaIos', () => {
|
||||
expect(wrapper.state().safeAreaInsets).not.toEqual(TEST_INSETS_1.safeAreaInsets);
|
||||
});
|
||||
|
||||
test('should set safe area insets on change if mounted and DeviceTypes.IS_IPHONE_WITH_INSETS is true', () => {
|
||||
test('should set safe area insets on change if mounted and DeviceTypes.IS_IPHONE_WITH_INSETS is true', async () => {
|
||||
DeviceTypes.IS_IPHONE_WITH_INSETS = true;
|
||||
mattermostManaged.hasSafeAreaInsets = false;
|
||||
|
||||
@@ -140,7 +140,7 @@ describe('SafeAreaIos', () => {
|
||||
expect(wrapper.state().safeAreaInsets).toEqual(TEST_INSETS_2.safeAreaInsets);
|
||||
});
|
||||
|
||||
test('should set safe area insets on change if mounted and mattermostManaged.hasSafeAreaInsets is true', () => {
|
||||
test('should set safe area insets on change if mounted and mattermostManaged.hasSafeAreaInsets is true', async () => {
|
||||
DeviceTypes.IS_IPHONE_WITH_INSETS = false;
|
||||
mattermostManaged.hasSafeAreaInsets = true;
|
||||
|
||||
@@ -155,7 +155,7 @@ describe('SafeAreaIos', () => {
|
||||
expect(wrapper.state().safeAreaInsets).toEqual(TEST_INSETS_2.safeAreaInsets);
|
||||
});
|
||||
|
||||
test('should not set safe area insets on change if mounted and neither DeviceTypes.IS_IPHONE_WITH_INSETS nor mattermostManaged.hasSafeAreaInsets is true', () => {
|
||||
test('should not set safe area insets on change if mounted and neither DeviceTypes.IS_IPHONE_WITH_INSETS nor mattermostManaged.hasSafeAreaInsets is true', async () => {
|
||||
DeviceTypes.IS_IPHONE_WITH_INSETS = false;
|
||||
mattermostManaged.hasSafeAreaInsets = false;
|
||||
|
||||
@@ -170,7 +170,7 @@ describe('SafeAreaIos', () => {
|
||||
expect(wrapper.state().safeAreaInsets).not.toEqual(TEST_INSETS_2.safeAreaInsets);
|
||||
});
|
||||
|
||||
test('should set safe area insets on change not mounted', () => {
|
||||
test('should set safe area insets on change not mounted', async () => {
|
||||
DeviceTypes.IS_IPHONE_WITH_INSETS = true;
|
||||
mattermostManaged.hasSafeAreaInsets = true;
|
||||
|
||||
@@ -186,7 +186,7 @@ describe('SafeAreaIos', () => {
|
||||
expect(wrapper.state().safeAreaInsets).not.toEqual(TEST_INSETS_2.safeAreaInsets);
|
||||
});
|
||||
|
||||
test('should set portrait safe area insets', () => {
|
||||
test('should set portrait safe area insets', async () => {
|
||||
const wrapper = shallow(
|
||||
<SafeAreaIos {...baseProps}/>,
|
||||
);
|
||||
@@ -203,7 +203,7 @@ describe('SafeAreaIos', () => {
|
||||
expect(EphemeralStore.safeAreaInsets[LANDSCAPE]).toEqual(null);
|
||||
});
|
||||
|
||||
test('should set portrait safe area insets from EphemeralStore', () => {
|
||||
test('should set portrait safe area insets from EphemeralStore', async () => {
|
||||
const wrapper = shallow(
|
||||
<SafeAreaIos {...baseProps}/>,
|
||||
);
|
||||
@@ -236,7 +236,7 @@ describe('SafeAreaIos', () => {
|
||||
expect(EphemeralStore.safeAreaInsets[PORTRAIT]).toEqual(null);
|
||||
});
|
||||
|
||||
test('should set landscape safe area insets from EphemeralStore', () => {
|
||||
test('should set landscape safe area insets from EphemeralStore', async () => {
|
||||
const wrapper = shallow(
|
||||
<SafeAreaIos {...baseProps}/>,
|
||||
);
|
||||
@@ -252,7 +252,7 @@ describe('SafeAreaIos', () => {
|
||||
expect(EphemeralStore.safeAreaInsets[PORTRAIT]).toEqual(null);
|
||||
});
|
||||
|
||||
test('should add safeAreaInsetsForRootViewDidChange listener when EphemeralStore values are not set', () => {
|
||||
test('should add safeAreaInsetsForRootViewDidChange listener when EphemeralStore values are not set', async () => {
|
||||
const addEventListener = jest.spyOn(SafeArea, 'addEventListener');
|
||||
|
||||
expect(EphemeralStore.safeAreaInsets[PORTRAIT]).toEqual(null);
|
||||
@@ -281,7 +281,7 @@ describe('SafeAreaIos', () => {
|
||||
expect(addEventListener).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should remove safeAreaInsetsForRootViewDidChange listener when EphemeralStore values are set', () => {
|
||||
test('should remove safeAreaInsetsForRootViewDidChange listener when EphemeralStore values are set', async () => {
|
||||
const removeEventListener = jest.spyOn(SafeArea, 'removeEventListener');
|
||||
|
||||
const wrapper = shallow(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ChannelItem should match snapshot 1`] = `
|
||||
<Component
|
||||
<ForwardRef
|
||||
onPress={[Function]}
|
||||
underlayColor="rgba(69,120,191,0.5)"
|
||||
>
|
||||
@@ -99,11 +99,11 @@ exports[`ChannelItem should match snapshot 1`] = `
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</Component>
|
||||
</ForwardRef>
|
||||
`;
|
||||
|
||||
exports[`ChannelItem should match snapshot for current user i.e currentUser (you) 1`] = `
|
||||
<Component
|
||||
<ForwardRef
|
||||
onPress={[Function]}
|
||||
underlayColor="rgba(69,120,191,0.5)"
|
||||
>
|
||||
@@ -211,11 +211,11 @@ exports[`ChannelItem should match snapshot for current user i.e currentUser (you
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</Component>
|
||||
</ForwardRef>
|
||||
`;
|
||||
|
||||
exports[`ChannelItem should match snapshot for current user i.e currentUser (you) when isSearchResult 1`] = `
|
||||
<Component
|
||||
<ForwardRef
|
||||
onPress={[Function]}
|
||||
underlayColor="rgba(69,120,191,0.5)"
|
||||
>
|
||||
@@ -323,11 +323,11 @@ exports[`ChannelItem should match snapshot for current user i.e currentUser (you
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</Component>
|
||||
</ForwardRef>
|
||||
`;
|
||||
|
||||
exports[`ChannelItem should match snapshot for deactivated user and is currentChannel 1`] = `
|
||||
<Component
|
||||
<ForwardRef
|
||||
onPress={[Function]}
|
||||
underlayColor="rgba(69,120,191,0.5)"
|
||||
>
|
||||
@@ -435,11 +435,11 @@ exports[`ChannelItem should match snapshot for deactivated user and is currentCh
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</Component>
|
||||
</ForwardRef>
|
||||
`;
|
||||
|
||||
exports[`ChannelItem should match snapshot for deactivated user and is searchResult 1`] = `
|
||||
<Component
|
||||
<ForwardRef
|
||||
onPress={[Function]}
|
||||
underlayColor="rgba(69,120,191,0.5)"
|
||||
>
|
||||
@@ -537,11 +537,11 @@ exports[`ChannelItem should match snapshot for deactivated user and is searchRes
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</Component>
|
||||
</ForwardRef>
|
||||
`;
|
||||
|
||||
exports[`ChannelItem should match snapshot for deactivated user and not searchResults or currentChannel 1`] = `
|
||||
<Component
|
||||
<ForwardRef
|
||||
onPress={[Function]}
|
||||
underlayColor="rgba(69,120,191,0.5)"
|
||||
>
|
||||
@@ -639,11 +639,11 @@ exports[`ChannelItem should match snapshot for deactivated user and not searchRe
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</Component>
|
||||
</ForwardRef>
|
||||
`;
|
||||
|
||||
exports[`ChannelItem should match snapshot for isManualUnread 1`] = `
|
||||
<Component
|
||||
<ForwardRef
|
||||
onPress={[Function]}
|
||||
underlayColor="rgba(69,120,191,0.5)"
|
||||
>
|
||||
@@ -741,7 +741,7 @@ exports[`ChannelItem should match snapshot for isManualUnread 1`] = `
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</Component>
|
||||
</ForwardRef>
|
||||
`;
|
||||
|
||||
exports[`ChannelItem should match snapshot for no displayName 1`] = `null`;
|
||||
@@ -749,7 +749,7 @@ exports[`ChannelItem should match snapshot for no displayName 1`] = `null`;
|
||||
exports[`ChannelItem should match snapshot for showUnreadForMsgs 1`] = `null`;
|
||||
|
||||
exports[`ChannelItem should match snapshot with draft 1`] = `
|
||||
<Component
|
||||
<ForwardRef
|
||||
onPress={[Function]}
|
||||
underlayColor="rgba(69,120,191,0.5)"
|
||||
>
|
||||
@@ -847,11 +847,11 @@ exports[`ChannelItem should match snapshot with draft 1`] = `
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</Component>
|
||||
</ForwardRef>
|
||||
`;
|
||||
|
||||
exports[`ChannelItem should match snapshot with mentions and muted 1`] = `
|
||||
<Component
|
||||
<ForwardRef
|
||||
onPress={[Function]}
|
||||
underlayColor="rgba(69,120,191,0.5)"
|
||||
>
|
||||
@@ -982,5 +982,5 @@ exports[`ChannelItem should match snapshot with mentions and muted 1`] = `
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</Component>
|
||||
</ForwardRef>
|
||||
`;
|
||||
|
||||
@@ -50,7 +50,7 @@ export default class MainSidebarIOS extends MainSidebarBase {
|
||||
if (this.state.drawerOpened && this.drawerRef?.current) {
|
||||
this.drawerRef.current.closeDrawer();
|
||||
} else if (this.drawerSwiper && DeviceTypes.IS_TABLET) {
|
||||
this.resetDrawer(true);
|
||||
this.resetDrawer();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -156,6 +156,9 @@ export default class MainSidebarBase extends Component {
|
||||
|
||||
onSearchEnds = () => {
|
||||
this.setState({searching: false});
|
||||
if (this.drawerRef?.current) {
|
||||
this.drawerRef.current.canClose = true;
|
||||
}
|
||||
};
|
||||
|
||||
onSearchStart = () => {
|
||||
|
||||
@@ -285,9 +285,9 @@ export default class SlideUpPanel extends PureComponent {
|
||||
>
|
||||
<Animated.View>
|
||||
<SlideUpPanelIndicator/>
|
||||
{headerComponent}
|
||||
</Animated.View>
|
||||
</PanGestureHandler>
|
||||
{headerComponent}
|
||||
<PanGestureHandler
|
||||
ref={this.panRef}
|
||||
simultaneousHandlers={[this.scrollRef, this.masterRef]}
|
||||
|
||||
@@ -47,7 +47,7 @@ exports[`components/widgets/settings/RadioSetting should match snapshot when err
|
||||
]
|
||||
}
|
||||
>
|
||||
<Component
|
||||
<ForwardRef
|
||||
onPress={[Function]}
|
||||
>
|
||||
<View
|
||||
@@ -87,8 +87,8 @@ exports[`components/widgets/settings/RadioSetting should match snapshot when err
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Component>
|
||||
<Component
|
||||
</ForwardRef>
|
||||
<ForwardRef
|
||||
onPress={[Function]}
|
||||
>
|
||||
<View
|
||||
@@ -128,8 +128,8 @@ exports[`components/widgets/settings/RadioSetting should match snapshot when err
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Component>
|
||||
<Component
|
||||
</ForwardRef>
|
||||
<ForwardRef
|
||||
onPress={[Function]}
|
||||
>
|
||||
<View
|
||||
@@ -164,7 +164,7 @@ exports[`components/widgets/settings/RadioSetting should match snapshot when err
|
||||
width={12}
|
||||
/>
|
||||
</View>
|
||||
</Component>
|
||||
</ForwardRef>
|
||||
</View>
|
||||
<View
|
||||
style={null}
|
||||
|
||||
@@ -18,4 +18,6 @@ const NavigationTypes = keyMirror({
|
||||
BLUR_POST_DRAFT: null,
|
||||
});
|
||||
|
||||
NavigationTypes.CHANNEL_SCREEN = 'Channel';
|
||||
|
||||
export default NavigationTypes;
|
||||
|
||||
@@ -20,10 +20,15 @@ import mattermostManaged from 'app/mattermost_managed';
|
||||
|
||||
export const HEADER_X_CLUSTER_ID = 'X-Cluster-Id';
|
||||
export const HEADER_TOKEN = 'Token';
|
||||
const DEFAULT_TIMEOUT = 10000;
|
||||
|
||||
let managedConfig;
|
||||
|
||||
mattermostManaged.addEventListener('managedConfigDidChange', (config) => {
|
||||
if (config?.timeout !== managedConfig?.timeout) {
|
||||
initFetchConfig();
|
||||
return;
|
||||
}
|
||||
managedConfig = config;
|
||||
});
|
||||
|
||||
@@ -150,11 +155,16 @@ Client4.doFetchWithResponse = async (url, options) => {
|
||||
const initFetchConfig = async () => {
|
||||
const fetchConfig = {
|
||||
auto: true,
|
||||
timeout: 5000, // Set the base timeout for every request to 5s
|
||||
timeout: DEFAULT_TIMEOUT, // Set the base timeout for every request to 5s
|
||||
};
|
||||
|
||||
try {
|
||||
managedConfig = await mattermostManaged.getConfig();
|
||||
|
||||
if (managedConfig?.timeout) {
|
||||
const timeout = parseInt(managedConfig.timeout, 10);
|
||||
fetchConfig.timeout = timeout || DEFAULT_TIMEOUT;
|
||||
}
|
||||
} catch {
|
||||
// no managed config
|
||||
}
|
||||
|
||||
@@ -124,7 +124,9 @@ export function componentDidAppearListener({componentId}) {
|
||||
}
|
||||
|
||||
export function componentDidDisappearListener({componentId}) {
|
||||
EphemeralStore.removeNavigationComponentId(componentId);
|
||||
if (componentId !== NavigationTypes.CHANNEL_SCREEN) {
|
||||
EphemeralStore.removeNavigationComponentId(componentId);
|
||||
}
|
||||
|
||||
if (componentId === 'MainSidebar') {
|
||||
EventEmitter.emit(NavigationTypes.MAIN_SIDEBAR_DID_CLOSE);
|
||||
|
||||
@@ -3126,6 +3126,7 @@ export class ClientError extends Error {
|
||||
intl: { defaultMessage: string; id: string } | { defaultMessage: string; id: string } | { id: string; defaultMessage: string; values: any } | { id: string; defaultMessage: string };
|
||||
server_error_id: any;
|
||||
status_code: any;
|
||||
details: Error;
|
||||
constructor(baseUrl: string, data: any) {
|
||||
super(data.message + ': ' + cleanUrlForLogging(baseUrl, data.url));
|
||||
|
||||
@@ -3134,6 +3135,7 @@ export class ClientError extends Error {
|
||||
this.intl = data.intl;
|
||||
this.server_error_id = data.server_error_id;
|
||||
this.status_code = data.status_code;
|
||||
this.details = data.details;
|
||||
|
||||
// Ensure message is treated as a property of this class when object spreading. Without this,
|
||||
// copying the object by using `{...error}` would not include the message.
|
||||
|
||||
@@ -44,6 +44,7 @@ export default {
|
||||
RESTRICT_DIRECT_MESSAGE_ANY: 'any',
|
||||
RESTRICT_DIRECT_MESSAGE_TEAM: 'team',
|
||||
SWITCH_TO_DEFAULT_CHANNEL: 'switch_to_default_channel',
|
||||
REMOVED_FROM_CHANNEL: 'removed_from_channel',
|
||||
DEFAULT_CHANNEL: 'town-square',
|
||||
DM_CHANNEL: 'D',
|
||||
OPEN_CHANNEL: 'O',
|
||||
|
||||
@@ -2,15 +2,13 @@
|
||||
// See LICENSE.txt for license information.
|
||||
import * as reselect from 'reselect';
|
||||
import {GlobalState} from '@mm-redux/types/store';
|
||||
import {Dictionary} from '@mm-redux/types/utilities';
|
||||
import {Dictionary, NameMappedObjects} from '@mm-redux/types/utilities';
|
||||
import {Group} from '@mm-redux/types/groups';
|
||||
import {filterGroupsMatchingTerm} from '@mm-redux/utils/group_utils';
|
||||
import {getCurrentUserLocale} from '@mm-redux/selectors/entities/i18n';
|
||||
import {getChannel} from '@mm-redux/selectors/entities/channels';
|
||||
import {UserMentionKey} from '@mm-redux/selectors/entities/users';
|
||||
import {haveIChannelPermission} from '@mm-redux/selectors/entities/roles';
|
||||
import {getTeam} from '@mm-redux/selectors/entities/teams';
|
||||
import {Permissions} from '@mm-redux/constants';
|
||||
|
||||
const emptyList: any[] = [];
|
||||
const emptySyncables = {
|
||||
@@ -73,15 +71,6 @@ export function getAssociatedGroupsForReference(state: GlobalState, teamId: stri
|
||||
const channel = getChannel(state, channelId);
|
||||
const locale = getCurrentUserLocale(state);
|
||||
|
||||
if (!haveIChannelPermission(state, {
|
||||
permission: Permissions.USE_GROUP_MENTIONS,
|
||||
channel: channelId,
|
||||
team: teamId,
|
||||
default: true,
|
||||
})) {
|
||||
return emptyList;
|
||||
}
|
||||
|
||||
let groupsForReference = [];
|
||||
if (team && team.group_constrained && channel && channel.group_constrained) {
|
||||
const groupsFromChannel = getGroupsAssociatedToChannelForReference(state, channelId);
|
||||
@@ -173,24 +162,8 @@ export const getAllAssociatedGroupsForReference = reselect.createSelector(
|
||||
},
|
||||
);
|
||||
|
||||
export const getMyAllowReferencedGroups = reselect.createSelector(
|
||||
getMyGroups,
|
||||
(myGroups) => {
|
||||
return Object.values(myGroups).filter((group) => group.allow_reference && group.delete_at === 0);
|
||||
},
|
||||
);
|
||||
|
||||
export const getCurrentUserGroupMentionKeys = reselect.createSelector(
|
||||
getMyAllowReferencedGroups,
|
||||
(groups: Array<Group>) => {
|
||||
const keys: UserMentionKey[] = [];
|
||||
groups.forEach((group) => keys.push({key: `@${group.name}`}));
|
||||
return keys;
|
||||
},
|
||||
);
|
||||
|
||||
export const getGroupsByName = reselect.createSelector(
|
||||
getAllGroups,
|
||||
export const getAssociatedGroupsByName: (state: GlobalState, teamID: string, channelId: string) => NameMappedObjects<Group> = reselect.createSelector(
|
||||
getAssociatedGroupsForReference,
|
||||
(groups) => {
|
||||
const groupsByName: Dictionary<Group> = {};
|
||||
|
||||
@@ -201,3 +174,49 @@ export const getGroupsByName = reselect.createSelector(
|
||||
return groupsByName;
|
||||
},
|
||||
);
|
||||
|
||||
export const getAllGroupsForReferenceByName: (state: GlobalState) => NameMappedObjects<Group> = reselect.createSelector(
|
||||
getAllAssociatedGroupsForReference,
|
||||
(groups) => {
|
||||
const groupsByName: Dictionary<Group> = {};
|
||||
|
||||
Object.values(groups).forEach((group) => {
|
||||
groupsByName[group.name] = group;
|
||||
});
|
||||
|
||||
return groupsByName;
|
||||
},
|
||||
);
|
||||
|
||||
export const getMyAllowReferencedGroups = reselect.createSelector(
|
||||
getMyGroups,
|
||||
(myGroups) => {
|
||||
return Object.values(myGroups).filter((group) => group.allow_reference && group.delete_at === 0);
|
||||
},
|
||||
);
|
||||
|
||||
export const getMyGroupMentionKeys: (state: GlobalState) => UserMentionKey[] = reselect.createSelector(
|
||||
getMyAllowReferencedGroups,
|
||||
(groups: Array<Group>) => {
|
||||
const keys: UserMentionKey[] = [];
|
||||
groups.forEach((group) => keys.push({key: `@${group.name}`}));
|
||||
return keys;
|
||||
},
|
||||
);
|
||||
|
||||
export const getMyGroupsAssociatedToChannelForReference: (state: GlobalState, teamId: string, channelId: string) => Group[] = reselect.createSelector(
|
||||
getMyGroups,
|
||||
getAssociatedGroupsByName,
|
||||
(myGroups, groups) => {
|
||||
return Object.values(myGroups).filter((group) => group.allow_reference && group.delete_at === 0 && groups[group.name]);
|
||||
},
|
||||
);
|
||||
|
||||
export const getMyGroupMentionKeysForChannel: (state: GlobalState, teamId: string, channelId: string) => UserMentionKey[] = reselect.createSelector(
|
||||
getMyGroupsAssociatedToChannelForReference,
|
||||
(groups: Array<Group>) => {
|
||||
const keys: UserMentionKey[] = [];
|
||||
groups.forEach((group) => keys.push({key: `@${group.name}`}));
|
||||
return keys;
|
||||
},
|
||||
);
|
||||
|
||||
@@ -69,23 +69,20 @@ export const getPostsInCurrentChannel: (a: GlobalState) => Array<PostWithFormatD
|
||||
export function makeGetPostIdsForThread(): (b: GlobalState, a: $ID<Post>) => Array<$ID<Post>> {
|
||||
return createIdsSelector(
|
||||
getAllPosts,
|
||||
(state: GlobalState, rootId: string) => state.entities.posts.postsInThread[rootId] || [],
|
||||
(state: GlobalState, rootId) => state.entities.posts.posts[rootId],
|
||||
(posts, postsForThread, rootPost) => {
|
||||
(state: GlobalState, rootId: string) => state.entities.posts.posts[rootId],
|
||||
(posts, rootPost) => {
|
||||
const thread: Post[] = [];
|
||||
|
||||
if (rootPost) {
|
||||
thread.push(rootPost);
|
||||
}
|
||||
|
||||
postsForThread.forEach((id) => {
|
||||
const post = posts[id];
|
||||
if (post) {
|
||||
thread.push(post);
|
||||
const postsArray = Object.values(posts).filter((p) => p.root_id === rootPost.id);
|
||||
if (postsArray.length) {
|
||||
thread.push(...postsArray);
|
||||
}
|
||||
});
|
||||
|
||||
thread.sort(comparePosts);
|
||||
thread.sort(comparePosts);
|
||||
}
|
||||
|
||||
return thread.map((post) => post.id);
|
||||
},
|
||||
|
||||
@@ -104,7 +104,7 @@ export const getMyCurrentChannelPermissions = reselect.createSelector(
|
||||
for (const permission of roles[roleName].permissions) {
|
||||
permissions.add(permission);
|
||||
}
|
||||
roleFound = true && teamRoleFound;
|
||||
roleFound = teamRoleFound;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -144,8 +144,10 @@ export const getMyChannelPermissions = reselect.createSelector(
|
||||
getMyChannelRoles,
|
||||
getRoles,
|
||||
getMyTeamPermissions,
|
||||
(state, options: PermissionsOptions) => options.channel,
|
||||
(myChannelRoles, roles, {permissions: teamPermissions, roleFound: teamRoleFound}, channelId) => {
|
||||
(state, options: PermissionsOptions) => options,
|
||||
(myChannelRoles, roles, {permissions: teamPermissions, roleFound: teamRoleFound}, options: PermissionsOptions) => {
|
||||
const channelId = options.channel;
|
||||
const teamId = options.team;
|
||||
const permissions = new Set<string>();
|
||||
let roleFound = false;
|
||||
if (myChannelRoles[channelId!]) {
|
||||
@@ -154,7 +156,7 @@ export const getMyChannelPermissions = reselect.createSelector(
|
||||
for (const permission of roles[roleName].permissions) {
|
||||
permissions.add(permission);
|
||||
}
|
||||
roleFound = true && teamRoleFound;
|
||||
roleFound = teamRoleFound || !teamId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,38 +65,104 @@ describe('Selectors.Search', () => {
|
||||
assert.deepEqual(Selectors.getAllUserMentionKeys(state), [{key: 'First', caseSensitive: true}, {key: '@user'}, {key: '@I-AM-THE-BEST!'}, {key: '@Do-you-love-me?'}]);
|
||||
});
|
||||
|
||||
describe('makeGetMentionKeysForPost', () => {
|
||||
it('should return all mentionKeys', () => {
|
||||
const postProps = {
|
||||
disable_group_highlight: false,
|
||||
mentionHighlightDisabled: false,
|
||||
};
|
||||
const state = {
|
||||
entities: {
|
||||
users: {
|
||||
currentUserId: 'a123',
|
||||
profiles: {
|
||||
a123: {
|
||||
username: 'a123',
|
||||
notify_props: {
|
||||
channel: 'true',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
groups: {
|
||||
myGroups: {
|
||||
developers: {
|
||||
id: 123,
|
||||
name: 'developers',
|
||||
allow_reference: true,
|
||||
delete_at: 0,
|
||||
describe('getMentionKeysForPost', () => {
|
||||
const group = {
|
||||
id: 123,
|
||||
name: 'developers',
|
||||
allow_reference: true,
|
||||
delete_at: 0,
|
||||
};
|
||||
|
||||
const group2 = {
|
||||
id: 1234,
|
||||
name: 'developers2',
|
||||
allow_reference: true,
|
||||
delete_at: 0,
|
||||
};
|
||||
|
||||
const channel1 = {...TestHelper.fakeChannelWithId(team1.id), group_constrained: true};
|
||||
const channel2 = {...TestHelper.fakeChannelWithId(team1.id), group_constrained: false};
|
||||
|
||||
const state = {
|
||||
entities: {
|
||||
users: {
|
||||
currentUserId: 'a123',
|
||||
profiles: {
|
||||
a123: {
|
||||
username: 'a123',
|
||||
notify_props: {
|
||||
channel: 'true',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
groups: {
|
||||
syncables: {},
|
||||
members: {},
|
||||
groups: {
|
||||
[group.name]: group,
|
||||
[group2.name]: group2,
|
||||
},
|
||||
myGroups: {
|
||||
[group.name]: group,
|
||||
[group2.name]: group2,
|
||||
},
|
||||
},
|
||||
teams: {
|
||||
teams: {
|
||||
[team1.id]: team1,
|
||||
},
|
||||
groupsAssociatedToTeam: {
|
||||
[team1.id]: {ids: []},
|
||||
},
|
||||
},
|
||||
channels: {
|
||||
channels: {
|
||||
[channel1.id]: channel1,
|
||||
[channel2.id]: channel2,
|
||||
},
|
||||
groupsAssociatedToChannel: {
|
||||
[channel1.id]: {ids: [group.id]},
|
||||
[channel2.id]: {ids: []},
|
||||
},
|
||||
},
|
||||
general: {
|
||||
config: {},
|
||||
},
|
||||
preferences: {
|
||||
myPreferences: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const getMentionKeysForPost = Selectors.makeGetMentionKeysForPost();
|
||||
|
||||
it('should return all mentionKeys for post if null channel given', () => {
|
||||
const postProps = {
|
||||
disable_group_highlight: false,
|
||||
mentionHighlightDisabled: false,
|
||||
};
|
||||
const results = Selectors.makeGetMentionKeysForPost(state, postProps.disable_group_highlight, postProps.mentionHighlightDisabled);
|
||||
const results = getMentionKeysForPost(state, null, postProps.disable_group_highlight, postProps.mentionHighlightDisabled);
|
||||
const expected = [{key: '@channel'}, {key: '@all'}, {key: '@here'}, {key: '@a123'}, {key: '@developers'}, {key: '@developers2'}];
|
||||
assert.deepEqual(results, expected);
|
||||
});
|
||||
|
||||
it('should return all mentionKeys for post made in non group constrained channel', () => {
|
||||
const postProps = {
|
||||
disable_group_highlight: false,
|
||||
mentionHighlightDisabled: false,
|
||||
};
|
||||
const results = getMentionKeysForPost(state, channel2, postProps.disable_group_highlight, postProps.mentionHighlightDisabled);
|
||||
const expected = [{key: '@channel'}, {key: '@all'}, {key: '@here'}, {key: '@a123'}, {key: '@developers'}, {key: '@developers2'}];
|
||||
assert.deepEqual(results, expected);
|
||||
});
|
||||
|
||||
it('should return mentionKeys for post made in group constrained channel', () => {
|
||||
const postProps = {
|
||||
disable_group_highlight: false,
|
||||
mentionHighlightDisabled: false,
|
||||
};
|
||||
const results = getMentionKeysForPost(state, channel1, postProps.disable_group_highlight, postProps.mentionHighlightDisabled);
|
||||
const expected = [{key: '@channel'}, {key: '@all'}, {key: '@here'}, {key: '@a123'}, {key: '@developers'}];
|
||||
assert.deepEqual(results, expected);
|
||||
});
|
||||
@@ -106,32 +172,7 @@ describe('Selectors.Search', () => {
|
||||
disable_group_highlight: true,
|
||||
mentionHighlightDisabled: false,
|
||||
};
|
||||
const state = {
|
||||
entities: {
|
||||
users: {
|
||||
currentUserId: 'a123',
|
||||
profiles: {
|
||||
a123: {
|
||||
username: 'a123',
|
||||
notify_props: {
|
||||
channel: 'true',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
groups: {
|
||||
myGroups: {
|
||||
developers: {
|
||||
id: 123,
|
||||
name: 'developers',
|
||||
allow_reference: true,
|
||||
delete_at: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const results = Selectors.makeGetMentionKeysForPost(state, postProps.disable_group_highlight, postProps.mentionHighlightDisabled);
|
||||
const results = getMentionKeysForPost(state, channel1, postProps.disable_group_highlight, postProps.mentionHighlightDisabled);
|
||||
const expected = [{key: '@channel'}, {key: '@all'}, {key: '@here'}, {key: '@a123'}];
|
||||
assert.deepEqual(results, expected);
|
||||
});
|
||||
@@ -141,32 +182,7 @@ describe('Selectors.Search', () => {
|
||||
disable_group_highlight: false,
|
||||
mentionHighlightDisabled: true,
|
||||
};
|
||||
const state = {
|
||||
entities: {
|
||||
users: {
|
||||
currentUserId: 'a123',
|
||||
profiles: {
|
||||
a123: {
|
||||
username: 'a123',
|
||||
notify_props: {
|
||||
channel: 'true',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
groups: {
|
||||
myGroups: {
|
||||
developers: {
|
||||
id: 123,
|
||||
name: 'developers',
|
||||
allow_reference: true,
|
||||
delete_at: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const results = Selectors.makeGetMentionKeysForPost(state, postProps.disable_group_highlight, postProps.mentionHighlightDisabled);
|
||||
const results = getMentionKeysForPost(state, channel1, postProps.disable_group_highlight, postProps.mentionHighlightDisabled);
|
||||
const expected = [{key: '@a123'}, {key: '@developers'}];
|
||||
assert.deepEqual(results, expected);
|
||||
});
|
||||
@@ -176,32 +192,7 @@ describe('Selectors.Search', () => {
|
||||
disable_group_highlight: true,
|
||||
mentionHighlightDisabled: true,
|
||||
};
|
||||
const state = {
|
||||
entities: {
|
||||
users: {
|
||||
currentUserId: 'a123',
|
||||
profiles: {
|
||||
a123: {
|
||||
username: 'a123',
|
||||
notify_props: {
|
||||
channel: 'true',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
groups: {
|
||||
myGroups: {
|
||||
developers: {
|
||||
id: 123,
|
||||
name: 'developers',
|
||||
allow_reference: true,
|
||||
delete_at: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const results = Selectors.makeGetMentionKeysForPost(state, postProps.disable_group_highlight, postProps.mentionHighlightDisabled);
|
||||
const results = getMentionKeysForPost(state, channel1, postProps.disable_group_highlight, postProps.mentionHighlightDisabled);
|
||||
const expected = [{key: '@a123'}];
|
||||
assert.deepEqual(results, expected);
|
||||
});
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
|
||||
import * as reselect from 'reselect';
|
||||
import {GlobalState} from '@mm-redux/types/store';
|
||||
import {Channel} from '@mm-redux/types/channels';
|
||||
import {UserMentionKey} from './users';
|
||||
import {getCurrentUserMentionKeys} from '@mm-redux/selectors/entities/users';
|
||||
import {getCurrentUserGroupMentionKeys} from '@mm-redux/selectors/entities/groups';
|
||||
import {getMyGroupMentionKeys, getMyGroupMentionKeysForChannel} from '@mm-redux/selectors/entities/groups';
|
||||
|
||||
import {getCurrentTeamId} from '@mm-redux/selectors/entities/teams';
|
||||
|
||||
@@ -19,29 +20,30 @@ export const getCurrentSearchForCurrentTeam = reselect.createSelector(
|
||||
|
||||
export const getAllUserMentionKeys: (state: GlobalState) => UserMentionKey[] = reselect.createSelector(
|
||||
getCurrentUserMentionKeys,
|
||||
(state: GlobalState) => getCurrentUserGroupMentionKeys(state),
|
||||
(state: GlobalState) => getMyGroupMentionKeys(state),
|
||||
(userMentionKeys, groupMentionKeys) => {
|
||||
return userMentionKeys.concat(groupMentionKeys);
|
||||
},
|
||||
);
|
||||
|
||||
export const makeGetMentionKeysForPost: (state: GlobalState, disableGroupHighlight: boolean, mentionHighlightDisabled: boolean) => UserMentionKey[] = reselect.createSelector(
|
||||
getAllUserMentionKeys,
|
||||
getCurrentUserMentionKeys,
|
||||
(state: GlobalState, disableGroupHighlight: boolean) => disableGroupHighlight,
|
||||
(state: GlobalState, disableGroupHighlight: boolean, mentionHighlightDisabled: boolean) => mentionHighlightDisabled,
|
||||
(allMentionKeys, mentionKeysWithoutGroups, disableGroupHighlight = false, mentionHighlightDisabled = false) => {
|
||||
let mentionKeys = allMentionKeys;
|
||||
if (disableGroupHighlight) {
|
||||
mentionKeys = mentionKeysWithoutGroups;
|
||||
}
|
||||
export function makeGetMentionKeysForPost(): (state: GlobalState, channel: Channel, disableGroupHighlight: boolean, mentionHighlightDisabled: boolean) => UserMentionKey[] {
|
||||
return reselect.createSelector(
|
||||
getCurrentUserMentionKeys,
|
||||
(state: GlobalState, channel: Channel) => (channel?.id ? getMyGroupMentionKeysForChannel(state, channel?.team_id, channel?.id) : getMyGroupMentionKeys(state)),
|
||||
(state: GlobalState, channel: Channel, disableGroupHighlight: boolean) => disableGroupHighlight,
|
||||
(state: GlobalState, channel: Channel, disableGroupHighlight: boolean, mentionHighlightDisabled: boolean) => mentionHighlightDisabled,
|
||||
(mentionKeysWithoutGroups, groupMentionKeys, disableGroupHighlight = false, mentionHighlightDisabled = false) => {
|
||||
let mentionKeys = mentionKeysWithoutGroups;
|
||||
if (!disableGroupHighlight) {
|
||||
mentionKeys = mentionKeys.concat(groupMentionKeys);
|
||||
}
|
||||
|
||||
if (mentionHighlightDisabled) {
|
||||
const CHANNEL_MENTIONS = ['@all', '@channel', '@here'];
|
||||
mentionKeys = mentionKeys.filter((value) => !CHANNEL_MENTIONS.includes(value.key));
|
||||
}
|
||||
|
||||
return mentionKeys;
|
||||
},
|
||||
);
|
||||
if (mentionHighlightDisabled) {
|
||||
const CHANNEL_MENTIONS = ['@all', '@channel', '@here'];
|
||||
mentionKeys = mentionKeys.filter((value) => !CHANNEL_MENTIONS.includes(value.key));
|
||||
}
|
||||
|
||||
return mentionKeys;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -29,10 +29,9 @@ type BatchAction = {
|
||||
export type Action = GenericAction | Thunk | BatchAction | ActionFunc;
|
||||
|
||||
export type ActionResult = {
|
||||
data: any;
|
||||
} | {
|
||||
error: any;
|
||||
};
|
||||
data?: any;
|
||||
error?: any;
|
||||
}
|
||||
|
||||
export type DispatchFunc = (action: Action, getState?: GetStateFunc | null) => Promise<ActionResult>;
|
||||
export type ActionFunc = (dispatch: DispatchFunc, getState: GetStateFunc) => Promise<ActionResult|ActionResult[]> | ActionResult;
|
||||
|
||||
@@ -13,3 +13,8 @@ export type GeneralState = {
|
||||
serverVersion: string;
|
||||
timezones: Array<string>;
|
||||
};
|
||||
|
||||
export type FormattedMsg = {
|
||||
id: string;
|
||||
defaultMessage: string;
|
||||
};
|
||||
|
||||
3
app/mm-redux/types/module.d.ts
vendored
3
app/mm-redux/types/module.d.ts
vendored
@@ -2,4 +2,5 @@
|
||||
// See LICENSE.txt for license information.
|
||||
declare module 'gfycat-sdk';
|
||||
declare module 'remote-redux-devtools';
|
||||
declare module 'redux-action-buffer';
|
||||
declare module 'redux-action-buffer';
|
||||
declare module 'react-intl';
|
||||
@@ -62,11 +62,18 @@ export function canDeletePost(state: GlobalState, config: any, license: any, tea
|
||||
const isOwner = isPostOwner(userId, post);
|
||||
|
||||
if (hasNewPermissions(state)) {
|
||||
const canDelete = haveIChannelPermission(state, {team: teamId, channel: channelId, permission: Permissions.DELETE_POST});
|
||||
if (!isOwner) {
|
||||
return canDelete && haveIChannelPermission(state, {team: teamId, channel: channelId, permission: Permissions.DELETE_OTHERS_POSTS});
|
||||
let permissions = [];
|
||||
if (isOwner) {
|
||||
permissions = [Permissions.DELETE_POST];
|
||||
} else {
|
||||
const {serverVersion} = state.entities.general;
|
||||
|
||||
// prior to v5.27, the server used to require delete_own_posts and
|
||||
// delete_others_posts permissions to be able to delete a post by a
|
||||
// different author.
|
||||
permissions = isMinimumServerVersion(serverVersion, 5, 27) ? [Permissions.DELETE_OTHERS_POSTS] : [Permissions.DELETE_POST, Permissions.DELETE_OTHERS_POSTS];
|
||||
}
|
||||
return canDelete;
|
||||
return permissions.every((permission) => haveIChannelPermission(state, {team: teamId, channel: channelId, permission, default: false}));
|
||||
}
|
||||
|
||||
// Backwards compatibility with pre-advanced permissions config settings.
|
||||
@@ -87,11 +94,12 @@ export function canEditPost(state: GlobalState, config: any, license: any, teamI
|
||||
let canEdit = true;
|
||||
|
||||
if (hasNewPermissions(state)) {
|
||||
const {serverVersion} = state.entities.general;
|
||||
let permissions = [];
|
||||
if (isOwner) {
|
||||
permissions = [Permissions.EDIT_POST];
|
||||
} else {
|
||||
const {serverVersion} = state.entities.general;
|
||||
|
||||
// prior to v5.26, the server used to require edit_own_posts and
|
||||
// edit_others_posts permissions to be able to edit a post by a
|
||||
// different author.
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {intlShape} from 'react-intl';
|
||||
import {Animated, Keyboard, StyleSheet} from 'react-native';
|
||||
import {Alert, Animated, Keyboard, StyleSheet} from 'react-native';
|
||||
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
|
||||
import {General} from '@mm-redux/constants';
|
||||
|
||||
import {showModal, showModalOverCurrentContext} from '@actions/navigation';
|
||||
import LocalConfig from '@assets/config';
|
||||
@@ -83,6 +84,7 @@ export default class ChannelBase extends PureComponent {
|
||||
EventEmitter.on(NavigationTypes.BLUR_POST_DRAFT, this.blurPostDraft);
|
||||
EventEmitter.on('leave_team', this.handleLeaveTeam);
|
||||
EventEmitter.on(TYPING_VISIBLE, this.runTypingAnimations);
|
||||
EventEmitter.on(General.REMOVED_FROM_CHANNEL, this.handleRemovedFromChannel);
|
||||
|
||||
if (currentTeamId) {
|
||||
this.loadChannels(currentTeamId);
|
||||
@@ -154,6 +156,7 @@ export default class ChannelBase extends PureComponent {
|
||||
EventEmitter.off(NavigationTypes.BLUR_POST_DRAFT, this.blurPostDraft);
|
||||
EventEmitter.off('leave_team', this.handleLeaveTeam);
|
||||
EventEmitter.off(TYPING_VISIBLE, this.runTypingAnimations);
|
||||
EventEmitter.off(General.REMOVED_FROM_CHANNEL, this.handleRemovedFromChannel);
|
||||
}
|
||||
|
||||
registerTypingAnimation = (animation) => {
|
||||
@@ -203,6 +206,21 @@ export default class ChannelBase extends PureComponent {
|
||||
this.props.actions.selectDefaultTeam();
|
||||
};
|
||||
|
||||
handleRemovedFromChannel = (channelName) => {
|
||||
const {formatMessage} = this.context.intl;
|
||||
|
||||
Alert.alert(
|
||||
formatMessage({
|
||||
id: 'mobile.user_removed.title',
|
||||
defaultMessage: 'Removed from {channelName}',
|
||||
}, {channelName}),
|
||||
formatMessage({
|
||||
id: 'mobile.user_removed.message',
|
||||
defaultMessage: 'You were removed from the channel.',
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
loadChannels = (teamId) => {
|
||||
const {loadChannelsForTeam, selectInitialChannel} = this.props.actions;
|
||||
if (EphemeralStore.getStartFromNotification()) {
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {Alert} from 'react-native';
|
||||
import {shallow} from 'enzyme';
|
||||
import EventEmitter from '@mm-redux/utils/event_emitter';
|
||||
import {General} from '@mm-redux/constants';
|
||||
|
||||
import Preferences from '@mm-redux/constants/preferences';
|
||||
|
||||
@@ -95,4 +98,15 @@ describe('ChannelBase', () => {
|
||||
removeAnimation();
|
||||
expect(instance.typingAnimations).toStrictEqual([]);
|
||||
});
|
||||
|
||||
test('should display an alert when the user is removed from the current channel', () => {
|
||||
const alert = jest.spyOn(Alert, 'alert');
|
||||
shallow(
|
||||
<ChannelBase {...baseProps}/>,
|
||||
{context: {intl: {formatMessage: jest.fn()}}},
|
||||
);
|
||||
|
||||
EventEmitter.emit(General.REMOVED_FROM_CHANNEL);
|
||||
expect(alert).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ChannelDrawerButton should match, full snapshot 1`] = `
|
||||
<Component
|
||||
<ForwardRef
|
||||
accessibilityHint="Opens the channels and teams drawer"
|
||||
accessibilityLabel="Channels and teams"
|
||||
accessibilityRole="button"
|
||||
@@ -36,11 +36,11 @@ exports[`ChannelDrawerButton should match, full snapshot 1`] = `
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</Component>
|
||||
</ForwardRef>
|
||||
`;
|
||||
|
||||
exports[`ChannelDrawerButton should match, full snapshot 2`] = `
|
||||
<Component
|
||||
<ForwardRef
|
||||
accessibilityHint="Opens the channels and teams drawer"
|
||||
accessibilityLabel="Channels and teams"
|
||||
accessibilityRole="button"
|
||||
@@ -104,5 +104,5 @@ exports[`ChannelDrawerButton should match, full snapshot 2`] = `
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</Component>
|
||||
</ForwardRef>
|
||||
`;
|
||||
|
||||
@@ -62,11 +62,14 @@ export default class ChannelNavBar extends PureComponent {
|
||||
}
|
||||
};
|
||||
|
||||
handlePermanentSidebar = () => {
|
||||
handlePermanentSidebar = async () => {
|
||||
if (DeviceTypes.IS_TABLET && this.mounted) {
|
||||
AsyncStorage.getItem(DeviceTypes.PERMANENT_SIDEBAR_SETTINGS).then((enabled) => {
|
||||
try {
|
||||
const enabled = await AsyncStorage.getItem(DeviceTypes.PERMANENT_SIDEBAR_SETTINGS);
|
||||
this.setState({permanentSidebar: enabled === 'true'});
|
||||
});
|
||||
} catch {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -33,12 +33,12 @@ describe('ChannelNavBar', () => {
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should not set the permanentSidebar state if not Tablet', () => {
|
||||
test('should not set the permanentSidebar state if not Tablet', async () => {
|
||||
const wrapper = shallow(
|
||||
<ChannelNavBar {...baseProps}/>,
|
||||
);
|
||||
|
||||
wrapper.instance().handlePermanentSidebar();
|
||||
await wrapper.instance().handlePermanentSidebar();
|
||||
expect(wrapper.state('permanentSidebar')).toBeUndefined();
|
||||
});
|
||||
|
||||
@@ -50,11 +50,10 @@ describe('ChannelNavBar', () => {
|
||||
DeviceTypes.IS_TABLET = true;
|
||||
|
||||
await wrapper.instance().handlePermanentSidebar();
|
||||
|
||||
expect(wrapper.state('permanentSidebar')).toBeDefined();
|
||||
});
|
||||
|
||||
test('drawerButtonVisible appears for android tablets', () => {
|
||||
test('drawerButtonVisible appears for android tablets', async () => {
|
||||
const wrapper = shallow(
|
||||
<ChannelNavBar {...baseProps}/>,
|
||||
);
|
||||
@@ -65,7 +64,7 @@ describe('ChannelNavBar', () => {
|
||||
expect(wrapper.instance().drawerButtonVisible()).toBe(true);
|
||||
});
|
||||
|
||||
test('drawerButtonVisible appears for android phones', () => {
|
||||
test('drawerButtonVisible appears for android phones', async () => {
|
||||
const wrapper = shallow(
|
||||
<ChannelNavBar {...baseProps}/>,
|
||||
);
|
||||
@@ -76,7 +75,7 @@ describe('ChannelNavBar', () => {
|
||||
expect(wrapper.instance().drawerButtonVisible()).toBe(true);
|
||||
});
|
||||
|
||||
test('drawerButtonVisible appears for iOS phones', () => {
|
||||
test('drawerButtonVisible appears for iOS phones', async () => {
|
||||
const wrapper = shallow(
|
||||
<ChannelNavBar {...baseProps}/>,
|
||||
);
|
||||
@@ -87,7 +86,7 @@ describe('ChannelNavBar', () => {
|
||||
expect(wrapper.instance().drawerButtonVisible()).toBe(true);
|
||||
});
|
||||
|
||||
test('drawerButtonVisible appears for iOS tablets with PermanentSidebar at default false, and not in splitview', () => {
|
||||
test('drawerButtonVisible appears for iOS tablets with PermanentSidebar at default false, and not in splitview', async () => {
|
||||
const wrapper = shallow(
|
||||
<ChannelNavBar {...baseProps}/>,
|
||||
);
|
||||
@@ -100,7 +99,7 @@ describe('ChannelNavBar', () => {
|
||||
expect(wrapper.instance().drawerButtonVisible()).toBe(true);
|
||||
});
|
||||
|
||||
test('drawerButtonVisible does not appear for iOS tablets with permanentSidebar enabled', () => {
|
||||
test('drawerButtonVisible does not appear for iOS tablets with permanentSidebar enabled', async () => {
|
||||
const wrapper = shallow(
|
||||
<ChannelNavBar {...baseProps}/>,
|
||||
);
|
||||
@@ -113,7 +112,7 @@ describe('ChannelNavBar', () => {
|
||||
expect(wrapper.instance().drawerButtonVisible()).toBe(false);
|
||||
});
|
||||
|
||||
test('drawerButtonVisible appears for iOS tablets with splitview enabled', () => {
|
||||
test('drawerButtonVisible appears for iOS tablets with splitview enabled', async () => {
|
||||
const wrapper = shallow(
|
||||
<ChannelNavBar {...baseProps}/>,
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ChannelTitle should match snapshot 1`] = `
|
||||
<Component
|
||||
<ForwardRef
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
@@ -47,11 +47,11 @@ exports[`ChannelTitle should match snapshot 1`] = `
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</Component>
|
||||
</ForwardRef>
|
||||
`;
|
||||
|
||||
exports[`ChannelTitle should match snapshot when is DM and has guests and the teammate is the guest (when can show subtitles) 1`] = `
|
||||
<Component
|
||||
<ForwardRef
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
@@ -122,11 +122,11 @@ exports[`ChannelTitle should match snapshot when is DM and has guests and the te
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</Component>
|
||||
</ForwardRef>
|
||||
`;
|
||||
|
||||
exports[`ChannelTitle should match snapshot when is DM and has guests but the teammate is not the guest (when can show subtitles) 1`] = `
|
||||
<Component
|
||||
<ForwardRef
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
@@ -172,11 +172,11 @@ exports[`ChannelTitle should match snapshot when is DM and has guests but the te
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</Component>
|
||||
</ForwardRef>
|
||||
`;
|
||||
|
||||
exports[`ChannelTitle should match snapshot when isSelfDMChannel is true 1`] = `
|
||||
<Component
|
||||
<ForwardRef
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
@@ -230,5 +230,5 @@ exports[`ChannelTitle should match snapshot when isSelfDMChannel is true 1`] = `
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</Component>
|
||||
</ForwardRef>
|
||||
`;
|
||||
|
||||
@@ -35,7 +35,7 @@ class SettingDrawerButton extends PureComponent {
|
||||
|
||||
const icon = (
|
||||
<Icon
|
||||
name='md-more'
|
||||
name='ellipsis-vertical'
|
||||
size={25}
|
||||
color={theme.sidebarHeaderTextColor}
|
||||
/>
|
||||
|
||||
@@ -30,7 +30,7 @@ export default class ChannelPostList extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
loadPostsIfNecessaryWithRetry: PropTypes.func.isRequired,
|
||||
loadThreadIfNecessary: PropTypes.func.isRequired,
|
||||
getPostThread: PropTypes.func.isRequired,
|
||||
increasePostVisibility: PropTypes.func.isRequired,
|
||||
selectPost: PropTypes.func.isRequired,
|
||||
recordLoadTime: PropTypes.func.isRequired,
|
||||
@@ -106,7 +106,7 @@ export default class ChannelPostList extends PureComponent {
|
||||
const rootId = (post.root_id || post.id);
|
||||
|
||||
Keyboard.dismiss();
|
||||
actions.loadThreadIfNecessary(rootId);
|
||||
actions.getPostThread(rootId);
|
||||
actions.selectPost(rootId);
|
||||
|
||||
const screen = 'Thread';
|
||||
|
||||
@@ -12,7 +12,7 @@ describe('ChannelPostList', () => {
|
||||
const baseProps = {
|
||||
actions: {
|
||||
loadPostsIfNecessaryWithRetry: jest.fn(),
|
||||
loadThreadIfNecessary: jest.fn(),
|
||||
getPostThread: jest.fn(),
|
||||
increasePostVisibility: jest.fn(),
|
||||
selectPost: jest.fn(),
|
||||
recordLoadTime: jest.fn(),
|
||||
|
||||
@@ -4,21 +4,20 @@
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {
|
||||
loadPostsIfNecessaryWithRetry,
|
||||
increasePostVisibility,
|
||||
refreshChannelWithRetry,
|
||||
} from '@actions/views/channel';
|
||||
import {getPostThread} from '@actions/views/post';
|
||||
import {recordLoadTime} from 'app/actions/views/root';
|
||||
import {Types} from '@constants';
|
||||
import {selectPost} from '@mm-redux/actions/posts';
|
||||
import {getPostIdsInCurrentChannel} from '@mm-redux/selectors/entities/posts';
|
||||
import {getCurrentChannelId} from '@mm-redux/selectors/entities/channels';
|
||||
import {getCurrentUserId} from '@mm-redux/selectors/entities/users';
|
||||
import {getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
|
||||
import {
|
||||
loadPostsIfNecessaryWithRetry,
|
||||
loadThreadIfNecessary,
|
||||
increasePostVisibility,
|
||||
refreshChannelWithRetry,
|
||||
} from 'app/actions/views/channel';
|
||||
import {recordLoadTime} from 'app/actions/views/root';
|
||||
import {Types} from 'app/constants';
|
||||
import {isLandscape} from 'app/selectors/device';
|
||||
import {isLandscape} from '@selectors/device';
|
||||
|
||||
import ChannelPostList from './channel_post_list';
|
||||
|
||||
@@ -44,7 +43,7 @@ function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
loadPostsIfNecessaryWithRetry,
|
||||
loadThreadIfNecessary,
|
||||
getPostThread,
|
||||
increasePostVisibility,
|
||||
selectPost,
|
||||
recordLoadTime,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`channel_info should match snapshot 1`] = `
|
||||
exports[`channelInfo should match snapshot 1`] = `
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
@@ -75,15 +75,9 @@ exports[`channel_info should match snapshot 1`] = `
|
||||
}
|
||||
>
|
||||
<React.Fragment>
|
||||
<channelInfoRow
|
||||
action={[Function]}
|
||||
defaultMessage="Favorite"
|
||||
detail={false}
|
||||
icon="star-o"
|
||||
<Connect(Favorite)
|
||||
channelId="1234"
|
||||
isLandscape={false}
|
||||
rightArrow={true}
|
||||
shouldRender={true}
|
||||
textId="mobile.routes.channelInfo.favorite"
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
@@ -113,26 +107,8 @@ exports[`channel_info should match snapshot 1`] = `
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
togglable={true}
|
||||
/>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": undefined,
|
||||
"height": 1,
|
||||
"marginHorizontal": 15,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<channelInfoRow
|
||||
action={[Function]}
|
||||
defaultMessage="Mute channel"
|
||||
detail={false}
|
||||
icon="bell-slash-o"
|
||||
isLandscape={false}
|
||||
rightArrow={true}
|
||||
shouldRender={true}
|
||||
textId="channel_notifications.muteChannel.settings"
|
||||
<Separator
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
@@ -162,26 +138,10 @@ exports[`channel_info should match snapshot 1`] = `
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
togglable={true}
|
||||
/>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": undefined,
|
||||
"height": 1,
|
||||
"marginHorizontal": 15,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<channelInfoRow
|
||||
action={[Function]}
|
||||
defaultMessage="Ignore @channel, @here, @all"
|
||||
detail={false}
|
||||
icon="at"
|
||||
<Connect(Mute)
|
||||
channelId="1234"
|
||||
isLandscape={false}
|
||||
rightArrow={true}
|
||||
shouldRender={true}
|
||||
textId="channel_notifications.ignoreChannelMentions.settings"
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
@@ -211,25 +171,9 @@ exports[`channel_info should match snapshot 1`] = `
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
togglable={true}
|
||||
userId="1234"
|
||||
/>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": undefined,
|
||||
"height": 1,
|
||||
"marginHorizontal": 15,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<channelInfoRow
|
||||
action={[Function]}
|
||||
defaultMessage="Pinned Posts"
|
||||
image={1}
|
||||
isLandscape={false}
|
||||
rightArrow={true}
|
||||
shouldRender={true}
|
||||
textId="channel_header.pinnedPosts"
|
||||
<Separator
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
@@ -259,228 +203,202 @@ exports[`channel_info should match snapshot 1`] = `
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
togglable={false}
|
||||
/>
|
||||
<React.Fragment>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": undefined,
|
||||
"height": 1,
|
||||
"marginHorizontal": 15,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<channelInfoRow
|
||||
action={[Function]}
|
||||
defaultMessage="Manage Members"
|
||||
detail={2}
|
||||
icon="users"
|
||||
isLandscape={false}
|
||||
rightArrow={true}
|
||||
shouldRender={true}
|
||||
textId="channel_header.manageMembers"
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
togglable={false}
|
||||
/>
|
||||
</React.Fragment>
|
||||
<React.Fragment>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": undefined,
|
||||
"height": 1,
|
||||
"marginHorizontal": 15,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<channelInfoRow
|
||||
action={[Function]}
|
||||
defaultMessage="Add Members"
|
||||
icon="user-plus"
|
||||
isLandscape={false}
|
||||
rightArrow={true}
|
||||
shouldRender={true}
|
||||
textId="channel_header.addMembers"
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
togglable={false}
|
||||
/>
|
||||
</React.Fragment>
|
||||
<React.Fragment>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": undefined,
|
||||
"height": 1,
|
||||
"marginHorizontal": 15,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<channelInfoRow
|
||||
action={[Function]}
|
||||
defaultMessage="Convert to Private Channel"
|
||||
icon="lock"
|
||||
isLandscape={false}
|
||||
rightArrow={true}
|
||||
shouldRender={true}
|
||||
textId="mobile.channel_info.convert"
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
togglable={false}
|
||||
/>
|
||||
</React.Fragment>
|
||||
<React.Fragment>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": undefined,
|
||||
"height": 1,
|
||||
"marginHorizontal": 15,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<channelInfoRow
|
||||
action={[Function]}
|
||||
defaultMessage="Edit Channel"
|
||||
icon="edit"
|
||||
isLandscape={false}
|
||||
rightArrow={true}
|
||||
shouldRender={true}
|
||||
textId="mobile.channel_info.edit"
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
togglable={false}
|
||||
/>
|
||||
</React.Fragment>
|
||||
</React.Fragment>
|
||||
<React.Fragment>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": undefined,
|
||||
"height": 1,
|
||||
"marginHorizontal": 15,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<channelInfoRow
|
||||
action={[Function]}
|
||||
defaultMessage="Leave Channel"
|
||||
icon="sign-out"
|
||||
<Connect(IgnoreMentions)
|
||||
channelId="1234"
|
||||
isLandscape={false}
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Separator
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Connect(Pinned)
|
||||
channelId="1234"
|
||||
isLandscape={false}
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Connect(ManageMembers)
|
||||
isLandscape={false}
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Connect(AddMembers)
|
||||
isLandscape={false}
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Connect(ConvertPrivate)
|
||||
isLandscape={false}
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Connect(EditChannel)
|
||||
isLandscape={false}
|
||||
rightArrow={true}
|
||||
shouldRender={true}
|
||||
textId="navbar.leave"
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
@@ -510,40 +428,52 @@ exports[`channel_info should match snapshot 1`] = `
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
togglable={false}
|
||||
/>
|
||||
</React.Fragment>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"backgroundColor": "#ffffff",
|
||||
"borderBottomColor": undefined,
|
||||
"borderBottomWidth": 1,
|
||||
"borderTopColor": undefined,
|
||||
"borderTopWidth": 1,
|
||||
},
|
||||
Object {
|
||||
"borderBottomColor": undefined,
|
||||
"borderBottomWidth": 1,
|
||||
"borderTopColor": undefined,
|
||||
"borderTopWidth": 1,
|
||||
"marginTop": 40,
|
||||
},
|
||||
]
|
||||
Object {
|
||||
"marginTop": 40,
|
||||
}
|
||||
}
|
||||
>
|
||||
<channelInfoRow
|
||||
action={[Function]}
|
||||
defaultMessage="Archive Channel"
|
||||
icon="archive"
|
||||
iconColor="#CA3B27"
|
||||
<Connect(Leave)
|
||||
close={[Function]}
|
||||
isLandscape={false}
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Connect(Archive)
|
||||
close={[Function]}
|
||||
isLandscape={false}
|
||||
rightArrow={true}
|
||||
shouldRender={true}
|
||||
textColor="#CA3B27"
|
||||
textId="mobile.routes.channelInfo.delete_channel"
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
@@ -573,7 +503,6 @@ exports[`channel_info should match snapshot 1`] = `
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
togglable={false}
|
||||
/>
|
||||
</View>
|
||||
</ScrollView>
|
||||
|
||||
@@ -92,7 +92,7 @@ exports[`channel_info_header should match snapshot 1`] = `
|
||||
]
|
||||
}
|
||||
>
|
||||
<Component
|
||||
<ForwardRef
|
||||
onLongPress={[Function]}
|
||||
>
|
||||
<View
|
||||
@@ -126,7 +126,7 @@ exports[`channel_info_header should match snapshot 1`] = `
|
||||
Purpose string
|
||||
</Text>
|
||||
</View>
|
||||
</Component>
|
||||
</ForwardRef>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
@@ -138,7 +138,7 @@ exports[`channel_info_header should match snapshot 1`] = `
|
||||
]
|
||||
}
|
||||
>
|
||||
<Component
|
||||
<ForwardRef
|
||||
onLongPress={[Function]}
|
||||
>
|
||||
<View
|
||||
@@ -275,7 +275,7 @@ exports[`channel_info_header should match snapshot 1`] = `
|
||||
value="Header string"
|
||||
/>
|
||||
</View>
|
||||
</Component>
|
||||
</ForwardRef>
|
||||
</View>
|
||||
<Text
|
||||
style={
|
||||
@@ -395,9 +395,12 @@ exports[`channel_info_header should match snapshot when DM and hasGuests and is
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"marginTop": 15,
|
||||
}
|
||||
Array [
|
||||
Object {
|
||||
"marginTop": 15,
|
||||
},
|
||||
null,
|
||||
]
|
||||
}
|
||||
>
|
||||
<View
|
||||
@@ -431,7 +434,7 @@ exports[`channel_info_header should match snapshot when DM and hasGuests and is
|
||||
]
|
||||
}
|
||||
>
|
||||
<Component
|
||||
<ForwardRef
|
||||
onLongPress={[Function]}
|
||||
>
|
||||
<View
|
||||
@@ -465,7 +468,7 @@ exports[`channel_info_header should match snapshot when DM and hasGuests and is
|
||||
Purpose string
|
||||
</Text>
|
||||
</View>
|
||||
</Component>
|
||||
</ForwardRef>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
@@ -477,7 +480,7 @@ exports[`channel_info_header should match snapshot when DM and hasGuests and is
|
||||
]
|
||||
}
|
||||
>
|
||||
<Component
|
||||
<ForwardRef
|
||||
onLongPress={[Function]}
|
||||
>
|
||||
<View
|
||||
@@ -614,7 +617,7 @@ exports[`channel_info_header should match snapshot when DM and hasGuests and is
|
||||
value="Header string"
|
||||
/>
|
||||
</View>
|
||||
</Component>
|
||||
</ForwardRef>
|
||||
</View>
|
||||
<Text
|
||||
style={
|
||||
@@ -742,7 +745,7 @@ exports[`channel_info_header should match snapshot when DM and hasGuests but its
|
||||
]
|
||||
}
|
||||
>
|
||||
<Component
|
||||
<ForwardRef
|
||||
onLongPress={[Function]}
|
||||
>
|
||||
<View
|
||||
@@ -776,7 +779,7 @@ exports[`channel_info_header should match snapshot when DM and hasGuests but its
|
||||
Purpose string
|
||||
</Text>
|
||||
</View>
|
||||
</Component>
|
||||
</ForwardRef>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
@@ -788,7 +791,7 @@ exports[`channel_info_header should match snapshot when DM and hasGuests but its
|
||||
]
|
||||
}
|
||||
>
|
||||
<Component
|
||||
<ForwardRef
|
||||
onLongPress={[Function]}
|
||||
>
|
||||
<View
|
||||
@@ -925,7 +928,7 @@ exports[`channel_info_header should match snapshot when DM and hasGuests but its
|
||||
value="Header string"
|
||||
/>
|
||||
</View>
|
||||
</Component>
|
||||
</ForwardRef>
|
||||
</View>
|
||||
<Text
|
||||
style={
|
||||
@@ -1045,9 +1048,12 @@ exports[`channel_info_header should match snapshot when GM and hasGuests 1`] = `
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"marginTop": 15,
|
||||
}
|
||||
Array [
|
||||
Object {
|
||||
"marginTop": 15,
|
||||
},
|
||||
null,
|
||||
]
|
||||
}
|
||||
>
|
||||
<View
|
||||
@@ -1081,7 +1087,7 @@ exports[`channel_info_header should match snapshot when GM and hasGuests 1`] = `
|
||||
]
|
||||
}
|
||||
>
|
||||
<Component
|
||||
<ForwardRef
|
||||
onLongPress={[Function]}
|
||||
>
|
||||
<View
|
||||
@@ -1115,7 +1121,7 @@ exports[`channel_info_header should match snapshot when GM and hasGuests 1`] = `
|
||||
Purpose string
|
||||
</Text>
|
||||
</View>
|
||||
</Component>
|
||||
</ForwardRef>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
@@ -1127,7 +1133,7 @@ exports[`channel_info_header should match snapshot when GM and hasGuests 1`] = `
|
||||
]
|
||||
}
|
||||
>
|
||||
<Component
|
||||
<ForwardRef
|
||||
onLongPress={[Function]}
|
||||
>
|
||||
<View
|
||||
@@ -1264,7 +1270,7 @@ exports[`channel_info_header should match snapshot when GM and hasGuests 1`] = `
|
||||
value="Header string"
|
||||
/>
|
||||
</View>
|
||||
</Component>
|
||||
</ForwardRef>
|
||||
</View>
|
||||
<Text
|
||||
style={
|
||||
@@ -1392,7 +1398,7 @@ exports[`channel_info_header should match snapshot when is group constrained 1`]
|
||||
]
|
||||
}
|
||||
>
|
||||
<Component
|
||||
<ForwardRef
|
||||
onLongPress={[Function]}
|
||||
>
|
||||
<View
|
||||
@@ -1426,7 +1432,7 @@ exports[`channel_info_header should match snapshot when is group constrained 1`]
|
||||
Purpose string
|
||||
</Text>
|
||||
</View>
|
||||
</Component>
|
||||
</ForwardRef>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
@@ -1438,7 +1444,7 @@ exports[`channel_info_header should match snapshot when is group constrained 1`]
|
||||
]
|
||||
}
|
||||
>
|
||||
<Component
|
||||
<ForwardRef
|
||||
onLongPress={[Function]}
|
||||
>
|
||||
<View
|
||||
@@ -1575,7 +1581,7 @@ exports[`channel_info_header should match snapshot when is group constrained 1`]
|
||||
value="Header string"
|
||||
/>
|
||||
</View>
|
||||
</Component>
|
||||
</ForwardRef>
|
||||
</View>
|
||||
<Text
|
||||
style={
|
||||
@@ -1717,9 +1723,12 @@ exports[`channel_info_header should match snapshot when public channel and hasGu
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"marginTop": 15,
|
||||
}
|
||||
Array [
|
||||
Object {
|
||||
"marginTop": 15,
|
||||
},
|
||||
null,
|
||||
]
|
||||
}
|
||||
>
|
||||
<View
|
||||
@@ -1753,7 +1762,7 @@ exports[`channel_info_header should match snapshot when public channel and hasGu
|
||||
]
|
||||
}
|
||||
>
|
||||
<Component
|
||||
<ForwardRef
|
||||
onLongPress={[Function]}
|
||||
>
|
||||
<View
|
||||
@@ -1787,7 +1796,7 @@ exports[`channel_info_header should match snapshot when public channel and hasGu
|
||||
Purpose string
|
||||
</Text>
|
||||
</View>
|
||||
</Component>
|
||||
</ForwardRef>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
@@ -1799,7 +1808,7 @@ exports[`channel_info_header should match snapshot when public channel and hasGu
|
||||
]
|
||||
}
|
||||
>
|
||||
<Component
|
||||
<ForwardRef
|
||||
onLongPress={[Function]}
|
||||
>
|
||||
<View
|
||||
@@ -1936,7 +1945,7 @@ exports[`channel_info_header should match snapshot when public channel and hasGu
|
||||
value="Header string"
|
||||
/>
|
||||
</View>
|
||||
</Component>
|
||||
</ForwardRef>
|
||||
</View>
|
||||
<Text
|
||||
style={
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ChannelInfo -> Add Members should match snapshot 1`] = `
|
||||
<React.Fragment>
|
||||
<Separator
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<channelInfoRow
|
||||
action={[Function]}
|
||||
defaultMessage="Add Members"
|
||||
icon="user-plus"
|
||||
isLandscape={false}
|
||||
rightArrow={true}
|
||||
shouldRender={true}
|
||||
textId="channel_header.addMembers"
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
togglable={false}
|
||||
/>
|
||||
</React.Fragment>
|
||||
`;
|
||||
55
app/screens/channel_info/add_members/add_members.test.js
Normal file
55
app/screens/channel_info/add_members/add_members.test.js
Normal file
@@ -0,0 +1,55 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {shallowWithIntl} from 'test/intl-test-helper';
|
||||
|
||||
import Preferences from '@mm-redux/constants/preferences';
|
||||
|
||||
import AddMembers from './add_members';
|
||||
|
||||
jest.mock('@utils/theme', () => {
|
||||
const original = jest.requireActual('../../../utils/theme');
|
||||
return {
|
||||
...original,
|
||||
changeOpacity: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
describe('ChannelInfo -> Add Members', () => {
|
||||
const baseProps = {
|
||||
canManageUsers: true,
|
||||
groupConstrained: false,
|
||||
isLandscape: false,
|
||||
theme: Preferences.THEMES.default,
|
||||
};
|
||||
|
||||
test('should match snapshot', () => {
|
||||
const wrapper = shallowWithIntl(
|
||||
<AddMembers
|
||||
{...baseProps}
|
||||
/>,
|
||||
);
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should render null if cannot manage members', () => {
|
||||
const wrapper = shallowWithIntl(
|
||||
<AddMembers
|
||||
{...baseProps}
|
||||
canManageUsers={false}
|
||||
/>,
|
||||
);
|
||||
expect(wrapper.getElement()).toBeNull();
|
||||
});
|
||||
|
||||
test('should render null if channel is constrained to groups', () => {
|
||||
const wrapper = shallowWithIntl(
|
||||
<AddMembers
|
||||
{...baseProps}
|
||||
groupConstrained={true}
|
||||
/>,
|
||||
);
|
||||
expect(wrapper.getElement()).toBeNull();
|
||||
});
|
||||
});
|
||||
55
app/screens/channel_info/add_members/add_members.tsx
Normal file
55
app/screens/channel_info/add_members/add_members.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import {intlShape} from 'react-intl';
|
||||
|
||||
import {goToScreen} from '@actions/navigation';
|
||||
import {Theme} from '@mm-redux/types/preferences';
|
||||
import ChannelInfoRow from '@screens/channel_info/channel_info_row';
|
||||
import Separator from '@screens/channel_info/separator';
|
||||
import {t} from '@utils/i18n';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
|
||||
interface AddMembersProps {
|
||||
canManageUsers: boolean;
|
||||
groupConstrained: boolean;
|
||||
isLandscape: boolean;
|
||||
theme: Theme;
|
||||
}
|
||||
|
||||
export default class AddMembers extends PureComponent<AddMembersProps> {
|
||||
static contextTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
goToChannelAddMembers = preventDoubleTap(() => {
|
||||
const {intl} = this.context;
|
||||
const screen = 'ChannelAddMembers';
|
||||
const title = intl.formatMessage({id: 'channel_header.addMembers', defaultMessage: 'Add Members'});
|
||||
|
||||
goToScreen(screen, title);
|
||||
});
|
||||
|
||||
render() {
|
||||
const {canManageUsers, groupConstrained, isLandscape, theme} = this.props;
|
||||
|
||||
if (canManageUsers && !groupConstrained) {
|
||||
return (
|
||||
<>
|
||||
<Separator theme={theme}/>
|
||||
<ChannelInfoRow
|
||||
action={this.goToChannelAddMembers}
|
||||
defaultMessage='Add Members'
|
||||
icon='user-plus'
|
||||
textId={t('channel_header.addMembers')}
|
||||
theme={theme}
|
||||
isLandscape={isLandscape}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
22
app/screens/channel_info/add_members/index.js
Normal file
22
app/screens/channel_info/add_members/index.js
Normal file
@@ -0,0 +1,22 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {canManageChannelMembers, getCurrentChannel} from '@mm-redux/selectors/entities/channels';
|
||||
import AddMembers from './add_members';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const currentChannel = getCurrentChannel(state);
|
||||
let canManageUsers = currentChannel?.id ? canManageChannelMembers(state) : false;
|
||||
if (currentChannel.group_constrained) {
|
||||
canManageUsers = false;
|
||||
}
|
||||
|
||||
return {
|
||||
canManageUsers,
|
||||
groupConstrained: currentChannel.group_constrained,
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(AddMembers);
|
||||
@@ -0,0 +1,155 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ChannelInfo -> Archive should match snapshot for Archive Channel 1`] = `
|
||||
<React.Fragment>
|
||||
<Separator
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<channelInfoRow
|
||||
action={[Function]}
|
||||
defaultMessage="Archive Channel"
|
||||
icon="archive"
|
||||
iconColor="#CA3B27"
|
||||
isLandscape={false}
|
||||
rightArrow={false}
|
||||
shouldRender={true}
|
||||
textColor="#CA3B27"
|
||||
textId="mobile.routes.channelInfo.delete_channel"
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
togglable={false}
|
||||
/>
|
||||
</React.Fragment>
|
||||
`;
|
||||
|
||||
exports[`ChannelInfo -> Archive should match snapshot for Unarchive Channel 1`] = `
|
||||
<React.Fragment>
|
||||
<Separator
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<channelInfoRow
|
||||
action={[Function]}
|
||||
defaultMessage="Unarchive Channel"
|
||||
icon="archive"
|
||||
iconColor="#CA3B27"
|
||||
isLandscape={false}
|
||||
rightArrow={false}
|
||||
shouldRender={true}
|
||||
textColor="#CA3B27"
|
||||
textId="mobile.routes.channelInfo.unarchive_channel"
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
togglable={false}
|
||||
/>
|
||||
</React.Fragment>
|
||||
`;
|
||||
67
app/screens/channel_info/archive/archive.test.js
Normal file
67
app/screens/channel_info/archive/archive.test.js
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {shallowWithIntl} from 'test/intl-test-helper';
|
||||
|
||||
import Preferences from '@mm-redux/constants/preferences';
|
||||
|
||||
import Archive from './archive';
|
||||
|
||||
jest.mock('@utils/theme', () => {
|
||||
const original = jest.requireActual('../../../utils/theme');
|
||||
return {
|
||||
...original,
|
||||
changeOpacity: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
describe('ChannelInfo -> Archive', () => {
|
||||
const baseProps = {
|
||||
canArchive: true,
|
||||
canUnarchive: false,
|
||||
channelId: '123',
|
||||
close: jest.fn(),
|
||||
deleteChannel: jest.fn(),
|
||||
displayName: 'Test Channel',
|
||||
getChannel: jest.fn(),
|
||||
handleSelectChannel: jest.fn(),
|
||||
isLandscape: false,
|
||||
isPublic: true,
|
||||
unarchiveChannel: jest.fn(),
|
||||
selectPenultimateChannel: jest.fn(),
|
||||
teamId: 'team-123',
|
||||
theme: Preferences.THEMES.default,
|
||||
viewArchivedChannels: true,
|
||||
};
|
||||
|
||||
test('should match snapshot for Archive Channel', () => {
|
||||
const wrapper = shallowWithIntl(
|
||||
<Archive
|
||||
{...baseProps}
|
||||
/>,
|
||||
);
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot for Unarchive Channel', () => {
|
||||
const wrapper = shallowWithIntl(
|
||||
<Archive
|
||||
{...baseProps}
|
||||
canUnarchive={true}
|
||||
/>,
|
||||
);
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot Not render Archive', () => {
|
||||
const wrapper = shallowWithIntl(
|
||||
<Archive
|
||||
{...baseProps}
|
||||
canArchive={false}
|
||||
canUnarchive={false}
|
||||
/>,
|
||||
);
|
||||
expect(wrapper.getElement()).toBeNull();
|
||||
});
|
||||
});
|
||||
177
app/screens/channel_info/archive/archive.tsx
Normal file
177
app/screens/channel_info/archive/archive.tsx
Normal file
@@ -0,0 +1,177 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import {Alert} from 'react-native';
|
||||
import {intlShape} from 'react-intl';
|
||||
|
||||
import {ActionResult} from '@mm-redux/types/actions';
|
||||
import {FormattedMsg} from '@mm-redux/types/general';
|
||||
import {Theme} from '@mm-redux/types/preferences';
|
||||
import ChannelInfoRow from '@screens/channel_info/channel_info_row';
|
||||
import Separator from '@screens/channel_info/separator';
|
||||
import {alertErrorWithFallback} from '@utils/general';
|
||||
import {t} from '@utils/i18n';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
|
||||
interface ArchiveProps {
|
||||
canArchive: boolean;
|
||||
canUnarchive: boolean;
|
||||
channelId: string;
|
||||
close: (redirect: boolean) => void;
|
||||
deleteChannel: (channelId: string) => Promise<ActionResult>;
|
||||
displayName: string;
|
||||
getChannel: (channelId: string) => Promise<ActionResult>;
|
||||
handleSelectChannel: (channelId: string) => Promise<ActionResult>;
|
||||
isLandscape: boolean;
|
||||
isPublic: boolean;
|
||||
unarchiveChannel: (channelId: string) => Promise<ActionResult>;
|
||||
selectPenultimateChannel: (channelId: string) => Promise<ActionResult>;
|
||||
teamId: string;
|
||||
theme: Theme;
|
||||
viewArchivedChannels: boolean;
|
||||
}
|
||||
|
||||
export default class Archive extends PureComponent<ArchiveProps> {
|
||||
static contextTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
alertAndHandleYesAction = (title: FormattedMsg, message: FormattedMsg, onPressAction: () => void) => {
|
||||
const {formatMessage} = this.context.intl;
|
||||
const {displayName, isPublic} = this.props;
|
||||
|
||||
// eslint-disable-next-line multiline-ternary
|
||||
const term = isPublic ? formatMessage({id: 'mobile.channel_info.publicChannel', defaultMessage: 'Public Channel'}) :
|
||||
formatMessage({id: 'mobile.channel_info.privateChannel', defaultMessage: 'Private Channel'});
|
||||
|
||||
Alert.alert(
|
||||
formatMessage(title, {term}),
|
||||
formatMessage(
|
||||
message,
|
||||
{
|
||||
term: term.toLowerCase(),
|
||||
name: displayName,
|
||||
},
|
||||
),
|
||||
[{
|
||||
text: formatMessage({id: 'mobile.channel_info.alertNo', defaultMessage: 'No'}),
|
||||
}, {
|
||||
text: formatMessage({id: 'mobile.channel_info.alertYes', defaultMessage: 'Yes'}),
|
||||
onPress: onPressAction,
|
||||
}],
|
||||
);
|
||||
}
|
||||
|
||||
handleDelete = preventDoubleTap(() => {
|
||||
const {channelId, deleteChannel, displayName, teamId} = this.props;
|
||||
const title = {id: t('mobile.channel_info.alertTitleDeleteChannel'), defaultMessage: 'Archive {term}'};
|
||||
const message = {
|
||||
id: t('mobile.channel_info.alertMessageDeleteChannel'),
|
||||
defaultMessage: 'Are you sure you want to archive the {term} {name}?',
|
||||
};
|
||||
const onPressAction = async () => {
|
||||
const result = await deleteChannel(channelId);
|
||||
if (result.error) {
|
||||
alertErrorWithFallback(
|
||||
this.context.intl,
|
||||
result.error,
|
||||
{
|
||||
id: t('mobile.channel_info.delete_failed'),
|
||||
defaultMessage: "We couldn't archive the channel {displayName}. Please check your connection and try again.",
|
||||
},
|
||||
{
|
||||
displayName,
|
||||
},
|
||||
);
|
||||
if (result.error.server_error_id === 'api.channel.delete_channel.deleted.app_error') {
|
||||
this.props.getChannel(channelId);
|
||||
}
|
||||
} else if (this.props.viewArchivedChannels) {
|
||||
this.props.handleSelectChannel(channelId);
|
||||
this.props.close(false);
|
||||
} else {
|
||||
this.props.selectPenultimateChannel(teamId);
|
||||
this.props.close(false);
|
||||
}
|
||||
};
|
||||
this.alertAndHandleYesAction(title, message, onPressAction);
|
||||
});
|
||||
|
||||
handleUnarchive = preventDoubleTap(() => {
|
||||
const {channelId, displayName} = this.props;
|
||||
const title = {id: t('mobile.channel_info.alertTitleUnarchiveChannel'), defaultMessage: 'Unarchive {term}'};
|
||||
const message = {
|
||||
id: t('mobile.channel_info.alertMessageUnarchiveChannel'),
|
||||
defaultMessage: 'Are you sure you want to unarchive the {term} {name}?',
|
||||
};
|
||||
const onPressAction = async () => {
|
||||
const result = await this.props.unarchiveChannel(channelId);
|
||||
if (result.error) {
|
||||
alertErrorWithFallback(
|
||||
this.context.intl,
|
||||
result.error,
|
||||
{
|
||||
id: t('mobile.channel_info.unarchive_failed'),
|
||||
defaultMessage: "We couldn't unarchive the channel {displayName}. Please check your connection and try again.",
|
||||
},
|
||||
{
|
||||
displayName,
|
||||
},
|
||||
);
|
||||
if (result.error.server_error_id === 'api.channel.unarchive_channel.unarchive.app_error') {
|
||||
this.props.getChannel(channelId);
|
||||
}
|
||||
} else {
|
||||
this.props.close(false);
|
||||
}
|
||||
};
|
||||
this.alertAndHandleYesAction(title, message, onPressAction);
|
||||
});
|
||||
|
||||
render() {
|
||||
const {canArchive, canUnarchive, isLandscape, theme} = this.props;
|
||||
|
||||
if (!canArchive && !canUnarchive) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let element;
|
||||
if (canUnarchive) {
|
||||
element = (
|
||||
<ChannelInfoRow
|
||||
action={this.handleUnarchive}
|
||||
defaultMessage='Unarchive Channel'
|
||||
icon='archive'
|
||||
iconColor='#CA3B27'
|
||||
textColor='#CA3B27'
|
||||
textId={t('mobile.routes.channelInfo.unarchive_channel')}
|
||||
theme={theme}
|
||||
isLandscape={isLandscape}
|
||||
rightArrow={false}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
element = (
|
||||
<ChannelInfoRow
|
||||
action={this.handleDelete}
|
||||
defaultMessage='Archive Channel'
|
||||
iconColor='#CA3B27'
|
||||
icon='archive'
|
||||
textId={t('mobile.routes.channelInfo.delete_channel')}
|
||||
textColor='#CA3B27'
|
||||
theme={theme}
|
||||
isLandscape={isLandscape}
|
||||
rightArrow={false}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Separator theme={theme}/>
|
||||
{element}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
76
app/screens/channel_info/archive/index.js
Normal file
76
app/screens/channel_info/archive/index.js
Normal file
@@ -0,0 +1,76 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {
|
||||
handleSelectChannel,
|
||||
selectPenultimateChannel,
|
||||
} from '@actions/views/channel';
|
||||
import {deleteChannel, getChannel, unarchiveChannel} from '@mm-redux/actions/channels';
|
||||
import {General} from '@mm-redux/constants';
|
||||
import Permissions from '@mm-redux/constants/permissions';
|
||||
import {getCurrentChannel, isCurrentChannelReadOnly} from '@mm-redux/selectors/entities/channels';
|
||||
import {getConfig, getLicense, hasNewPermissions} from '@mm-redux/selectors/entities/general';
|
||||
import {haveITeamPermission} from '@mm-redux/selectors/entities/roles';
|
||||
import {getCurrentTeamId} from '@mm-redux/selectors/entities/teams';
|
||||
import {getCurrentUser, getCurrentUserRoles} from '@mm-redux/selectors/entities/users';
|
||||
import {showDeleteOption} from '@mm-redux/utils/channel_utils';
|
||||
import {isMinimumServerVersion} from '@mm-redux/utils/helpers';
|
||||
import {isAdmin as checkIsAdmin, isChannelAdmin as checkIsChannelAdmin, isSystemAdmin as checkIsSystemAdmin} from '@mm-redux/utils/user_utils';
|
||||
import {isGuest as isUserGuest} from '@utils/users';
|
||||
|
||||
import Archive from './archive';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const config = getConfig(state);
|
||||
const license = getLicense(state);
|
||||
const currentChannel = getCurrentChannel(state);
|
||||
const currentUser = getCurrentUser(state);
|
||||
const roles = getCurrentUserRoles(state) || '';
|
||||
const isGuest = isUserGuest(currentUser);
|
||||
const isDefaultChannel = currentChannel.name === General.DEFAULT_CHANNEL;
|
||||
const isDirectMessage = currentChannel.type === General.DM_CHANNEL;
|
||||
const isGroupMessage = currentChannel.type === General.GM_CHANNEL;
|
||||
const isAdmin = checkIsAdmin(roles);
|
||||
const isChannelAdmin = checkIsChannelAdmin(roles);
|
||||
const isSystemAdmin = checkIsSystemAdmin(roles);
|
||||
const canLeave = (!isDefaultChannel && !isDirectMessage && !isGroupMessage) || (isDefaultChannel && isGuest);
|
||||
const canDelete = showDeleteOption(state, config, license, currentChannel, isAdmin, isSystemAdmin, isChannelAdmin);
|
||||
const canUnarchive = (currentChannel?.delete_at > 0 && !isDirectMessage && !isGroupMessage);
|
||||
const viewArchivedChannels = config.ExperimentalViewArchivedChannels === 'true';
|
||||
const {serverVersion} = state.entities.general;
|
||||
|
||||
let isReadOnly = false;
|
||||
if (currentUser?.id && currentChannel?.id) {
|
||||
isReadOnly = isCurrentChannelReadOnly(state) || false;
|
||||
}
|
||||
|
||||
let canUnarchiveChannel = false;
|
||||
if (hasNewPermissions(state) && isMinimumServerVersion(serverVersion, 5, 20)) {
|
||||
canUnarchiveChannel = haveITeamPermission(state, {
|
||||
team: getCurrentTeamId(state),
|
||||
permission: Permissions.MANAGE_TEAM,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
canArchive: (canLeave && canDelete && !isReadOnly),
|
||||
canUnarchive: canUnarchive && canUnarchiveChannel,
|
||||
channelId: currentChannel?.id || '',
|
||||
displayName: (currentChannel?.display_name || '').trim(),
|
||||
isPublic: currentChannel?.type === General.OPEN_CHANNEL,
|
||||
teamId: currentChannel?.team_id || '',
|
||||
viewArchivedChannels,
|
||||
};
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
deleteChannel,
|
||||
getChannel,
|
||||
handleSelectChannel,
|
||||
unarchiveChannel,
|
||||
selectPenultimateChannel,
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Archive);
|
||||
@@ -5,69 +5,47 @@ import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {intlShape} from 'react-intl';
|
||||
import {
|
||||
Alert,
|
||||
ScrollView,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import {Navigation} from 'react-native-navigation';
|
||||
|
||||
import {dismissModal, goToScreen, showModalOverCurrentContext} from '@actions/navigation';
|
||||
import pinIcon from '@assets/images/channel_info/pin.png';
|
||||
import {dismissModal, showModalOverCurrentContext} from '@actions/navigation';
|
||||
import StatusBar from '@components/status_bar';
|
||||
import {General, Users} from '@mm-redux/constants';
|
||||
import {alertErrorWithFallback} from '@utils/general';
|
||||
import {t} from '@utils/i18n';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
import AddMembers from './add_members';
|
||||
import Archive from './archive';
|
||||
import ChannelInfoHeader from './channel_info_header';
|
||||
import ChannelInfoRow from './channel_info_row';
|
||||
import ConvertPrivate from './convert_private';
|
||||
import EditChannel from './edit_channel';
|
||||
import Favorite from './favorite';
|
||||
import IgnoreMentions from './ignore_mentions';
|
||||
import Leave from './leave';
|
||||
import ManageMembers from './manage_members';
|
||||
import Mute from './mute';
|
||||
import Pinned from './pinned';
|
||||
import Separator from './separator';
|
||||
|
||||
export default class ChannelInfo extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
clearPinnedPosts: PropTypes.func.isRequired,
|
||||
closeDMChannel: PropTypes.func.isRequired,
|
||||
closeGMChannel: PropTypes.func.isRequired,
|
||||
convertChannelToPrivate: PropTypes.func.isRequired,
|
||||
deleteChannel: PropTypes.func.isRequired,
|
||||
unarchiveChannel: PropTypes.func.isRequired,
|
||||
getChannelStats: PropTypes.func.isRequired,
|
||||
getChannel: PropTypes.func.isRequired,
|
||||
leaveChannel: PropTypes.func.isRequired,
|
||||
loadChannelsByTeamName: PropTypes.func.isRequired,
|
||||
favoriteChannel: PropTypes.func.isRequired,
|
||||
unfavoriteChannel: PropTypes.func.isRequired,
|
||||
getCustomEmojisInText: PropTypes.func.isRequired,
|
||||
selectFocusedPostId: PropTypes.func.isRequired,
|
||||
updateChannelNotifyProps: PropTypes.func.isRequired,
|
||||
selectPenultimateChannel: PropTypes.func.isRequired,
|
||||
handleSelectChannel: PropTypes.func.isRequired,
|
||||
setChannelDisplayName: PropTypes.func.isRequired,
|
||||
}),
|
||||
componentId: PropTypes.string,
|
||||
viewArchivedChannels: PropTypes.bool.isRequired,
|
||||
canDeleteChannel: PropTypes.bool.isRequired,
|
||||
canUnarchiveChannel: PropTypes.bool.isRequired,
|
||||
currentChannel: PropTypes.object.isRequired,
|
||||
currentChannelCreatorName: PropTypes.string,
|
||||
currentChannelMemberCount: PropTypes.number,
|
||||
currentChannelGuestCount: PropTypes.number,
|
||||
currentChannelPinnedPostCount: PropTypes.number,
|
||||
currentChannelMemberCount: PropTypes.number,
|
||||
currentUserId: PropTypes.string,
|
||||
currentUserIsGuest: PropTypes.bool,
|
||||
isBot: PropTypes.bool.isRequired,
|
||||
isLandscape: PropTypes.bool.isRequired,
|
||||
isTeammateGuest: PropTypes.bool.isRequired,
|
||||
status: PropTypes.string,
|
||||
theme: PropTypes.object.isRequired,
|
||||
isChannelMuted: PropTypes.bool.isRequired,
|
||||
isCurrent: PropTypes.bool.isRequired,
|
||||
isFavorite: PropTypes.bool.isRequired,
|
||||
canConvertChannel: PropTypes.bool.isRequired,
|
||||
canManageUsers: PropTypes.bool.isRequired,
|
||||
canEditChannel: PropTypes.bool.isRequired,
|
||||
ignoreChannelMentions: PropTypes.bool.isRequired,
|
||||
isBot: PropTypes.bool.isRequired,
|
||||
isTeammateGuest: PropTypes.bool.isRequired,
|
||||
isLandscape: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
@@ -78,30 +56,6 @@ export default class ChannelInfo extends PureComponent {
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
isFavorite: props.isFavorite,
|
||||
isMuted: props.isChannelMuted,
|
||||
ignoreChannelMentions: props.ignoreChannelMentions,
|
||||
};
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(nextProps, state) {
|
||||
if (state.isFavorite !== nextProps.isFavorite ||
|
||||
state.isMuted !== nextProps.isChannelMuted ||
|
||||
state.ignoreChannelMentions !== nextProps.ignoreChannelMentions) {
|
||||
return {
|
||||
isFavorite: nextProps.isFavorite,
|
||||
isMuted: nextProps.isChannelMuted,
|
||||
ignoreChannelMentions: nextProps.ignoreChannelMentions,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.navigationEventListener = Navigation.events().bindComponent(this);
|
||||
this.props.actions.getChannelStats(this.props.currentChannel.id);
|
||||
@@ -124,234 +78,6 @@ export default class ChannelInfo extends PureComponent {
|
||||
dismissModal();
|
||||
};
|
||||
|
||||
goToChannelAddMembers = preventDoubleTap(() => {
|
||||
const {intl} = this.context;
|
||||
const screen = 'ChannelAddMembers';
|
||||
const title = intl.formatMessage({id: 'channel_header.addMembers', defaultMessage: 'Add Members'});
|
||||
|
||||
goToScreen(screen, title);
|
||||
});
|
||||
|
||||
goToChannelMembers = preventDoubleTap(() => {
|
||||
const {canManageUsers} = this.props;
|
||||
const {intl} = this.context;
|
||||
const id = canManageUsers ? t('channel_header.manageMembers') : t('channel_header.viewMembers');
|
||||
const defaultMessage = canManageUsers ? 'Manage Members' : 'View Members';
|
||||
const screen = 'ChannelMembers';
|
||||
const title = intl.formatMessage({id, defaultMessage});
|
||||
|
||||
goToScreen(screen, title);
|
||||
});
|
||||
|
||||
goToPinnedPosts = preventDoubleTap(() => {
|
||||
const {currentChannel} = this.props;
|
||||
const {formatMessage} = this.context.intl;
|
||||
const id = t('channel_header.pinnedPosts');
|
||||
const defaultMessage = 'Pinned Posts';
|
||||
const screen = 'PinnedPosts';
|
||||
const title = formatMessage({id, defaultMessage});
|
||||
const passProps = {
|
||||
currentChannelId: currentChannel.id,
|
||||
};
|
||||
|
||||
goToScreen(screen, title, passProps);
|
||||
});
|
||||
|
||||
handleChannelEdit = preventDoubleTap(() => {
|
||||
const {intl} = this.context;
|
||||
const id = t('mobile.channel_info.edit');
|
||||
const defaultMessage = 'Edit Channel';
|
||||
const screen = 'EditChannel';
|
||||
const title = intl.formatMessage({id, defaultMessage});
|
||||
|
||||
goToScreen(screen, title);
|
||||
});
|
||||
|
||||
handleConfirmConvertToPrivate = preventDoubleTap(async () => {
|
||||
const {actions, currentChannel} = this.props;
|
||||
const result = await actions.convertChannelToPrivate(currentChannel.id);
|
||||
const displayName = {displayName: currentChannel.display_name.trim()};
|
||||
const {formatMessage} = this.context.intl;
|
||||
if (result.error) {
|
||||
alertErrorWithFallback(
|
||||
this.context.intl,
|
||||
result.error,
|
||||
{
|
||||
id: t('mobile.channel_info.convert_failed'),
|
||||
defaultMessage: 'We were unable to convert {displayName} to a private channel.',
|
||||
},
|
||||
{
|
||||
displayName,
|
||||
},
|
||||
[{
|
||||
text: formatMessage({id: 'mobile.share_extension.error_close', defaultMessage: 'Close'}),
|
||||
}, {
|
||||
text: formatMessage({id: 'mobile.terms_of_service.alert_retry', defaultMessage: 'Try Again'}),
|
||||
onPress: this.handleConfirmConvertToPrivate,
|
||||
}],
|
||||
);
|
||||
} else {
|
||||
Alert.alert(
|
||||
'',
|
||||
formatMessage({id: t('mobile.channel_info.convert_success'), defaultMessage: '{displayName} is now a private channel.'}, displayName),
|
||||
);
|
||||
}
|
||||
})
|
||||
|
||||
handleConvertToPrivate = preventDoubleTap(() => {
|
||||
const {currentChannel} = this.props;
|
||||
const {formatMessage} = this.context.intl;
|
||||
const displayName = {displayName: currentChannel.display_name.trim()};
|
||||
const title = {id: t('mobile.channel_info.alertTitleConvertChannel'), defaultMessage: 'Convert {displayName} to a private channel?'};
|
||||
const message = {
|
||||
id: t('mobile.channel_info.alertMessageConvertChannel'),
|
||||
defaultMessage: 'When you convert {displayName} to a private channel, history and membership are preserved. Publicly shared files remain accessible to anyone with the link. Membership in a private channel is by invitation only.\n\nThe change is permanent and cannot be undone.\n\nAre you sure you want to convert {displayName} to a private channel?',
|
||||
};
|
||||
|
||||
Alert.alert(
|
||||
formatMessage(title, displayName),
|
||||
formatMessage(message, displayName),
|
||||
[{
|
||||
text: formatMessage({id: 'mobile.channel_info.alertNo', defaultMessage: 'No'}),
|
||||
}, {
|
||||
text: formatMessage({id: 'mobile.channel_info.alertYes', defaultMessage: 'Yes'}),
|
||||
onPress: this.handleConfirmConvertToPrivate,
|
||||
}],
|
||||
);
|
||||
});
|
||||
|
||||
handleLeave = preventDoubleTap(() => {
|
||||
const title = {id: t('mobile.channel_info.alertTitleLeaveChannel'), defaultMessage: 'Leave {term}'};
|
||||
const message = {
|
||||
id: t('mobile.channel_info.alertMessageLeaveChannel'),
|
||||
defaultMessage: 'Are you sure you want to leave the {term} {name}?',
|
||||
};
|
||||
const onPressAction = () => {
|
||||
this.props.actions.leaveChannel(this.props.currentChannel, true).then(() => {
|
||||
this.close();
|
||||
});
|
||||
};
|
||||
this.alertAndHandleYesAction(title, message, onPressAction);
|
||||
});
|
||||
|
||||
handleDelete = preventDoubleTap(() => {
|
||||
const channel = this.props.currentChannel;
|
||||
const title = {id: t('mobile.channel_info.alertTitleDeleteChannel'), defaultMessage: 'Archive {term}'};
|
||||
const message = {
|
||||
id: t('mobile.channel_info.alertMessageDeleteChannel'),
|
||||
defaultMessage: 'Are you sure you want to archive the {term} {name}?',
|
||||
};
|
||||
const onPressAction = async () => {
|
||||
const result = await this.props.actions.deleteChannel(channel.id);
|
||||
if (result.error) {
|
||||
alertErrorWithFallback(
|
||||
this.context.intl,
|
||||
result.error,
|
||||
{
|
||||
id: t('mobile.channel_info.delete_failed'),
|
||||
defaultMessage: "We couldn't archive the channel {displayName}. Please check your connection and try again.",
|
||||
},
|
||||
{
|
||||
displayName: channel.display_name.trim(),
|
||||
},
|
||||
);
|
||||
if (result.error.server_error_id === 'api.channel.delete_channel.deleted.app_error') {
|
||||
this.props.actions.getChannel(channel.id);
|
||||
}
|
||||
} else if (this.props.viewArchivedChannels) {
|
||||
this.props.actions.handleSelectChannel(channel.id);
|
||||
this.close(false);
|
||||
} else {
|
||||
this.props.actions.selectPenultimateChannel(channel.team_id);
|
||||
this.close(false);
|
||||
}
|
||||
};
|
||||
this.alertAndHandleYesAction(title, message, onPressAction);
|
||||
});
|
||||
|
||||
handleUnarchive = preventDoubleTap(() => {
|
||||
const channel = this.props.currentChannel;
|
||||
const title = {id: t('mobile.channel_info.alertTitleUnarchiveChannel'), defaultMessage: 'Unarchive {term}'};
|
||||
const message = {
|
||||
id: t('mobile.channel_info.alertMessageUnarchiveChannel'),
|
||||
defaultMessage: 'Are you sure you want to unarchive the {term} {name}?',
|
||||
};
|
||||
const onPressAction = async () => {
|
||||
const result = await this.props.actions.unarchiveChannel(channel.id);
|
||||
if (result.error) {
|
||||
alertErrorWithFallback(
|
||||
this.context.intl,
|
||||
result.error,
|
||||
{
|
||||
id: t('mobile.channel_info.unarchive_failed'),
|
||||
defaultMessage: "We couldn't unarchive the channel {displayName}. Please check your connection and try again.",
|
||||
},
|
||||
{
|
||||
displayName: channel.display_name.trim(),
|
||||
},
|
||||
);
|
||||
if (result.error.server_error_id === 'api.channel.unarchive_channel.unarchive.app_error') {
|
||||
this.props.actions.getChannel(channel.id);
|
||||
}
|
||||
} else {
|
||||
this.close(false);
|
||||
}
|
||||
};
|
||||
this.alertAndHandleYesAction(title, message, onPressAction);
|
||||
});
|
||||
|
||||
alertAndHandleYesAction = (title, message, onPressAction) => {
|
||||
const {formatMessage} = this.context.intl;
|
||||
const channel = this.props.currentChannel;
|
||||
const term = channel.type === General.OPEN_CHANNEL ?
|
||||
formatMessage({id: 'mobile.channel_info.publicChannel', defaultMessage: 'Public Channel'}) :
|
||||
formatMessage({id: 'mobile.channel_info.privateChannel', defaultMessage: 'Private Channel'});
|
||||
|
||||
Alert.alert(
|
||||
formatMessage(title, {term}),
|
||||
formatMessage(
|
||||
message,
|
||||
{
|
||||
term: term.toLowerCase(),
|
||||
name: channel.display_name.trim(),
|
||||
},
|
||||
),
|
||||
[{
|
||||
text: formatMessage({id: 'mobile.channel_info.alertNo', defaultMessage: 'No'}),
|
||||
}, {
|
||||
text: formatMessage({id: 'mobile.channel_info.alertYes', defaultMessage: 'Yes'}),
|
||||
onPress: onPressAction,
|
||||
}],
|
||||
);
|
||||
}
|
||||
|
||||
handleClose = preventDoubleTap(() => {
|
||||
const {currentChannel, isCurrent, isFavorite} = this.props;
|
||||
const channel = Object.assign({}, currentChannel, {isCurrent}, {isFavorite});
|
||||
const {closeDMChannel, closeGMChannel} = this.props.actions;
|
||||
|
||||
switch (channel.type) {
|
||||
case General.DM_CHANNEL:
|
||||
closeDMChannel(channel).then(() => {
|
||||
this.close();
|
||||
});
|
||||
break;
|
||||
case General.GM_CHANNEL:
|
||||
closeGMChannel(channel).then(() => {
|
||||
this.close();
|
||||
});
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
handleFavorite = preventDoubleTap(() => {
|
||||
const {isFavorite, actions, currentChannel} = this.props;
|
||||
const {favoriteChannel, unfavoriteChannel} = actions;
|
||||
const toggleFavorite = isFavorite ? unfavoriteChannel : favoriteChannel;
|
||||
this.setState({isFavorite: !isFavorite});
|
||||
toggleFavorite(currentChannel.id);
|
||||
});
|
||||
|
||||
handleClosePermalink = () => {
|
||||
const {actions} = this.props;
|
||||
actions.selectFocusedPostId('');
|
||||
@@ -363,29 +89,6 @@ export default class ChannelInfo extends PureComponent {
|
||||
this.showPermalinkView(postId);
|
||||
};
|
||||
|
||||
handleMuteChannel = preventDoubleTap(() => {
|
||||
const {actions, currentChannel, currentUserId, isChannelMuted} = this.props;
|
||||
const {updateChannelNotifyProps} = actions;
|
||||
const opts = {
|
||||
mark_unread: isChannelMuted ? 'all' : 'mention',
|
||||
};
|
||||
|
||||
this.setState({isMuted: !isChannelMuted});
|
||||
updateChannelNotifyProps(currentUserId, currentChannel.id, opts);
|
||||
});
|
||||
|
||||
handleIgnoreChannelMentions = preventDoubleTap(() => {
|
||||
const {actions, currentChannel, currentUserId, ignoreChannelMentions} = this.props;
|
||||
const {updateChannelNotifyProps} = actions;
|
||||
|
||||
const opts = {
|
||||
ignore_channel_mentions: ignoreChannelMentions ? Users.IGNORE_CHANNEL_MENTIONS_OFF : Users.IGNORE_CHANNEL_MENTIONS_ON,
|
||||
};
|
||||
|
||||
this.setState({ignoreChannelMentions: !ignoreChannelMentions});
|
||||
updateChannelNotifyProps(currentUserId, currentChannel.id, opts);
|
||||
});
|
||||
|
||||
showPermalinkView = (postId) => {
|
||||
const {actions} = this.props;
|
||||
const screen = 'Permalink';
|
||||
@@ -405,194 +108,66 @@ export default class ChannelInfo extends PureComponent {
|
||||
showModalOverCurrentContext(screen, passProps, options);
|
||||
};
|
||||
|
||||
renderViewOrManageMembersRow = () => {
|
||||
const channel = this.props.currentChannel;
|
||||
const isDirectMessage = channel.type === General.DM_CHANNEL;
|
||||
|
||||
return !isDirectMessage;
|
||||
};
|
||||
|
||||
renderLeaveOrDeleteChannelRow = () => {
|
||||
const channel = this.props.currentChannel;
|
||||
const isGuest = this.props.currentUserIsGuest;
|
||||
const isDefaultChannel = channel.name === General.DEFAULT_CHANNEL;
|
||||
const isDirectMessage = channel.type === General.DM_CHANNEL;
|
||||
const isGroupMessage = channel.type === General.GM_CHANNEL;
|
||||
|
||||
return (!isDefaultChannel && !isDirectMessage && !isGroupMessage) || (isDefaultChannel && isGuest);
|
||||
};
|
||||
|
||||
renderCloseDirect = () => {
|
||||
const channel = this.props.currentChannel;
|
||||
const isDirectMessage = channel.type === General.DM_CHANNEL;
|
||||
const isGroupMessage = channel.type === General.GM_CHANNEL;
|
||||
|
||||
return isDirectMessage || isGroupMessage;
|
||||
};
|
||||
|
||||
renderUnarchiveChannel = () => {
|
||||
const {canUnarchiveChannel} = this.props;
|
||||
if (!canUnarchiveChannel) {
|
||||
return false;
|
||||
}
|
||||
const channel = this.props.currentChannel;
|
||||
const channelIsArchived = channel.delete_at !== 0;
|
||||
const isDirectMessage = channel.type === General.DM_CHANNEL;
|
||||
const isGroupMessage = channel.type === General.GM_CHANNEL;
|
||||
|
||||
return channelIsArchived && (!isDirectMessage && !isGroupMessage);
|
||||
};
|
||||
|
||||
renderConvertToPrivateRow = () => {
|
||||
const {currentChannel, canConvertChannel} = this.props;
|
||||
const isDefaultChannel = currentChannel.name === General.DEFAULT_CHANNEL;
|
||||
const isPublicChannel = currentChannel.type === General.OPEN_CHANNEL;
|
||||
return !isDefaultChannel && isPublicChannel && canConvertChannel;
|
||||
}
|
||||
|
||||
actionsRows = (style, channelIsArchived) => {
|
||||
const {
|
||||
currentChannelMemberCount,
|
||||
currentChannelPinnedPostCount,
|
||||
canManageUsers,
|
||||
canEditChannel,
|
||||
theme,
|
||||
currentChannel,
|
||||
isLandscape,
|
||||
} = this.props;
|
||||
const {currentChannel, currentUserId, isLandscape, theme} = this.props;
|
||||
|
||||
if (channelIsArchived) {
|
||||
return (this.renderViewOrManageMembersRow() &&
|
||||
<View>
|
||||
<ChannelInfoRow
|
||||
action={this.goToChannelMembers}
|
||||
defaultMessage={canManageUsers ? 'Manage Members' : 'View Members'}
|
||||
detail={currentChannelMemberCount}
|
||||
icon='users'
|
||||
textId={canManageUsers ? t('channel_header.manageMembers') : t('channel_header.viewMembers')}
|
||||
theme={theme}
|
||||
isLandscape={isLandscape}
|
||||
/>
|
||||
</View>);
|
||||
return (
|
||||
<ManageMembers
|
||||
isLandscape={isLandscape}
|
||||
theme={theme}
|
||||
separator={false}
|
||||
/>);
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<ChannelInfoRow
|
||||
action={this.handleFavorite}
|
||||
defaultMessage='Favorite'
|
||||
detail={this.state.isFavorite}
|
||||
icon='star-o'
|
||||
textId={t('mobile.routes.channelInfo.favorite')}
|
||||
togglable={true}
|
||||
theme={theme}
|
||||
<>
|
||||
<Favorite
|
||||
channelId={currentChannel.id}
|
||||
isLandscape={isLandscape}
|
||||
/>
|
||||
<View style={style.separator}/>
|
||||
<ChannelInfoRow
|
||||
action={this.handleMuteChannel}
|
||||
defaultMessage='Mute channel'
|
||||
detail={this.state.isMuted}
|
||||
icon='bell-slash-o'
|
||||
textId={t('channel_notifications.muteChannel.settings')}
|
||||
togglable={true}
|
||||
theme={theme}
|
||||
isLandscape={isLandscape}
|
||||
/>
|
||||
<View style={style.separator}/>
|
||||
<ChannelInfoRow
|
||||
action={this.handleIgnoreChannelMentions}
|
||||
defaultMessage='Ignore @channel, @here, @all'
|
||||
detail={this.state.ignoreChannelMentions}
|
||||
icon='at'
|
||||
textId={t('channel_notifications.ignoreChannelMentions.settings')}
|
||||
togglable={true}
|
||||
<Separator theme={theme}/>
|
||||
<Mute
|
||||
channelId={currentChannel.id}
|
||||
isLandscape={isLandscape}
|
||||
userId={currentUserId}
|
||||
theme={theme}
|
||||
isLandscape={isLandscape}
|
||||
/>
|
||||
<View style={style.separator}/>
|
||||
<ChannelInfoRow
|
||||
action={this.goToPinnedPosts}
|
||||
defaultMessage='Pinned Posts'
|
||||
detail={currentChannelPinnedPostCount}
|
||||
image={pinIcon}
|
||||
textId={t('channel_header.pinnedPosts')}
|
||||
<Separator theme={theme}/>
|
||||
<IgnoreMentions
|
||||
channelId={currentChannel.id}
|
||||
isLandscape={isLandscape}
|
||||
theme={theme}
|
||||
isLandscape={isLandscape}
|
||||
/>
|
||||
{
|
||||
|
||||
/**
|
||||
<ChannelInfoRow
|
||||
action={() => true}
|
||||
defaultMessage='Notification Preferences'
|
||||
icon='bell-o'
|
||||
textId='channel_header.notificationPreferences'
|
||||
theme={theme}
|
||||
/>
|
||||
<View style={style.separator}/>
|
||||
**/
|
||||
}
|
||||
{this.renderViewOrManageMembersRow() &&
|
||||
<React.Fragment>
|
||||
<View style={style.separator}/>
|
||||
<ChannelInfoRow
|
||||
action={this.goToChannelMembers}
|
||||
defaultMessage={canManageUsers ? 'Manage Members' : 'View Members'}
|
||||
detail={currentChannelMemberCount}
|
||||
icon='users'
|
||||
textId={canManageUsers ? t('channel_header.manageMembers') : t('channel_header.viewMembers')}
|
||||
theme={theme}
|
||||
isLandscape={isLandscape}
|
||||
/>
|
||||
</React.Fragment>
|
||||
}
|
||||
{canManageUsers && !currentChannel.group_constrained &&
|
||||
<React.Fragment>
|
||||
<View style={style.separator}/>
|
||||
<ChannelInfoRow
|
||||
action={this.goToChannelAddMembers}
|
||||
defaultMessage='Add Members'
|
||||
icon='user-plus'
|
||||
textId={t('channel_header.addMembers')}
|
||||
theme={theme}
|
||||
isLandscape={isLandscape}
|
||||
/>
|
||||
</React.Fragment>
|
||||
}
|
||||
{this.renderConvertToPrivateRow() && (
|
||||
<React.Fragment>
|
||||
<View style={style.separator}/>
|
||||
<ChannelInfoRow
|
||||
action={this.handleConvertToPrivate}
|
||||
defaultMessage='Convert to Private Channel'
|
||||
icon='lock'
|
||||
textId={t('mobile.channel_info.convert')}
|
||||
theme={theme}
|
||||
isLandscape={isLandscape}
|
||||
/>
|
||||
</React.Fragment>
|
||||
)}
|
||||
{canEditChannel && (
|
||||
<React.Fragment>
|
||||
<View style={style.separator}/>
|
||||
<ChannelInfoRow
|
||||
action={this.handleChannelEdit}
|
||||
defaultMessage='Edit Channel'
|
||||
icon='edit'
|
||||
textId={t('mobile.channel_info.edit')}
|
||||
theme={theme}
|
||||
isLandscape={isLandscape}
|
||||
/>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</React.Fragment>
|
||||
<Separator theme={theme}/>
|
||||
<Pinned
|
||||
channelId={currentChannel.id}
|
||||
isLandscape={isLandscape}
|
||||
theme={theme}
|
||||
/>
|
||||
<ManageMembers
|
||||
isLandscape={isLandscape}
|
||||
theme={theme}
|
||||
/>
|
||||
<AddMembers
|
||||
isLandscape={isLandscape}
|
||||
theme={theme}
|
||||
/>
|
||||
<ConvertPrivate
|
||||
isLandscape={isLandscape}
|
||||
theme={theme}
|
||||
/>
|
||||
<EditChannel
|
||||
isLandscape={isLandscape}
|
||||
theme={theme}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
canDeleteChannel,
|
||||
currentChannel,
|
||||
currentChannelCreatorName,
|
||||
currentChannelMemberCount,
|
||||
@@ -607,26 +182,13 @@ export default class ChannelInfo extends PureComponent {
|
||||
const style = getStyleSheet(theme);
|
||||
const channelIsArchived = currentChannel.delete_at !== 0;
|
||||
|
||||
let i18nId;
|
||||
let defaultMessage;
|
||||
switch (currentChannel.type) {
|
||||
case General.DM_CHANNEL:
|
||||
i18nId = t('mobile.channel_list.closeDM');
|
||||
defaultMessage = 'Close Direct Message';
|
||||
break;
|
||||
case General.GM_CHANNEL:
|
||||
i18nId = t('mobile.channel_list.closeGM');
|
||||
defaultMessage = 'Close Group Message';
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={style.container}>
|
||||
<StatusBar/>
|
||||
<ScrollView
|
||||
style={style.scrollView}
|
||||
>
|
||||
{currentChannel.hasOwnProperty('id') &&
|
||||
{Boolean(currentChannel?.id) &&
|
||||
<ChannelInfoHeader
|
||||
createAt={currentChannel.create_at}
|
||||
creator={currentChannelCreatorName}
|
||||
@@ -648,62 +210,19 @@ export default class ChannelInfo extends PureComponent {
|
||||
}
|
||||
<View style={style.rowsContainer}>
|
||||
{this.actionsRows(style, channelIsArchived)}
|
||||
{this.renderLeaveOrDeleteChannelRow() &&
|
||||
<React.Fragment>
|
||||
<View style={style.separator}/>
|
||||
<ChannelInfoRow
|
||||
action={this.handleLeave}
|
||||
defaultMessage='Leave Channel'
|
||||
icon='sign-out'
|
||||
textId={t('navbar.leave')}
|
||||
theme={theme}
|
||||
isLandscape={isLandscape}
|
||||
/>
|
||||
{this.renderUnarchiveChannel() &&
|
||||
<React.Fragment>
|
||||
<View style={style.separator}/>
|
||||
<ChannelInfoRow
|
||||
action={this.handleUnarchive}
|
||||
defaultMessage='Unarchive Channel'
|
||||
icon='archive' // might need to change the icon...
|
||||
textId={t('mobile.routes.channelInfo.unarchive_channel')}
|
||||
theme={theme}
|
||||
isLandscape={isLandscape}
|
||||
/>
|
||||
</React.Fragment>
|
||||
}
|
||||
</React.Fragment>
|
||||
}
|
||||
</View>
|
||||
{this.renderLeaveOrDeleteChannelRow() && canDeleteChannel && !channelIsArchived &&
|
||||
<View style={[style.rowsContainer, style.footer]}>
|
||||
<ChannelInfoRow
|
||||
action={this.handleDelete}
|
||||
defaultMessage='Archive Channel'
|
||||
iconColor='#CA3B27'
|
||||
icon='archive'
|
||||
textId={t('mobile.routes.channelInfo.delete_channel')}
|
||||
textColor='#CA3B27'
|
||||
theme={theme}
|
||||
<View style={style.footer}>
|
||||
<Leave
|
||||
close={this.close}
|
||||
isLandscape={isLandscape}
|
||||
theme={theme}
|
||||
/>
|
||||
<Archive
|
||||
close={this.close}
|
||||
isLandscape={isLandscape}
|
||||
theme={theme}
|
||||
/>
|
||||
</View>
|
||||
}
|
||||
{this.renderCloseDirect() &&
|
||||
<View style={[style.rowsContainer, style.footer]}>
|
||||
<ChannelInfoRow
|
||||
action={this.handleClose}
|
||||
defaultMessage={defaultMessage}
|
||||
icon='times'
|
||||
iconColor='#CA3B27'
|
||||
rightArrow={false}
|
||||
textId={i18nId}
|
||||
textColor='#CA3B27'
|
||||
theme={theme}
|
||||
isLandscape={isLandscape}
|
||||
/>
|
||||
</View>
|
||||
}
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
@@ -721,10 +240,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
},
|
||||
footer: {
|
||||
marginTop: 40,
|
||||
borderTopWidth: 1,
|
||||
borderBottomWidth: 1,
|
||||
borderTopColor: changeOpacity(theme.centerChannelColor, 0.1),
|
||||
borderBottomColor: changeOpacity(theme.centerChannelColor, 0.1),
|
||||
},
|
||||
rowsContainer: {
|
||||
borderTopWidth: 1,
|
||||
@@ -733,10 +248,5 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
borderBottomColor: changeOpacity(theme.centerChannelColor, 0.1),
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
separator: {
|
||||
marginHorizontal: 15,
|
||||
height: 1,
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -9,11 +9,6 @@ import {General} from '@mm-redux/constants';
|
||||
|
||||
import ChannelInfo from './channel_info';
|
||||
|
||||
// ChannelInfoRow expects to receive the pinIcon as a number
|
||||
jest.mock('@assets/images/channel_info/pin.png', () => {
|
||||
return 1;
|
||||
});
|
||||
|
||||
jest.mock('@utils/theme', () => {
|
||||
const original = jest.requireActual('../../utils/theme');
|
||||
return {
|
||||
@@ -22,7 +17,7 @@ jest.mock('@utils/theme', () => {
|
||||
};
|
||||
});
|
||||
|
||||
describe('channel_info', () => {
|
||||
describe('channelInfo', () => {
|
||||
const intlMock = {
|
||||
formatMessage: jest.fn(),
|
||||
formatDate: jest.fn(),
|
||||
@@ -34,12 +29,6 @@ describe('channel_info', () => {
|
||||
now: jest.fn(),
|
||||
};
|
||||
const baseProps = {
|
||||
canDeleteChannel: true,
|
||||
canUnarchiveChannel: false,
|
||||
canConvertChannel: true,
|
||||
canManageUsers: true,
|
||||
viewArchivedChannels: true,
|
||||
canEditChannel: true,
|
||||
currentChannel: {
|
||||
id: '1234',
|
||||
display_name: 'Channel Name',
|
||||
@@ -54,35 +43,17 @@ describe('channel_info', () => {
|
||||
currentChannelMemberCount: 2,
|
||||
currentChannelGuestCount: 0,
|
||||
currentUserId: '1234',
|
||||
currentUserIsGuest: false,
|
||||
isChannelMuted: false,
|
||||
ignoreChannelMentions: false,
|
||||
isCurrent: true,
|
||||
isFavorite: false,
|
||||
status: 'status',
|
||||
theme: Preferences.THEMES.default,
|
||||
isBot: false,
|
||||
isTeammateGuest: false,
|
||||
isLandscape: false,
|
||||
actions: {
|
||||
clearPinnedPosts: jest.fn(),
|
||||
closeDMChannel: jest.fn(),
|
||||
closeGMChannel: jest.fn(),
|
||||
convertChannelToPrivate: jest.fn(),
|
||||
deleteChannel: jest.fn(),
|
||||
unarchiveChannel: jest.fn(),
|
||||
getChannelStats: jest.fn(),
|
||||
getChannel: jest.fn(),
|
||||
leaveChannel: jest.fn(),
|
||||
loadChannelsByTeamName: jest.fn(),
|
||||
favoriteChannel: jest.fn(),
|
||||
unfavoriteChannel: jest.fn(),
|
||||
getCustomEmojisInText: jest.fn(),
|
||||
selectFocusedPostId: jest.fn(),
|
||||
updateChannelNotifyProps: jest.fn(),
|
||||
selectPenultimateChannel: jest.fn(),
|
||||
setChannelDisplayName: jest.fn(),
|
||||
handleSelectChannel: jest.fn(),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -96,63 +67,6 @@ describe('channel_info', () => {
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should render convert to private button when user has team admin permissions', async () => {
|
||||
const wrapper = shallow(
|
||||
<ChannelInfo
|
||||
{...baseProps}
|
||||
/>,
|
||||
{context: {intl: intlMock}},
|
||||
);
|
||||
|
||||
const instance = wrapper.instance();
|
||||
const render = instance.renderConvertToPrivateRow();
|
||||
expect(render).toBeTruthy();
|
||||
});
|
||||
|
||||
test('should not render convert to private button when user is not team admin or above', async () => {
|
||||
const wrapper = shallow(
|
||||
<ChannelInfo
|
||||
{...baseProps}
|
||||
canConvertChannel={false}
|
||||
/>,
|
||||
{context: {intl: intlMock}},
|
||||
);
|
||||
|
||||
const instance = wrapper.instance();
|
||||
const render = instance.renderConvertToPrivateRow();
|
||||
expect(render).toBeFalsy();
|
||||
});
|
||||
|
||||
test('should not render convert to private button currentChannel is already private', async () => {
|
||||
const props = Object.assign({}, baseProps);
|
||||
props.currentChannel.type = General.PRIVATE_CHANNEL;
|
||||
const wrapper = shallow(
|
||||
<ChannelInfo
|
||||
{...props}
|
||||
/>,
|
||||
{context: {intl: intlMock}},
|
||||
);
|
||||
|
||||
const instance = wrapper.instance();
|
||||
const render = instance.renderConvertToPrivateRow();
|
||||
expect(render).toBeFalsy();
|
||||
});
|
||||
|
||||
test('should not render convert to private button when currentChannel is a default channel', async () => {
|
||||
const props = Object.assign({}, baseProps);
|
||||
props.currentChannel.name = General.DEFAULT_CHANNEL;
|
||||
const wrapper = shallow(
|
||||
<ChannelInfo
|
||||
{...props}
|
||||
/>,
|
||||
{context: {intl: intlMock}},
|
||||
);
|
||||
|
||||
const instance = wrapper.instance();
|
||||
const render = instance.renderConvertToPrivateRow();
|
||||
expect(render).toBeFalsy();
|
||||
});
|
||||
|
||||
test('should dismiss modal on close', () => {
|
||||
const dismissModal = jest.spyOn(NavigationActions, 'dismissModal');
|
||||
const wrapper = shallow(
|
||||
@@ -167,38 +81,4 @@ describe('channel_info', () => {
|
||||
instance.close();
|
||||
expect(dismissModal).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should render unarchive channel button when currentChannel is an archived channel', async () => {
|
||||
const props = Object.assign({}, baseProps);
|
||||
props.canUnarchiveChannel = true;
|
||||
props.currentChannel.delete_at = 1234566;
|
||||
|
||||
const wrapper = shallow(
|
||||
<ChannelInfo
|
||||
{...props}
|
||||
/>,
|
||||
{context: {intl: intlMock}},
|
||||
);
|
||||
|
||||
const instance = wrapper.instance();
|
||||
const render = instance.renderUnarchiveChannel();
|
||||
expect(render).toBeTruthy();
|
||||
});
|
||||
|
||||
test('should not render unarchive channel button when currentChannel is an active channel', async () => {
|
||||
const props = Object.assign({}, baseProps);
|
||||
props.canUnarchiveChannel = false;
|
||||
props.currentChannel.delete_at = 0;
|
||||
|
||||
const wrapper = shallow(
|
||||
<ChannelInfo
|
||||
{...props}
|
||||
/>,
|
||||
{context: {intl: intlMock}},
|
||||
);
|
||||
|
||||
const instance = wrapper.instance();
|
||||
const render = instance.renderUnarchiveChannel();
|
||||
expect(render).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Clipboard,
|
||||
Platform,
|
||||
Text,
|
||||
TouchableHighlight,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import {intlShape} from 'react-intl';
|
||||
import Clipboard from '@react-native-community/clipboard';
|
||||
|
||||
import {General} from '@mm-redux/constants';
|
||||
|
||||
@@ -52,7 +52,7 @@ export default class ChannelInfoHeader extends React.PureComponent {
|
||||
};
|
||||
|
||||
renderHasGuestText = (style) => {
|
||||
const {type, hasGuests, isTeammateGuest} = this.props;
|
||||
const {type, hasGuests, isLandscape, isTeammateGuest} = this.props;
|
||||
if (!hasGuests) {
|
||||
return null;
|
||||
}
|
||||
@@ -74,7 +74,7 @@ export default class ChannelInfoHeader extends React.PureComponent {
|
||||
defaultMessage = 'This channel has guests';
|
||||
}
|
||||
return (
|
||||
<View style={style.section}>
|
||||
<View style={[style.section, padding(isLandscape, -15)]}>
|
||||
<View style={style.row}>
|
||||
<FormattedText
|
||||
style={style.header}
|
||||
|
||||
@@ -102,6 +102,7 @@ channelInfoRow.propTypes = {
|
||||
iconColor: PropTypes.string,
|
||||
image: PropTypes.number,
|
||||
imageTintColor: PropTypes.string,
|
||||
isLandscape: PropTypes.bool,
|
||||
rightArrow: PropTypes.bool,
|
||||
textId: PropTypes.string.isRequired,
|
||||
togglable: PropTypes.bool,
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ChannelInfo -> ConvertPrivate should match snapshot for Convert to Private Channel 1`] = `
|
||||
<React.Fragment>
|
||||
<Separator
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<channelInfoRow
|
||||
action={[Function]}
|
||||
defaultMessage="Convert to Private Channel"
|
||||
icon="lock"
|
||||
isLandscape={false}
|
||||
rightArrow={false}
|
||||
shouldRender={true}
|
||||
textId="mobile.channel_info.convert"
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
togglable={false}
|
||||
/>
|
||||
</React.Fragment>
|
||||
`;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user