forked from Ivasoft/mattermost-mobile
Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
da6f326e17 | ||
|
|
4b6db7d9b1 | ||
|
|
c74450adfd | ||
|
|
0adf3618e2 | ||
|
|
c3569e27a9 | ||
|
|
385b667d65 | ||
|
|
116a07e1c8 | ||
|
|
bd767ad7fa | ||
|
|
f0fb2abba4 | ||
|
|
95e0e45fc7 | ||
|
|
c6df27e51b | ||
|
|
2851c50347 | ||
|
|
044a066c4a | ||
|
|
81b1ba8489 | ||
|
|
c2d513be36 | ||
|
|
7010672260 | ||
|
|
bb0e056fba | ||
|
|
e48371646b | ||
|
|
5421ddb2e6 | ||
|
|
17e62878ab | ||
|
|
ed153010f5 | ||
|
|
57eb0ddfd8 | ||
|
|
77b3dfff2d | ||
|
|
c9e6cd8073 | ||
|
|
91b4621433 | ||
|
|
5aeae45bd3 | ||
|
|
f57fa09257 | ||
|
|
40d94ee170 | ||
|
|
4e7d5a8907 | ||
|
|
8e88ab6e96 | ||
|
|
f054dfd8fb | ||
|
|
c16f3b2d8e | ||
|
|
eb0937d7ad |
@@ -23,7 +23,7 @@ executors:
|
||||
NODE_ENV: production
|
||||
BABEL_ENV: production
|
||||
macos:
|
||||
xcode: "12.0.0"
|
||||
xcode: "12.1.0"
|
||||
working_directory: ~/mattermost-mobile
|
||||
shell: /bin/bash --login -o pipefail
|
||||
|
||||
@@ -550,14 +550,14 @@ workflows:
|
||||
- test
|
||||
filters:
|
||||
branches:
|
||||
only: /^build-pr-.*/
|
||||
only: /^(build|android)-pr-.*/
|
||||
- build-ios-pr:
|
||||
context: mattermost-mobile-ios-pr
|
||||
requires:
|
||||
- test
|
||||
filters:
|
||||
branches:
|
||||
only: /^build-pr-.*/
|
||||
only: /^(build|ios)-pr-.*/
|
||||
|
||||
- build-android-unsigned:
|
||||
context: mattermost-mobile-unsigned
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"plugins": [
|
||||
"mattermost",
|
||||
"@typescript-eslint"
|
||||
"@typescript-eslint",
|
||||
"mattermost"
|
||||
],
|
||||
"settings": {
|
||||
"react": {
|
||||
|
||||
13
.flowconfig
13
.flowconfig
@@ -8,10 +8,6 @@
|
||||
; Ignore polyfills
|
||||
node_modules/react-native/Libraries/polyfills/.*
|
||||
|
||||
; These should not be required directly
|
||||
; require from fbjs/lib instead: require('fbjs/lib/warning')
|
||||
node_modules/warning/.*
|
||||
|
||||
; Flow doesn't support platforms
|
||||
.*/Libraries/Utilities/LoadingView.js
|
||||
|
||||
@@ -30,6 +26,8 @@ emoji=true
|
||||
esproposal.optional_chaining=enable
|
||||
esproposal.nullish_coalescing=enable
|
||||
|
||||
exact_by_default=true
|
||||
|
||||
module.file_ext=.js
|
||||
module.file_ext=.json
|
||||
module.file_ext=.ios.js
|
||||
@@ -44,10 +42,6 @@ suppress_type=$FlowFixMe
|
||||
suppress_type=$FlowFixMeProps
|
||||
suppress_type=$FlowFixMeState
|
||||
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\)
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\)?:? #[0-9]+
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
|
||||
|
||||
[lints]
|
||||
sketchy-null-number=warn
|
||||
sketchy-null-mixed=warn
|
||||
@@ -59,7 +53,6 @@ unsafe-getters-setters=warn
|
||||
inexact-spread=warn
|
||||
unnecessary-invariant=warn
|
||||
signature-verification-failure=warn
|
||||
deprecated-utility=error
|
||||
|
||||
[strict]
|
||||
deprecated-type
|
||||
@@ -71,4 +64,4 @@ untyped-import
|
||||
untyped-type-import
|
||||
|
||||
[version]
|
||||
^0.122.0
|
||||
^0.137.0
|
||||
|
||||
7
.gitattributes
vendored
7
.gitattributes
vendored
@@ -1,4 +1,3 @@
|
||||
*.pbxproj -text
|
||||
|
||||
# specific for windows script files
|
||||
*.bat text eol=crlf
|
||||
# Windows files should use crlf line endings
|
||||
# https://help.github.com/articles/dealing-with-line-endings/
|
||||
*.bat text eol=crlf
|
||||
1
.husky/.gitignore
vendored
Normal file
1
.husky/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
_
|
||||
4
.husky/pre-commit
Executable file
4
.husky/pre-commit
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
sh ./scripts/pre-commit.sh
|
||||
31
NOTICE.txt
31
NOTICE.txt
@@ -694,37 +694,6 @@ SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
## core-js
|
||||
|
||||
This product contains 'core-js' by Denis Pushkarev.
|
||||
|
||||
Modular standard library for JavaScript.
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/zloirock/core-js
|
||||
|
||||
* LICENSE: Copyright (c) 2014-2019 Denis Pushkarev
|
||||
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
## deep-equal
|
||||
|
||||
This product contains 'deep-equal' by James Halliday.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Mattermost Mobile
|
||||
|
||||
- **Minimum Server versions:** Current ESR version (5.25)
|
||||
- **Minimum Server versions:** Current ESR version (5.31)
|
||||
- **Supported iOS versions:** 11+
|
||||
- **Supported Android versions:** 7.0+
|
||||
|
||||
|
||||
@@ -132,8 +132,8 @@ android {
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
missingDimensionStrategy "RNNotifications.reactNativeVersion", "reactNative60"
|
||||
versionCode 347
|
||||
versionName "1.41.0"
|
||||
versionCode 353
|
||||
versionName "1.42.0"
|
||||
multiDexEnabled = true
|
||||
testBuildType System.getProperty('testBuildType', 'debug')
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
@@ -191,6 +191,10 @@ android {
|
||||
targetCompatibility 1.8
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
pickFirst '**/*.so'
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
repositories {
|
||||
|
||||
@@ -4,5 +4,10 @@
|
||||
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
||||
|
||||
<application android:usesCleartextTraffic="true" tools:targetApi="28" tools:ignore="GoogleAppIndexingWarning" />
|
||||
<application
|
||||
android:usesCleartextTraffic="true"
|
||||
tools:targetApi="28"
|
||||
tools:ignore="GoogleAppIndexingWarning">
|
||||
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
|
||||
</application>
|
||||
</manifest>
|
||||
|
||||
@@ -50,7 +50,6 @@
|
||||
<data android:scheme="mmauthbeta" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
|
||||
<service android:name=".NotificationDismissService"
|
||||
android:enabled="true"
|
||||
android:exported="false" />
|
||||
|
||||
@@ -16,12 +16,20 @@ import com.mattermost.react_native_interface.KeysReadableArray;
|
||||
|
||||
public class MattermostCredentialsHelper {
|
||||
static final String CURRENT_SERVER_URL = "@currentServerUrl";
|
||||
static KeychainModule keychainModule;
|
||||
static AsyncStorageHelper asyncStorage;
|
||||
|
||||
public static void getCredentialsForCurrentServer(ReactApplicationContext context, ResolvePromise promise) {
|
||||
final KeychainModule keychainModule = new KeychainModule(context);
|
||||
final AsyncStorageHelper asyncStorage = new AsyncStorageHelper(context);
|
||||
final ArrayList<String> keys = new ArrayList<String>(1);
|
||||
keys.add(CURRENT_SERVER_URL);
|
||||
|
||||
if (keychainModule == null) {
|
||||
keychainModule = new KeychainModule(context);
|
||||
}
|
||||
|
||||
if (asyncStorage == null) {
|
||||
asyncStorage = new AsyncStorageHelper(context);
|
||||
}
|
||||
KeysReadableArray asyncStorageKeys = new KeysReadableArray() {
|
||||
@Override
|
||||
public int size() {
|
||||
|
||||
@@ -72,7 +72,11 @@ public class RealPathUtil {
|
||||
split[1]
|
||||
};
|
||||
|
||||
return getDataColumn(context, contentUri, selection, selectionArgs);
|
||||
if (contentUri != null) {
|
||||
return getDataColumn(context, contentUri, selection, selectionArgs);
|
||||
} else {
|
||||
return getPathFromSavingTempFile(context, uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,4 +18,6 @@
|
||||
<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>
|
||||
<string name="inAppSessionAuth_title">In-App Session Auth</string>
|
||||
<string name="inAppSessionAuth_description">Instead of default flow from the mobile browser, enforce SSO with the WebView.</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<resources>
|
||||
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
|
||||
<style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
|
||||
|
||||
@@ -7,6 +7,12 @@
|
||||
android:description="@string/inAppPinCode_description"
|
||||
android:restrictionType="string"
|
||||
android:defaultValue="false" />
|
||||
<restriction
|
||||
android:key="inAppSessionAuth"
|
||||
android:title="@string/inAppSessionAuth_title"
|
||||
android:description="@string/inAppSessionAuth_description"
|
||||
android:restrictionType="string"
|
||||
android:defaultValue="false" />
|
||||
<restriction
|
||||
android:key="blurApplicationScreen"
|
||||
android:title="@string/blurApplicationScreen_title"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
buildscript {
|
||||
ext {
|
||||
buildToolsVersion = "29.0.2"
|
||||
buildToolsVersion = "29.0.3"
|
||||
minSdkVersion = 24
|
||||
compileSdkVersion = 29
|
||||
targetSdkVersion = 29
|
||||
@@ -10,6 +10,7 @@ buildscript {
|
||||
kotlinVersion = "1.3.61"
|
||||
firebaseVersion = "21.0.0"
|
||||
RNNKotlinVersion = kotlinVersion
|
||||
ndkVersion = "21.1.6352462"
|
||||
|
||||
}
|
||||
repositories {
|
||||
@@ -19,7 +20,7 @@ buildscript {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.5.3'
|
||||
classpath 'com.android.tools.build:gradle:4.1.0'
|
||||
classpath 'com.google.gms:google-services:4.2.0'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
|
||||
|
||||
|
||||
@@ -30,4 +30,4 @@ android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
|
||||
# Version of flipper SDK to use with React Native
|
||||
FLIPPER_VERSION=0.37.0
|
||||
FLIPPER_VERSION=0.75.1
|
||||
|
||||
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.5-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip
|
||||
|
||||
22
android/gradlew.bat
vendored
22
android/gradlew.bat
vendored
@@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
@@ -54,7 +54,7 @@ goto fail
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
@@ -64,28 +64,14 @@ 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%
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
|
||||
@@ -30,6 +30,7 @@ export function executeCommand(message: string, channelId: string, rootId: strin
|
||||
};
|
||||
|
||||
let msg = message;
|
||||
msg = filterEmDashForCommand(msg);
|
||||
|
||||
let cmdLength = msg.indexOf(' ');
|
||||
if (cmdLength < 0) {
|
||||
@@ -43,7 +44,7 @@ export function executeCommand(message: string, channelId: string, rootId: strin
|
||||
if (appsAreEnabled) {
|
||||
const parser = new AppCommandParser({dispatch, getState}, intl, args.channel_id, args.root_id);
|
||||
if (parser.isAppCommand(msg)) {
|
||||
const {call, errorMessage} = await parser.composeCallFromCommand(message);
|
||||
const {call, errorMessage} = await parser.composeCallFromCommand(msg);
|
||||
const createErrorMessage = (errMessage: string) => {
|
||||
return {error: {message: errMessage}};
|
||||
};
|
||||
@@ -64,7 +65,7 @@ export function executeCommand(message: string, channelId: string, rootId: strin
|
||||
switch (callResp.type) {
|
||||
case AppCallResponseTypes.OK:
|
||||
if (callResp.markdown) {
|
||||
dispatch(sendEphemeralPost(callResp.markdown, args.channel_id, args.parent_id));
|
||||
dispatch(sendEphemeralPost(callResp.markdown, args.channel_id, args.parent_id, callResp.app_metadata?.bot_user_id));
|
||||
}
|
||||
return {data: {}};
|
||||
case AppCallResponseTypes.FORM:
|
||||
@@ -90,3 +91,7 @@ export function executeCommand(message: string, channelId: string, rootId: strin
|
||||
return {data, error};
|
||||
};
|
||||
}
|
||||
|
||||
const filterEmDashForCommand = (command: string): string => {
|
||||
return command.replace(/\u2014/g, '--');
|
||||
};
|
||||
|
||||
@@ -28,12 +28,12 @@ import {getChannelSinceValue} from '@utils/channels';
|
||||
|
||||
import {getEmojisInPosts} from './emoji';
|
||||
|
||||
export function sendEphemeralPost(message, channelId = '', parentId = '') {
|
||||
export function sendEphemeralPost(message, channelId = '', parentId = '', userId = '0') {
|
||||
return async (dispatch, getState) => {
|
||||
const timestamp = Date.now();
|
||||
const post = {
|
||||
id: generateId(),
|
||||
user_id: '0',
|
||||
user_id: userId,
|
||||
channel_id: channelId || getCurrentChannelId(getState()),
|
||||
message,
|
||||
type: Posts.POST_TYPES.EPHEMERAL,
|
||||
|
||||
@@ -2,16 +2,14 @@
|
||||
|
||||
exports[`AtMention should match snapshot, no highlight 1`] = `
|
||||
<Text
|
||||
style={Object {}}
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "yellow",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"backgroundColor": "yellow",
|
||||
},
|
||||
]
|
||||
}
|
||||
style={Array []}
|
||||
>
|
||||
@John.Smith
|
||||
</Text>
|
||||
@@ -22,7 +20,11 @@ exports[`AtMention should match snapshot, with highlight 1`] = `
|
||||
<Text
|
||||
onLongPress={[Function]}
|
||||
onPress={[Function]}
|
||||
style={Object {}}
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "yellow",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
@@ -31,7 +33,8 @@ exports[`AtMention should match snapshot, with highlight 1`] = `
|
||||
"color": "#ff0000",
|
||||
},
|
||||
Object {
|
||||
"backgroundColor": "yellow",
|
||||
"backgroundColor": "#ffe577",
|
||||
"color": "#145dbf",
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -45,7 +48,11 @@ exports[`AtMention should match snapshot, without highlight 1`] = `
|
||||
<Text
|
||||
onLongPress={[Function]}
|
||||
onPress={[Function]}
|
||||
style={Object {}}
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "yellow",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
|
||||
@@ -10,7 +10,6 @@ import {displayUsername} from '@mm-redux/utils/user_utils';
|
||||
|
||||
import {showModal} from '@actions/navigation';
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import CustomPropTypes from '@constants/custom_prop_types';
|
||||
import BottomSheet from '@utils/bottom_sheet';
|
||||
import mattermostManaged from 'app/mattermost_managed';
|
||||
|
||||
@@ -19,9 +18,9 @@ export default class AtMention extends React.PureComponent {
|
||||
isSearchResult: PropTypes.bool,
|
||||
mentionKeys: PropTypes.array.isRequired,
|
||||
mentionName: PropTypes.string.isRequired,
|
||||
mentionStyle: CustomPropTypes.Style,
|
||||
mentionStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
|
||||
onPostPress: PropTypes.func,
|
||||
textStyle: CustomPropTypes.Style,
|
||||
textStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
|
||||
teammateNameDisplay: PropTypes.string,
|
||||
theme: PropTypes.object.isRequired,
|
||||
usersByUsername: PropTypes.object.isRequired,
|
||||
@@ -139,7 +138,7 @@ export default class AtMention extends React.PureComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
const {isSearchResult, mentionName, mentionStyle, onPostPress, teammateNameDisplay, textStyle, mentionKeys} = this.props;
|
||||
const {isSearchResult, mentionName, mentionStyle, onPostPress, teammateNameDisplay, textStyle, mentionKeys, theme} = this.props;
|
||||
const {user} = this.state;
|
||||
const mentionTextStyle = [];
|
||||
|
||||
@@ -155,9 +154,8 @@ export default class AtMention extends React.PureComponent {
|
||||
let styleText;
|
||||
|
||||
if (textStyle) {
|
||||
const {backgroundColor: bg, ...otherStyles} = StyleSheet.flatten(textStyle);
|
||||
backgroundColor = bg;
|
||||
styleText = otherStyles;
|
||||
backgroundColor = theme.mentionHighlightBg;
|
||||
styleText = textStyle;
|
||||
}
|
||||
|
||||
if (user?.username) {
|
||||
@@ -176,12 +174,12 @@ export default class AtMention extends React.PureComponent {
|
||||
} 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;
|
||||
highlighted = true;
|
||||
} else {
|
||||
mention = mentionName;
|
||||
}
|
||||
@@ -194,7 +192,7 @@ export default class AtMention extends React.PureComponent {
|
||||
}
|
||||
|
||||
if (suffix) {
|
||||
const suffixStyle = {...styleText, color: this.props.theme.centerChannelColor};
|
||||
const suffixStyle = {...StyleSheet.flatten(styleText), color: theme.centerChannelColor};
|
||||
suffixElement = (
|
||||
<Text style={suffixStyle}>
|
||||
{suffix}
|
||||
@@ -207,7 +205,7 @@ export default class AtMention extends React.PureComponent {
|
||||
}
|
||||
|
||||
if (highlighted) {
|
||||
mentionTextStyle.push({backgroundColor});
|
||||
mentionTextStyle.push({backgroundColor, color: theme.mentionColor});
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
|
||||
import React from 'react';
|
||||
import {shallow} from 'enzyme';
|
||||
|
||||
import {Preferences} from '@mm-redux/constants';
|
||||
|
||||
import AtMention from './at_mention.js';
|
||||
|
||||
describe('AtMention', () => {
|
||||
@@ -13,7 +16,7 @@ describe('AtMention', () => {
|
||||
mentionName: 'John.Smith',
|
||||
mentionStyle: {color: '#ff0000'},
|
||||
textStyle: {backgroundColor: 'yellow'},
|
||||
theme: {},
|
||||
theme: Preferences.THEMES.default,
|
||||
};
|
||||
|
||||
test('should match snapshot, no highlight', () => {
|
||||
|
||||
@@ -3,17 +3,17 @@
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {SectionList} from 'react-native';
|
||||
import {Platform, SectionList} from 'react-native';
|
||||
|
||||
import {AT_MENTION_REGEX, AT_MENTION_SEARCH_REGEX} from '@constants/autocomplete';
|
||||
import AtMentionItem from '@components/autocomplete/at_mention_item';
|
||||
import AutocompleteSectionHeader from '@components/autocomplete/autocomplete_section_header';
|
||||
import SpecialMentionItem from '@components/autocomplete/special_mention_item';
|
||||
import GroupMentionItem from '@components/autocomplete/at_mention_group/at_mention_group';
|
||||
import {RequestStatus} from '@mm-redux/constants';
|
||||
|
||||
import {AT_MENTION_REGEX, AT_MENTION_SEARCH_REGEX} from 'app/constants/autocomplete';
|
||||
import AtMentionItem from 'app/components/autocomplete/at_mention_item';
|
||||
import AutocompleteSectionHeader from 'app/components/autocomplete/autocomplete_section_header';
|
||||
import SpecialMentionItem from 'app/components/autocomplete/special_mention_item';
|
||||
import GroupMentionItem from 'app/components/autocomplete/at_mention_group/at_mention_group';
|
||||
import {makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
import {t} from 'app/utils/i18n';
|
||||
import {debounce} from '@mm-redux/actions/helpers';
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {t} from '@utils/i18n';
|
||||
|
||||
export default class AtMention extends PureComponent {
|
||||
static propTypes = {
|
||||
@@ -54,9 +54,15 @@ export default class AtMention extends PureComponent {
|
||||
sections: [],
|
||||
};
|
||||
}
|
||||
|
||||
runSearch = debounce((currentTeamId, channelId, matchTerm) => {
|
||||
this.props.actions.autocompleteUsers(matchTerm, currentTeamId, channelId);
|
||||
}, 200);
|
||||
|
||||
updateSections(sections) {
|
||||
this.setState({sections});
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (this.props.matchTerm !== prevProps.matchTerm) {
|
||||
if (this.props.matchTerm === null) {
|
||||
@@ -68,9 +74,9 @@ export default class AtMention extends PureComponent {
|
||||
this.props.onResultCountChange(sections.reduce((total, section) => total + section.data.length, 0));
|
||||
|
||||
// Update user autocomplete list with results of server request
|
||||
const {currentTeamId, currentChannelId} = this.props;
|
||||
const {currentTeamId, currentChannelId, matchTerm} = this.props;
|
||||
const channelId = this.props.isSearch ? '' : currentChannelId;
|
||||
this.props.actions.autocompleteUsers(this.props.matchTerm, currentTeamId, channelId);
|
||||
this.runSearch(currentTeamId, channelId, matchTerm);
|
||||
}
|
||||
}
|
||||
if (this.props.matchTerm !== null && this.props.matchTerm === prevProps.matchTerm) {
|
||||
@@ -251,15 +257,16 @@ export default class AtMention extends PureComponent {
|
||||
|
||||
return (
|
||||
<SectionList
|
||||
testID='at_mention_suggestion.list'
|
||||
keyboardShouldPersistTaps='always'
|
||||
keyExtractor={this.keyExtractor}
|
||||
style={[style.listView, {maxHeight: maxListHeight}]}
|
||||
sections={sections}
|
||||
renderItem={this.renderItem}
|
||||
renderSectionHeader={this.renderSectionHeader}
|
||||
initialNumToRender={10}
|
||||
nestedScrollEnabled={nestedScrollEnabled}
|
||||
removeClippedSubviews={Platform.OS === 'android'}
|
||||
renderItem={this.renderItem}
|
||||
renderSectionHeader={this.renderSectionHeader}
|
||||
style={[style.listView, {maxHeight: maxListHeight}]}
|
||||
sections={sections}
|
||||
testID='at_mention_suggestion.list'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ import {t} from 'app/utils/i18n';
|
||||
export default class ChannelMention extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
searchChannels: PropTypes.func.isRequired,
|
||||
autocompleteChannelsForSearch: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
currentTeamId: PropTypes.string.isRequired,
|
||||
@@ -225,15 +224,16 @@ export default class ChannelMention extends PureComponent {
|
||||
|
||||
return (
|
||||
<SectionList
|
||||
testID='channel_mention_suggestion.list'
|
||||
keyboardShouldPersistTaps='always'
|
||||
keyExtractor={this.keyExtractor}
|
||||
style={[style.listView, {maxHeight: maxListHeight}]}
|
||||
sections={sections}
|
||||
renderItem={this.renderItem}
|
||||
renderSectionHeader={this.renderSectionHeader}
|
||||
initialNumToRender={10}
|
||||
nestedScrollEnabled={nestedScrollEnabled}
|
||||
removeClippedSubviews={Platform.OS === 'android'}
|
||||
renderItem={this.renderItem}
|
||||
renderSectionHeader={this.renderSectionHeader}
|
||||
style={[style.listView, {maxHeight: maxListHeight}]}
|
||||
sections={sections}
|
||||
testID='channel_mention_suggestion.list'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {searchChannels, autocompleteChannelsForSearch} from '@mm-redux/actions/channels';
|
||||
import {autocompleteChannelsForSearch} from '@mm-redux/actions/channels';
|
||||
import {getMyChannelMemberships} from '@mm-redux/selectors/entities/channels';
|
||||
import {getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
import {getCurrentTeamId} from '@mm-redux/selectors/entities/teams';
|
||||
@@ -56,7 +56,6 @@ function mapStateToProps(state, ownProps) {
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
searchChannels,
|
||||
autocompleteChannelsForSearch,
|
||||
}, dispatch),
|
||||
};
|
||||
|
||||
@@ -3036,7 +3036,7 @@ exports[`components/autocomplete/emoji_suggestion should match snapshot 2`] = `
|
||||
numColumns={1}
|
||||
onEndReachedThreshold={2}
|
||||
pageSize={10}
|
||||
removeClippedSubviews={false}
|
||||
removeClippedSubviews={true}
|
||||
renderItem={[Function]}
|
||||
scrollEventThrottle={50}
|
||||
style={
|
||||
|
||||
@@ -230,6 +230,7 @@ export default class EmojiSuggestion extends PureComponent {
|
||||
extraData={this.state}
|
||||
data={this.state.dataSource}
|
||||
keyExtractor={this.keyExtractor}
|
||||
removeClippedSubviews={true}
|
||||
renderItem={this.renderItem}
|
||||
pageSize={10}
|
||||
initialListSize={10}
|
||||
|
||||
@@ -37,7 +37,7 @@ exports[`components/autocomplete/slash_suggestion should match snapshot 1`] = `
|
||||
nestedScrollEnabled={false}
|
||||
numColumns={1}
|
||||
onEndReachedThreshold={2}
|
||||
removeClippedSubviews={false}
|
||||
removeClippedSubviews={true}
|
||||
renderItem={[Function]}
|
||||
scrollEventThrottle={50}
|
||||
style={
|
||||
|
||||
@@ -416,7 +416,7 @@ describe('AppCommandParser', () => {
|
||||
{
|
||||
title: 'error: unexpected positional',
|
||||
command: '/jira issue create wrong',
|
||||
submit: {expectError: 'Command does not accept {positionX} positional arguments.'},
|
||||
submit: {expectError: 'Unable to identify argument.'},
|
||||
},
|
||||
{
|
||||
title: 'error: multiple equal signs',
|
||||
@@ -443,11 +443,6 @@ describe('AppCommandParser', () => {
|
||||
});
|
||||
|
||||
describe('getSuggestions', () => {
|
||||
test('just the app command', async () => {
|
||||
const suggestions = await parser.getSuggestions('/jira');
|
||||
expect(suggestions).toEqual([]);
|
||||
});
|
||||
|
||||
test('subcommand 1', async () => {
|
||||
const suggestions = await parser.getSuggestions('/jira ');
|
||||
expect(suggestions).toEqual([
|
||||
@@ -574,7 +569,7 @@ describe('AppCommandParser', () => {
|
||||
Description: 'The Jira issue key',
|
||||
Hint: '',
|
||||
IconData: '',
|
||||
Suggestion: '',
|
||||
Suggestion: 'issue: ""',
|
||||
},
|
||||
]);
|
||||
});
|
||||
@@ -726,7 +721,7 @@ describe('AppCommandParser', () => {
|
||||
Description: 'The Jira issue summary',
|
||||
Hint: '',
|
||||
IconData: 'Create icon',
|
||||
Suggestion: '',
|
||||
Suggestion: 'summary: ""',
|
||||
},
|
||||
]);
|
||||
});
|
||||
@@ -739,7 +734,7 @@ describe('AppCommandParser', () => {
|
||||
Description: 'The Jira issue summary',
|
||||
Hint: '',
|
||||
IconData: 'Create icon',
|
||||
Suggestion: 'Sum',
|
||||
Suggestion: 'summary: "Sum"',
|
||||
},
|
||||
]);
|
||||
});
|
||||
@@ -752,7 +747,7 @@ describe('AppCommandParser', () => {
|
||||
Description: 'The Jira issue summary',
|
||||
Hint: '',
|
||||
IconData: 'Create icon',
|
||||
Suggestion: 'Sum',
|
||||
Suggestion: 'summary: "Sum"',
|
||||
},
|
||||
]);
|
||||
});
|
||||
@@ -765,7 +760,7 @@ describe('AppCommandParser', () => {
|
||||
Description: 'The Jira issue summary',
|
||||
Hint: '',
|
||||
IconData: 'Create icon',
|
||||
Suggestion: 'Sum',
|
||||
Suggestion: 'summary: `Sum`',
|
||||
},
|
||||
]);
|
||||
});
|
||||
@@ -778,7 +773,7 @@ describe('AppCommandParser', () => {
|
||||
Description: 'The Jira issue summary',
|
||||
Hint: '',
|
||||
IconData: 'Create icon',
|
||||
Suggestion: '',
|
||||
Suggestion: 'summary: ""',
|
||||
},
|
||||
]);
|
||||
});
|
||||
@@ -836,9 +831,9 @@ describe('AppCommandParser', () => {
|
||||
{
|
||||
Complete: 'jira issue create --project KT --summary "great feature" --epic',
|
||||
Suggestion: '',
|
||||
Description: 'No matching options.',
|
||||
Hint: '',
|
||||
IconData: '',
|
||||
Description: '',
|
||||
Hint: 'No matching options.',
|
||||
IconData: 'error',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
getChannelByNameAndTeamName,
|
||||
getCurrentTeam,
|
||||
selectChannelByName,
|
||||
errorMessage as parserErrorMessage,
|
||||
} from './app_command_parser_dependencies';
|
||||
|
||||
export type Store = {
|
||||
@@ -65,7 +66,7 @@ export const ParseState = keyMirror({
|
||||
});
|
||||
|
||||
interface FormsCache {
|
||||
getForm: (location: string, binding: AppBinding) => Promise<AppForm | undefined>;
|
||||
getForm: (location: string, binding: AppBinding) => Promise<{form?: AppForm, error?: string} | undefined>;
|
||||
}
|
||||
|
||||
interface Intl {
|
||||
@@ -100,17 +101,6 @@ export class ParsedCommand {
|
||||
return this;
|
||||
};
|
||||
|
||||
errorMessage = (): string => {
|
||||
return this.intl.formatMessage({
|
||||
id: 'apps.error.parser',
|
||||
defaultMessage: 'Parsing error: {error}.\n```\n{command}\n{space}^\n```',
|
||||
}, {
|
||||
error: this.error,
|
||||
command: this.command,
|
||||
space: ' '.repeat(this.i),
|
||||
});
|
||||
}
|
||||
|
||||
// matchBinding finds the closest matching command binding.
|
||||
matchBinding = async (commandBindings: AppBinding[], autocompleteMode = false): Promise<ParsedCommand> => {
|
||||
if (commandBindings.length === 0) {
|
||||
@@ -225,7 +215,11 @@ export class ParsedCommand {
|
||||
|
||||
this.form = this.binding.form;
|
||||
if (!this.form) {
|
||||
this.form = await this.formsCache.getForm(this.location, this.binding);
|
||||
const fetched = await this.formsCache.getForm(this.location, this.binding);
|
||||
if (fetched?.error) {
|
||||
return this.asError(fetched.error);
|
||||
}
|
||||
this.form = fetched?.form;
|
||||
}
|
||||
|
||||
return this;
|
||||
@@ -273,9 +267,7 @@ export class ParsedCommand {
|
||||
if (!field) {
|
||||
return this.asError(this.intl.formatMessage({
|
||||
id: 'apps.error.parser.no_argument_pos_x',
|
||||
defaultMessage: 'Command does not accept {positionX} positional arguments.',
|
||||
}, {
|
||||
positionX: this.position,
|
||||
defaultMessage: 'Unable to identify argument.',
|
||||
}));
|
||||
}
|
||||
this.field = field;
|
||||
@@ -568,7 +560,7 @@ export class AppCommandParser {
|
||||
parsed = await parsed.matchBinding(commandBindings, false);
|
||||
parsed = parsed.parseForm(false);
|
||||
if (parsed.state === ParseState.Error) {
|
||||
return {call: null, errorMessage: parsed.errorMessage()};
|
||||
return {call: null, errorMessage: parserErrorMessage(this.intl, parsed.error, parsed.command, parsed.i)};
|
||||
}
|
||||
|
||||
const missing = this.getMissingFields(parsed);
|
||||
@@ -592,8 +584,9 @@ export class AppCommandParser {
|
||||
const result: AutocompleteSuggestion[] = [];
|
||||
|
||||
const bindings = this.getCommandBindings();
|
||||
|
||||
for (const binding of bindings) {
|
||||
let base = binding.app_id;
|
||||
let base = binding.label;
|
||||
if (!base) {
|
||||
continue;
|
||||
}
|
||||
@@ -601,10 +594,11 @@ export class AppCommandParser {
|
||||
if (base[0] !== '/') {
|
||||
base = '/' + base;
|
||||
}
|
||||
|
||||
if (base.startsWith(command)) {
|
||||
result.push({
|
||||
Complete: binding.label,
|
||||
Suggestion: base,
|
||||
Complete: base.substring(1),
|
||||
Description: binding.description || '',
|
||||
Hint: binding.hint || '',
|
||||
IconData: binding.icon || '',
|
||||
@@ -618,6 +612,7 @@ export class AppCommandParser {
|
||||
// getSuggestions returns suggestions for subcommands and/or form arguments
|
||||
public getSuggestions = async (pretext: string): Promise<AutocompleteSuggestion[]> => {
|
||||
let parsed = new ParsedCommand(pretext, this, this.intl);
|
||||
let suggestions: AutocompleteSuggestion[] = [];
|
||||
|
||||
const commandBindings = this.getCommandBindings();
|
||||
if (!commandBindings) {
|
||||
@@ -625,13 +620,19 @@ export class AppCommandParser {
|
||||
}
|
||||
|
||||
parsed = await parsed.matchBinding(commandBindings, true);
|
||||
let suggestions: AutocompleteSuggestion[] = [];
|
||||
if (parsed.state === ParseState.Error) {
|
||||
suggestions = this.getErrorSuggestion(parsed);
|
||||
}
|
||||
|
||||
if (parsed.state === ParseState.Command) {
|
||||
suggestions = this.getCommandSuggestions(parsed);
|
||||
}
|
||||
|
||||
if (parsed.form || parsed.incomplete) {
|
||||
parsed = parsed.parseForm(true);
|
||||
if (parsed.state === ParseState.Error) {
|
||||
suggestions = this.getErrorSuggestion(parsed);
|
||||
}
|
||||
const argSuggestions = await this.getParameterSuggestions(parsed);
|
||||
suggestions = suggestions.concat(argSuggestions);
|
||||
}
|
||||
@@ -654,11 +655,38 @@ export class AppCommandParser {
|
||||
if (execute) {
|
||||
suggestions = [execute, ...suggestions];
|
||||
}
|
||||
} else if (suggestions.length === 0 && (parsed.field?.type !== AppFieldTypes.USER && parsed.field?.type !== AppFieldTypes.CHANNEL)) {
|
||||
suggestions = this.getNoMatchingSuggestion();
|
||||
}
|
||||
|
||||
return suggestions.map((suggestion) => this.decorateSuggestionComplete(parsed, suggestion));
|
||||
}
|
||||
|
||||
getNoMatchingSuggestion = () => {
|
||||
return [{
|
||||
Complete: '',
|
||||
Suggestion: '',
|
||||
Hint: this.intl.formatMessage({
|
||||
id: 'apps.suggestion.no_suggestion',
|
||||
defaultMessage: 'No matching suggestions.',
|
||||
}),
|
||||
IconData: 'error',
|
||||
Description: '',
|
||||
}];
|
||||
}
|
||||
|
||||
getErrorSuggestion = (parsed: ParsedCommand) => {
|
||||
return [{
|
||||
Complete: '',
|
||||
Suggestion: '',
|
||||
Hint: this.intl.formatMessage({
|
||||
id: 'apps.suggestion.errors.parser_error',
|
||||
defaultMessage: 'Parsing error',
|
||||
}),
|
||||
IconData: 'error',
|
||||
Description: parsed.error,
|
||||
}];
|
||||
}
|
||||
|
||||
// composeCallFromParsed creates the form submission call
|
||||
composeCallFromParsed = async (parsed: ParsedCommand): Promise<{call: AppCallRequest | null, errorMessage?: string}> => {
|
||||
if (!parsed.binding) {
|
||||
@@ -789,7 +817,7 @@ export class AppCommandParser {
|
||||
goBackSpace = 1;
|
||||
}
|
||||
let complete = parsed.command.substring(0, parsed.incompleteStart - goBackSpace);
|
||||
complete += choice.Complete || choice.Suggestion;
|
||||
complete += choice.Complete === undefined ? choice.Suggestion : choice.Complete;
|
||||
choice.Hint = choice.Hint || '';
|
||||
complete = complete.substring(1);
|
||||
|
||||
@@ -821,7 +849,7 @@ export class AppCommandParser {
|
||||
isAppCommand = (pretext: string): boolean => {
|
||||
const command = pretext.toLowerCase();
|
||||
for (const binding of this.getCommandBindings()) {
|
||||
let base = binding.app_id;
|
||||
let base = binding.label;
|
||||
if (!base) {
|
||||
continue;
|
||||
}
|
||||
@@ -857,7 +885,7 @@ export class AppCommandParser {
|
||||
}
|
||||
|
||||
// fetchForm unconditionaly retrieves the form for the given binding (subcommand)
|
||||
fetchForm = async (binding: AppBinding): Promise<AppForm | undefined> => {
|
||||
fetchForm = async (binding: AppBinding): Promise<{form?: AppForm, error?: string} | undefined> => {
|
||||
if (!binding.call) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -870,11 +898,10 @@ export class AppCommandParser {
|
||||
const res = await this.store.dispatch(doAppCall(payload, AppCallTypes.FORM, this.intl));
|
||||
if (res.error) {
|
||||
const errorResponse = res.error as AppCallResponse;
|
||||
this.displayError(errorResponse.error || this.intl.formatMessage({
|
||||
return {error: errorResponse.error || this.intl.formatMessage({
|
||||
id: 'apps.error.unknown',
|
||||
defaultMessage: 'Unknown error.',
|
||||
}));
|
||||
return undefined;
|
||||
})};
|
||||
}
|
||||
|
||||
const callResponse = res.data as AppCallResponse;
|
||||
@@ -883,35 +910,33 @@ export class AppCommandParser {
|
||||
break;
|
||||
case AppCallResponseTypes.NAVIGATE:
|
||||
case AppCallResponseTypes.OK:
|
||||
this.displayError(this.intl.formatMessage({
|
||||
return {error: this.intl.formatMessage({
|
||||
id: 'apps.error.responses.unexpected_type',
|
||||
defaultMessage: 'App response type was not expected. Response type: {type}',
|
||||
}, {
|
||||
type: callResponse.type,
|
||||
}));
|
||||
return undefined;
|
||||
})};
|
||||
default:
|
||||
this.displayError(this.intl.formatMessage({
|
||||
return {error: this.intl.formatMessage({
|
||||
id: 'apps.error.responses.unknown_type',
|
||||
defaultMessage: 'App response type not supported. Response type: {type}.',
|
||||
}, {
|
||||
type: callResponse.type,
|
||||
}));
|
||||
return undefined;
|
||||
})};
|
||||
}
|
||||
|
||||
return callResponse.form;
|
||||
return {form: callResponse.form};
|
||||
}
|
||||
|
||||
getForm = async (location: string, binding: AppBinding): Promise<AppForm | undefined> => {
|
||||
getForm = async (location: string, binding: AppBinding): Promise<{form?: AppForm, error?: string} | undefined> => {
|
||||
const form = this.forms[location];
|
||||
if (form) {
|
||||
return form;
|
||||
return {form};
|
||||
}
|
||||
|
||||
const fetched = await this.fetchForm(binding);
|
||||
if (fetched) {
|
||||
this.forms[location] = fetched;
|
||||
if (fetched?.form) {
|
||||
this.forms[location] = fetched.form;
|
||||
}
|
||||
return fetched;
|
||||
}
|
||||
@@ -1054,7 +1079,7 @@ export class AppCommandParser {
|
||||
|
||||
return [{
|
||||
Complete: complete,
|
||||
Suggestion: parsed.incomplete,
|
||||
Suggestion: `${parsed.field.label || parsed.field.name}: ${delimiter || '"'}${parsed.incomplete}${delimiter || '"'}`,
|
||||
Description: f.description || '',
|
||||
Hint: '',
|
||||
IconData: parsed.binding?.icon || '',
|
||||
@@ -1069,12 +1094,12 @@ export class AppCommandParser {
|
||||
return [{
|
||||
Complete: '',
|
||||
Suggestion: '',
|
||||
Hint: '',
|
||||
Description: this.intl.formatMessage({
|
||||
Hint: this.intl.formatMessage({
|
||||
id: 'apps.suggestion.no_static',
|
||||
defaultMessage: 'No matching options.',
|
||||
}),
|
||||
IconData: '',
|
||||
Description: '',
|
||||
IconData: 'error',
|
||||
}];
|
||||
}
|
||||
return opts.map((opt) => {
|
||||
@@ -1099,7 +1124,7 @@ export class AppCommandParser {
|
||||
const f = parsed.field;
|
||||
if (!f) {
|
||||
// Should never happen
|
||||
return this.makeSuggestionError(this.intl.formatMessage({
|
||||
return this.makeDynamicSelectSuggestionError(this.intl.formatMessage({
|
||||
id: 'apps.error.parser.unexpected_error',
|
||||
defaultMessage: 'Unexpected error.',
|
||||
}));
|
||||
@@ -1107,7 +1132,7 @@ export class AppCommandParser {
|
||||
|
||||
const {call, errorMessage} = await this.composeCallFromParsed(parsed);
|
||||
if (!call) {
|
||||
return this.makeSuggestionError(this.intl.formatMessage({
|
||||
return this.makeDynamicSelectSuggestionError(this.intl.formatMessage({
|
||||
id: 'apps.error.lookup.error_preparing_request',
|
||||
defaultMessage: 'Error preparing lookup request: {errorMessage}',
|
||||
}, {
|
||||
@@ -1121,7 +1146,7 @@ export class AppCommandParser {
|
||||
const res = await this.store.dispatch(doAppCall<ResponseType>(call, AppCallTypes.LOOKUP, this.intl));
|
||||
if (res.error) {
|
||||
const errorResponse = res.error as AppCallResponse;
|
||||
return this.makeSuggestionError(errorResponse.error || this.intl.formatMessage({
|
||||
return this.makeDynamicSelectSuggestionError(errorResponse.error || this.intl.formatMessage({
|
||||
id: 'apps.error.unknown',
|
||||
defaultMessage: 'Unknown error.',
|
||||
}));
|
||||
@@ -1133,14 +1158,14 @@ export class AppCommandParser {
|
||||
break;
|
||||
case AppCallResponseTypes.NAVIGATE:
|
||||
case AppCallResponseTypes.FORM:
|
||||
return this.makeSuggestionError(this.intl.formatMessage({
|
||||
return this.makeDynamicSelectSuggestionError(this.intl.formatMessage({
|
||||
id: 'apps.error.responses.unexpected_type',
|
||||
defaultMessage: 'App response type was not expected. Response type: {type}',
|
||||
}, {
|
||||
type: callResponse.type,
|
||||
}));
|
||||
default:
|
||||
return this.makeSuggestionError(this.intl.formatMessage({
|
||||
return this.makeDynamicSelectSuggestionError(this.intl.formatMessage({
|
||||
id: 'apps.error.responses.unknown_type',
|
||||
defaultMessage: 'App response type not supported. Response type: {type}.',
|
||||
}, {
|
||||
@@ -1153,7 +1178,10 @@ export class AppCommandParser {
|
||||
return [{
|
||||
Complete: '',
|
||||
Suggestion: '',
|
||||
Hint: '',
|
||||
Hint: this.intl.formatMessage({
|
||||
id: 'apps.suggestion.no_static',
|
||||
defaultMessage: 'No matching options.',
|
||||
}),
|
||||
IconData: '',
|
||||
Description: this.intl.formatMessage({
|
||||
id: 'apps.suggestion.no_dynamic',
|
||||
@@ -1179,7 +1207,7 @@ export class AppCommandParser {
|
||||
});
|
||||
}
|
||||
|
||||
makeSuggestionError = (message: string): AutocompleteSuggestion[] => {
|
||||
makeDynamicSelectSuggestionError = (message: string): AutocompleteSuggestion[] => {
|
||||
const errMsg = this.intl.formatMessage({
|
||||
id: 'apps.error',
|
||||
defaultMessage: 'Error: {error}',
|
||||
@@ -1188,9 +1216,12 @@ export class AppCommandParser {
|
||||
});
|
||||
return [{
|
||||
Complete: '',
|
||||
Suggestion: '',
|
||||
Suggestion: this.intl.formatMessage({
|
||||
id: 'apps.suggestion.dynamic.error',
|
||||
defaultMessage: 'Dynamic select error',
|
||||
}),
|
||||
Hint: '',
|
||||
IconData: '',
|
||||
IconData: 'error',
|
||||
Description: errMsg,
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -76,3 +76,12 @@ export const displayError = (intl: typeof intlShape, body: string) => {
|
||||
});
|
||||
Alert.alert(title, body);
|
||||
};
|
||||
|
||||
export const errorMessage = (intl: typeof intlShape, error: string, _command: string, _position: number): string => { // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
return intl.formatMessage({
|
||||
id: 'apps.error.parser',
|
||||
defaultMessage: 'Parsing error: {error}',
|
||||
}, {
|
||||
error,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
Platform,
|
||||
} from 'react-native';
|
||||
|
||||
import {analytics} from '@init/analytics.ts';
|
||||
import {analytics} from '@init/analytics';
|
||||
import {Client4} from '@mm-redux/client';
|
||||
import {isMinimumServerVersion} from '@mm-redux/utils/helpers';
|
||||
import {Command, AutocompleteSuggestion, CommandArgs} from '@mm-redux/types/integrations';
|
||||
@@ -243,6 +243,7 @@ export default class SlashSuggestion extends PureComponent<Props, State> {
|
||||
theme={this.props.theme}
|
||||
suggestion={item.Suggestion}
|
||||
complete={item.Complete}
|
||||
icon={item.IconData}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -265,6 +266,7 @@ export default class SlashSuggestion extends PureComponent<Props, State> {
|
||||
extraData={this.state}
|
||||
data={this.state.dataSource}
|
||||
keyExtractor={this.keyExtractor}
|
||||
removeClippedSubviews={true}
|
||||
renderItem={this.renderItem}
|
||||
nestedScrollEnabled={nestedScrollEnabled}
|
||||
/>
|
||||
|
||||
@@ -9,7 +9,9 @@ import {Theme} from '@mm-redux/types/preferences';
|
||||
|
||||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import FastImage from 'react-native-fast-image';
|
||||
const slashIcon = require('@assets/images/autocomplete/slash_command.png');
|
||||
const bangIcon = require('@assets/images/autocomplete/slash_command_error.png');
|
||||
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
return {
|
||||
@@ -55,6 +57,7 @@ type Props = {
|
||||
hint: string;
|
||||
onPress: (complete: string) => void;
|
||||
suggestion: string;
|
||||
icon: string;
|
||||
theme: Theme;
|
||||
}
|
||||
|
||||
@@ -88,6 +91,32 @@ const SlashSuggestionItem = (props: Props) => {
|
||||
}
|
||||
}
|
||||
|
||||
let image = (
|
||||
<Image
|
||||
style={style.iconColor}
|
||||
width={10}
|
||||
height={16}
|
||||
source={slashIcon}
|
||||
/>
|
||||
);
|
||||
if (props.icon === 'error') {
|
||||
image = (
|
||||
<Image
|
||||
style={style.iconColor}
|
||||
width={10}
|
||||
height={16}
|
||||
source={bangIcon}
|
||||
/>
|
||||
);
|
||||
} else if (props.icon && props.icon.startsWith('http')) {
|
||||
image = (
|
||||
<FastImage
|
||||
source={{uri: props.icon}}
|
||||
style={{width: 16, height: 16}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<TouchableWithFeedback
|
||||
onPress={completeSuggestion}
|
||||
@@ -97,12 +126,7 @@ const SlashSuggestionItem = (props: Props) => {
|
||||
>
|
||||
<View style={style.container}>
|
||||
<View style={style.icon}>
|
||||
<Image
|
||||
style={style.iconColor}
|
||||
width={10}
|
||||
height={16}
|
||||
source={slashIcon}
|
||||
/>
|
||||
{image}
|
||||
</View>
|
||||
<View style={style.suggestionContainer}>
|
||||
<Text style={style.suggestionName}>{`${suggestionText}`}</Text>
|
||||
|
||||
@@ -29,6 +29,35 @@ exports[`Avatars should match snapshot for overflow 1`] = `
|
||||
showStatus={false}
|
||||
size={24}
|
||||
testID="avatars.profile_picture"
|
||||
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",
|
||||
}
|
||||
}
|
||||
userId="user1"
|
||||
/>
|
||||
</View>
|
||||
@@ -51,6 +80,35 @@ exports[`Avatars should match snapshot for overflow 1`] = `
|
||||
showStatus={false}
|
||||
size={24}
|
||||
testID="avatars.profile_picture"
|
||||
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",
|
||||
}
|
||||
}
|
||||
userId="user2"
|
||||
/>
|
||||
</View>
|
||||
@@ -73,6 +131,35 @@ exports[`Avatars should match snapshot for overflow 1`] = `
|
||||
showStatus={false}
|
||||
size={24}
|
||||
testID="avatars.profile_picture"
|
||||
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",
|
||||
}
|
||||
}
|
||||
userId="user3"
|
||||
/>
|
||||
</View>
|
||||
@@ -151,6 +238,35 @@ exports[`Avatars should match snapshot for single avatar 1`] = `
|
||||
showStatus={false}
|
||||
size={24}
|
||||
testID="avatars.profile_picture"
|
||||
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",
|
||||
}
|
||||
}
|
||||
userId="user1"
|
||||
/>
|
||||
</View>
|
||||
|
||||
@@ -109,6 +109,7 @@ export default class Avatars extends PureComponent<AvatarsProps> {
|
||||
size={ViewTypes.AVATAR_LIST_PICTURE_SIZE}
|
||||
showStatus={false}
|
||||
testID='avatars.profile_picture'
|
||||
theme={theme}
|
||||
/>
|
||||
</View>
|
||||
))}
|
||||
|
||||
@@ -6,10 +6,9 @@ import PropTypes from 'prop-types';
|
||||
import {Text} from 'react-native';
|
||||
import {intlShape} from 'react-intl';
|
||||
|
||||
import CustomPropTypes from 'app/constants/custom_prop_types';
|
||||
import {t} from 'app/utils/i18n';
|
||||
import {alertErrorWithFallback} from 'app/utils/general';
|
||||
import {popToRoot, dismissAllModals} from 'app/actions/navigation';
|
||||
import {popToRoot, dismissAllModals} from '@actions/navigation';
|
||||
import {t} from '@utils/i18n';
|
||||
import {alertErrorWithFallback} from '@utils/general';
|
||||
|
||||
import {getChannelFromChannelName} from './channel_link_utils';
|
||||
|
||||
@@ -19,9 +18,9 @@ export default class ChannelLink extends React.PureComponent {
|
||||
channelMentions: PropTypes.object,
|
||||
currentTeamId: PropTypes.string.isRequired,
|
||||
currentUserId: PropTypes.string.isRequired,
|
||||
linkStyle: CustomPropTypes.Style,
|
||||
linkStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
|
||||
onChannelLinkPress: PropTypes.func,
|
||||
textStyle: CustomPropTypes.Style,
|
||||
textStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
|
||||
channelsByName: PropTypes.object.isRequired,
|
||||
actions: PropTypes.shape({
|
||||
handleSelectChannel: PropTypes.func.isRequired,
|
||||
|
||||
@@ -14,7 +14,6 @@ import * as RNPlaceholder from 'rn-placeholder';
|
||||
import {SafeAreaView} from 'react-native-safe-area-context';
|
||||
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import CustomPropTypes from '@constants/custom_prop_types';
|
||||
import {INDICATOR_BAR_HEIGHT} from '@constants/view';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
@@ -39,7 +38,7 @@ export default class ChannelLoader extends PureComponent {
|
||||
static propTypes = {
|
||||
backgroundColor: PropTypes.string,
|
||||
channelIsLoading: PropTypes.bool.isRequired,
|
||||
style: CustomPropTypes.Style,
|
||||
style: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
|
||||
theme: PropTypes.object.isRequired,
|
||||
height: PropTypes.number,
|
||||
retryLoad: PropTypes.func,
|
||||
|
||||
@@ -5,12 +5,10 @@ import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import {TouchableOpacity} from 'react-native';
|
||||
|
||||
import CustomPropTypes from 'app/constants/custom_prop_types';
|
||||
|
||||
export default class ConditionalTouchable extends React.PureComponent {
|
||||
static propTypes = {
|
||||
touchable: PropTypes.bool,
|
||||
children: CustomPropTypes.Children.isRequired,
|
||||
children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf([PropTypes.node])]),
|
||||
};
|
||||
|
||||
render() {
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import ConditionalTouchable from '@components/conditional_touchable';
|
||||
import CustomPropTypes from '@constants/custom_prop_types';
|
||||
|
||||
export default class CustomListRow extends React.PureComponent {
|
||||
static propTypes = {
|
||||
@@ -18,7 +17,7 @@ export default class CustomListRow extends React.PureComponent {
|
||||
enabled: PropTypes.bool,
|
||||
selectable: PropTypes.bool,
|
||||
selected: PropTypes.bool,
|
||||
children: CustomPropTypes.Children,
|
||||
children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf([PropTypes.node])]),
|
||||
testID: PropTypes.string,
|
||||
};
|
||||
|
||||
|
||||
@@ -16,12 +16,13 @@ import {Post} from '@mm-redux/types/posts';
|
||||
import {AppExpandLevels, AppBindingLocations, AppCallTypes, AppCallResponseTypes} from '@mm-redux/constants/apps';
|
||||
import {createCallContext, createCallRequest} from '@utils/apps';
|
||||
import {Channel} from '@mm-redux/types/channels';
|
||||
import {SendEphemeralPost} from 'types/actions/posts';
|
||||
|
||||
type Props = {
|
||||
actions: {
|
||||
doAppCall: (call: AppCallRequest, type: AppCallType, intl: any) => Promise<{data?: AppCallResponse, error?: AppCallResponse}>;
|
||||
getChannel: (channelId: string) => Promise<ActionResult>;
|
||||
sendEphemeralPost: (message: any, channelId?: string, parentId?: string) => Promise<ActionResult>;
|
||||
sendEphemeralPost: SendEphemeralPost;
|
||||
};
|
||||
post: Post;
|
||||
binding: AppBinding;
|
||||
@@ -32,9 +33,10 @@ export default class ButtonBinding extends PureComponent<Props> {
|
||||
static contextTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
handleActionPress = preventDoubleTap(async () => {
|
||||
const ephemeral = (message: string) => this.props.actions.sendEphemeralPost(message, this.props.post.channel_id, this.props.post.root_id);
|
||||
|
||||
private mounted = false;
|
||||
|
||||
handleActionPress = preventDoubleTap(async () => {
|
||||
const {
|
||||
binding,
|
||||
post,
|
||||
@@ -66,8 +68,11 @@ export default class ButtonBinding extends PureComponent<Props> {
|
||||
);
|
||||
this.setState({executing: true});
|
||||
const res = await this.props.actions.doAppCall(call, AppCallTypes.SUBMIT, this.context.intl);
|
||||
this.setState({executing: false});
|
||||
if (this.mounted) {
|
||||
this.setState({executing: false});
|
||||
}
|
||||
|
||||
const ephemeral = (message: string) => this.props.actions.sendEphemeralPost(message, this.props.post.channel_id, this.props.post.root_id, res.data?.app_metadata?.bot_user_id);
|
||||
if (res.error) {
|
||||
const errorResponse = res.error;
|
||||
const errorMessage = errorResponse.error || intl.formatMessage(
|
||||
@@ -104,6 +109,14 @@ export default class ButtonBinding extends PureComponent<Props> {
|
||||
}
|
||||
}, 4000);
|
||||
|
||||
componentDidMount() {
|
||||
this.mounted = true;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.mounted = false;
|
||||
}
|
||||
|
||||
render() {
|
||||
const {theme, binding} = this.props;
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {Text, View, StyleSheet, StyleProp, TextStyle} from 'react-native';
|
||||
import Emoji from 'app/components/emoji';
|
||||
import {getEmoticonName} from 'app/utils/emoji_utils';
|
||||
import {reEmoji, reEmoticon, reMain} from 'app/constants/emoji';
|
||||
import {Text, View, StyleSheet, StyleProp} from 'react-native';
|
||||
|
||||
export default function ButtonBindingText({message, style}: {message: string; style: StyleProp<TextStyle>}) {
|
||||
import Emoji from '@components/emoji';
|
||||
import {reEmoji, reEmoticon, reMain} from '@constants/emoji';
|
||||
import {getEmoticonName} from '@utils/emoji_utils';
|
||||
|
||||
export default function ButtonBindingText({message, style}: {message: string; style: StyleProp<any>}) {
|
||||
const components = [] as JSX.Element[];
|
||||
|
||||
let text = message;
|
||||
|
||||
@@ -16,6 +16,7 @@ import ButtonBinding from './button_binding';
|
||||
import {getChannel} from '@mm-redux/actions/channels';
|
||||
import {sendEphemeralPost} from '@actions/views/post';
|
||||
import {getCurrentTeamId} from '@mm-redux/selectors/entities/teams';
|
||||
import {SendEphemeralPost} from 'types/actions/posts';
|
||||
|
||||
type OwnProps = {
|
||||
postId: string;
|
||||
@@ -32,7 +33,7 @@ function mapStateToProps(state: GlobalState, ownProps: OwnProps) {
|
||||
type Actions = {
|
||||
doAppCall: (call: AppCallRequest, type: AppCallType, intl: any) => Promise<{data?: AppCallResponse, error?: AppCallResponse}>;
|
||||
getChannel: (channelId: string) => Promise<ActionResult>;
|
||||
sendEphemeralPost: (message: any, channelId?: string, parentId?: string) => Promise<ActionResult>;
|
||||
sendEphemeralPost: SendEphemeralPost;
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch: Dispatch<GenericAction>) {
|
||||
|
||||
@@ -2,20 +2,20 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import {LayoutChangeEvent, ScrollView, StyleProp, TextStyle, View, ViewStyle} from 'react-native';
|
||||
import {LayoutChangeEvent, ScrollView, StyleProp, View} from 'react-native';
|
||||
|
||||
import Markdown from 'app/components/markdown';
|
||||
import ShowMoreButton from 'app/components/show_more_button';
|
||||
import Markdown from '@components/markdown';
|
||||
import ShowMoreButton from '@components/show_more_button';
|
||||
import {Theme} from '@mm-redux/types/preferences';
|
||||
|
||||
const SHOW_MORE_HEIGHT = 60;
|
||||
|
||||
type Props = {
|
||||
baseTextStyle: StyleProp<TextStyle>,
|
||||
blockStyles?: StyleProp<ViewStyle>[],
|
||||
baseTextStyle: StyleProp<any>,
|
||||
blockStyles?: StyleProp<any>[],
|
||||
deviceHeight: number,
|
||||
onPermalinkPress?: () => void,
|
||||
textStyles?: StyleProp<TextStyle>[],
|
||||
textStyles?: StyleProp<any>[],
|
||||
theme?: Theme,
|
||||
value?: string,
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import {GlobalState} from '@mm-redux/types/store';
|
||||
import {doAppCall} from '@actions/apps';
|
||||
import {ActionFunc, ActionResult, GenericAction} from '@mm-redux/types/actions';
|
||||
import {AppCallRequest, AppCallResponse, AppCallType} from '@mm-redux/types/apps';
|
||||
import {SendEphemeralPost} from 'types/actions/posts';
|
||||
import {getPost} from '@mm-redux/selectors/entities/posts';
|
||||
|
||||
import MenuBinding from './menu_binding';
|
||||
@@ -29,7 +30,7 @@ function mapStateToProps(state: GlobalState, ownProps: OwnProps) {
|
||||
type Actions = {
|
||||
doAppCall: (call: AppCallRequest, type: AppCallType, intl: any) => Promise<{data?: AppCallResponse, error?: AppCallResponse}>;
|
||||
getChannel: (channelId: string) => Promise<ActionResult>;
|
||||
sendEphemeralPost: (message: any, channelId?: string, parentId?: string) => Promise<ActionResult>;
|
||||
sendEphemeralPost: SendEphemeralPost;
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch: Dispatch<GenericAction>) {
|
||||
|
||||
@@ -12,12 +12,13 @@ import {ActionResult} from '@mm-redux/types/actions';
|
||||
import {AppExpandLevels, AppBindingLocations, AppCallTypes, AppCallResponseTypes} from '@mm-redux/constants/apps';
|
||||
import {Channel} from '@mm-redux/types/channels';
|
||||
import {createCallContext, createCallRequest} from '@utils/apps';
|
||||
import {SendEphemeralPost} from 'types/actions/posts';
|
||||
|
||||
type Props = {
|
||||
actions: {
|
||||
doAppCall: (call: AppCallRequest, type: AppCallType, intl: any) => Promise<{data?: AppCallResponse, error?: AppCallResponse}>;
|
||||
getChannel: (channelId: string) => Promise<ActionResult>;
|
||||
sendEphemeralPost: (message: any, channelId?: string, parentId?: string) => Promise<ActionResult>;
|
||||
sendEphemeralPost: SendEphemeralPost;
|
||||
};
|
||||
binding?: AppBinding;
|
||||
post: Post;
|
||||
@@ -42,7 +43,6 @@ export default class MenuBinding extends PureComponent<Props, State> {
|
||||
if (!selected) {
|
||||
return;
|
||||
}
|
||||
const ephemeral = (message: string) => this.props.actions.sendEphemeralPost(message, this.props.post.channel_id, this.props.post.root_id);
|
||||
|
||||
this.setState({selected});
|
||||
const binding = this.props.binding?.bindings?.find((b) => b.location === selected.value);
|
||||
@@ -83,6 +83,8 @@ export default class MenuBinding extends PureComponent<Props, State> {
|
||||
);
|
||||
|
||||
const res = await actions.doAppCall(call, AppCallTypes.SUBMIT, this.context.intl);
|
||||
|
||||
const ephemeral = (message: string) => this.props.actions.sendEphemeralPost(message, this.props.post.channel_id, this.props.post.root_id, res.data?.app_metadata?.bot_user_id);
|
||||
if (res.error) {
|
||||
const errorResponse = res.error;
|
||||
const errorMessage = errorResponse.error || intl.formatMessage({
|
||||
|
||||
@@ -10,8 +10,6 @@ import {
|
||||
} from 'react-native';
|
||||
import FastImage from 'react-native-fast-image';
|
||||
|
||||
import CustomPropTypes from 'app/constants/custom_prop_types';
|
||||
|
||||
export default class Emoji extends React.PureComponent {
|
||||
static propTypes = {
|
||||
|
||||
@@ -36,9 +34,9 @@ export default class Emoji extends React.PureComponent {
|
||||
displayTextOnly: PropTypes.bool,
|
||||
literal: PropTypes.string,
|
||||
size: PropTypes.number,
|
||||
textStyle: CustomPropTypes.Style,
|
||||
textStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
|
||||
unicode: PropTypes.string,
|
||||
customEmojiStyle: CustomPropTypes.Style,
|
||||
customEmojiStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
|
||||
testID: PropTypes.string,
|
||||
};
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
|
||||
onScroll={[Function]}
|
||||
onScrollToIndexFailed={[Function]}
|
||||
pageSize={50}
|
||||
removeClippedSubviews={false}
|
||||
removeClippedSubviews={true}
|
||||
renderItem={[Function]}
|
||||
renderSectionHeader={[Function]}
|
||||
scrollEventThrottle={50}
|
||||
|
||||
@@ -264,15 +264,16 @@ export default class EmojiPicker extends PureComponent {
|
||||
|
||||
listComponent = (
|
||||
<FlatList
|
||||
contentContainerStyle={contentContainerStyle}
|
||||
data={filteredEmojis}
|
||||
initialListSize={10}
|
||||
keyboardShouldPersistTaps='always'
|
||||
keyExtractor={this.flatListKeyExtractor}
|
||||
initialListSize={10}
|
||||
ListEmptyComponent={this.renderEmptyList}
|
||||
nativeID={SCROLLVIEW_NATIVE_ID}
|
||||
pageSize={10}
|
||||
renderItem={this.flatListRenderItem}
|
||||
ListEmptyComponent={this.renderEmptyList}
|
||||
contentContainerStyle={contentContainerStyle}
|
||||
removeClippedSubviews={true}
|
||||
style={styles.flatList}
|
||||
/>
|
||||
);
|
||||
@@ -292,7 +293,7 @@ export default class EmojiPicker extends PureComponent {
|
||||
onScroll={this.onScroll}
|
||||
onScrollToIndexFailed={this.handleScrollToSectionFailed}
|
||||
pageSize={50}
|
||||
removeClippedSubviews={false}
|
||||
removeClippedSubviews={true}
|
||||
renderItem={this.renderItem}
|
||||
renderSectionHeader={this.renderSectionHeader}
|
||||
sections={emojis}
|
||||
|
||||
@@ -5,7 +5,6 @@ import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {Text} from 'react-native';
|
||||
|
||||
import CustomPropTypes from 'app/constants/custom_prop_types';
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import {GlobalStyles} from 'app/styles';
|
||||
import {makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
@@ -14,7 +13,7 @@ export default class ErrorText extends PureComponent {
|
||||
static propTypes = {
|
||||
testID: PropTypes.string,
|
||||
error: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||
textStyle: CustomPropTypes.Style,
|
||||
textStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
|
||||
theme: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ import {injectIntl, intlShape} from 'react-intl';
|
||||
|
||||
import {concatStyles, changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
import {getMarkdownTextStyles} from 'app/utils/markdown';
|
||||
import CustomPropTypes from 'app/constants/custom_prop_types';
|
||||
|
||||
import AtMention from 'app/components/at_mention';
|
||||
import MarkdownLink from 'app/components/markdown/markdown_link';
|
||||
@@ -32,11 +31,11 @@ const TARGET_BLANK_URL_PREFIX = '!';
|
||||
*/
|
||||
class FormattedMarkdownText extends React.PureComponent {
|
||||
static propTypes = {
|
||||
baseTextStyle: CustomPropTypes.Style,
|
||||
baseTextStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
|
||||
defaultMessage: PropTypes.string.isRequired,
|
||||
id: PropTypes.string.isRequired,
|
||||
onPostPress: PropTypes.func,
|
||||
style: CustomPropTypes.Style,
|
||||
style: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
|
||||
textStyles: PropTypes.object,
|
||||
theme: PropTypes.object.isRequired,
|
||||
values: PropTypes.object,
|
||||
|
||||
@@ -6,15 +6,13 @@ import PropTypes from 'prop-types';
|
||||
import {Text} from 'react-native';
|
||||
import moment from 'moment-timezone';
|
||||
|
||||
import CustomPropTypes from 'app/constants/custom_prop_types';
|
||||
|
||||
export default class FormattedTime extends React.PureComponent {
|
||||
static propTypes = {
|
||||
value: PropTypes.any.isRequired,
|
||||
timeZone: PropTypes.string,
|
||||
children: PropTypes.func,
|
||||
hour12: PropTypes.bool,
|
||||
style: CustomPropTypes.Style,
|
||||
style: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
|
||||
testID: PropTypes.string,
|
||||
};
|
||||
|
||||
|
||||
@@ -10,12 +10,10 @@ import {
|
||||
View,
|
||||
} from 'react-native';
|
||||
|
||||
import * as CustomPropTypes from 'app/constants/custom_prop_types';
|
||||
|
||||
export default class KeyboardLayout extends PureComponent {
|
||||
static propTypes = {
|
||||
children: PropTypes.node,
|
||||
style: CustomPropTypes.Style,
|
||||
style: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
|
||||
testID: PropTypes.string,
|
||||
};
|
||||
|
||||
|
||||
@@ -5,13 +5,12 @@ import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import {Text} from 'react-native';
|
||||
|
||||
import CustomPropTypes from 'app/constants/custom_prop_types';
|
||||
import {popToRoot, showSearchModal, dismissAllModals} from 'app/actions/navigation';
|
||||
import {popToRoot, showSearchModal, dismissAllModals} from '@actions/navigation';
|
||||
|
||||
export default class Hashtag extends React.PureComponent {
|
||||
static propTypes = {
|
||||
hashtag: PropTypes.string.isRequired,
|
||||
linkStyle: CustomPropTypes.Style.isRequired,
|
||||
linkStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
|
||||
onHashtagPress: PropTypes.func,
|
||||
};
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ import ChannelLink from 'app/components/channel_link';
|
||||
import Emoji from 'app/components/emoji';
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import Hashtag from 'app/components/markdown/hashtag';
|
||||
import CustomPropTypes from 'app/constants/custom_prop_types';
|
||||
import {blendColors, concatStyles, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
import {getScheme} from 'app/utils/url';
|
||||
|
||||
@@ -39,23 +38,23 @@ import {
|
||||
|
||||
export default class Markdown extends PureComponent {
|
||||
static propTypes = {
|
||||
autolinkedUrlSchemes: PropTypes.array.isRequired,
|
||||
baseTextStyle: CustomPropTypes.Style,
|
||||
autolinkedUrlSchemes: PropTypes.array,
|
||||
baseTextStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
|
||||
blockStyles: PropTypes.object,
|
||||
channelMentions: PropTypes.object,
|
||||
imagesMetadata: PropTypes.object,
|
||||
isEdited: PropTypes.bool,
|
||||
isReplyPost: PropTypes.bool,
|
||||
isSearchResult: PropTypes.bool,
|
||||
mentionKeys: PropTypes.array.isRequired,
|
||||
minimumHashtagLength: PropTypes.number.isRequired,
|
||||
mentionKeys: PropTypes.array,
|
||||
minimumHashtagLength: PropTypes.number,
|
||||
onChannelLinkPress: PropTypes.func,
|
||||
onHashtagPress: PropTypes.func,
|
||||
onPermalinkPress: PropTypes.func,
|
||||
onPostPress: PropTypes.func,
|
||||
postId: PropTypes.string,
|
||||
textStyles: PropTypes.object,
|
||||
theme: PropTypes.object.isRequired,
|
||||
theme: PropTypes.object,
|
||||
value: PropTypes.string.isRequired,
|
||||
disableHashtags: PropTypes.bool,
|
||||
disableAtMentions: PropTypes.bool,
|
||||
@@ -155,7 +154,8 @@ export default class Markdown extends PureComponent {
|
||||
};
|
||||
|
||||
computeTextStyle = (baseStyle, context) => {
|
||||
return concatStyles(baseStyle, context.map((type) => this.props.textStyles[type]));
|
||||
const contextStyles = context.map((type) => this.props.textStyles[type]).filter((f) => f !== undefined);
|
||||
return contextStyles.length ? concatStyles(baseStyle, contextStyles) : baseStyle;
|
||||
};
|
||||
|
||||
renderText = ({context, literal}) => {
|
||||
|
||||
@@ -9,13 +9,12 @@ import {
|
||||
} from 'react-native';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import CustomPropTypes from '@constants/custom_prop_types';
|
||||
|
||||
export default class MarkdownBlockQuote extends PureComponent {
|
||||
static propTypes = {
|
||||
continue: PropTypes.bool,
|
||||
iconStyle: CustomPropTypes.Style,
|
||||
children: CustomPropTypes.Children.isRequired,
|
||||
iconStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
|
||||
children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf([PropTypes.node])]),
|
||||
};
|
||||
|
||||
render() {
|
||||
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
} 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';
|
||||
import TouchableWithFeedback from 'app/components/touchable_with_feedback';
|
||||
import BottomSheet from 'app/utils/bottom_sheet';
|
||||
@@ -29,7 +28,7 @@ export default class MarkdownCodeBlock extends React.PureComponent {
|
||||
theme: PropTypes.object.isRequired,
|
||||
language: PropTypes.string,
|
||||
content: PropTypes.string.isRequired,
|
||||
textStyle: CustomPropTypes.Style,
|
||||
textStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
|
||||
@@ -9,12 +9,11 @@ import PropTypes from 'prop-types';
|
||||
|
||||
import Emoji from 'app/components/emoji';
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import CustomPropTypes from 'app/constants/custom_prop_types';
|
||||
import {blendColors, concatStyles, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
export default class MarkdownEmoji extends PureComponent {
|
||||
static propTypes = {
|
||||
baseTextStyle: CustomPropTypes.Style,
|
||||
baseTextStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
|
||||
isEdited: PropTypes.bool,
|
||||
shouldRenderJumboEmoji: PropTypes.bool.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
|
||||
@@ -19,7 +19,6 @@ import ImageViewPort from '@components/image_viewport';
|
||||
import ProgressiveImage from '@components/progressive_image';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
import {CustomPropTypes} from '@constants';
|
||||
import EphemeralStore from '@store/ephemeral_store';
|
||||
import BottomSheet from '@utils/bottom_sheet';
|
||||
import {generateId} from '@utils/file';
|
||||
@@ -35,7 +34,7 @@ export default class MarkdownImage extends ImageViewPort {
|
||||
static propTypes = {
|
||||
children: PropTypes.node,
|
||||
disable: PropTypes.bool,
|
||||
errorTextStyle: CustomPropTypes.Style,
|
||||
errorTextStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
|
||||
imagesMetadata: PropTypes.object,
|
||||
isReplyPost: PropTypes.bool,
|
||||
linkDestination: PropTypes.string,
|
||||
|
||||
@@ -10,7 +10,6 @@ import urlParse from 'url-parse';
|
||||
|
||||
import Config from '@assets/config';
|
||||
import {DeepLinkTypes} from '@constants';
|
||||
import CustomPropTypes from '@constants/custom_prop_types';
|
||||
import {getCurrentServerUrl} from '@init/credentials';
|
||||
import BottomSheet from '@utils/bottom_sheet';
|
||||
import {errorBadChannel} from '@utils/draft';
|
||||
@@ -24,7 +23,7 @@ export default class MarkdownLink extends PureComponent {
|
||||
actions: PropTypes.shape({
|
||||
handleSelectChannelByName: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
children: CustomPropTypes.Children.isRequired,
|
||||
children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf([PropTypes.node])]),
|
||||
href: PropTypes.string.isRequired,
|
||||
onPermalinkPress: PropTypes.func,
|
||||
serverURL: PropTypes.string,
|
||||
|
||||
@@ -9,16 +9,14 @@ import {
|
||||
View,
|
||||
} from 'react-native';
|
||||
|
||||
import CustomPropTypes from 'app/constants/custom_prop_types';
|
||||
|
||||
export default class MarkdownListItem extends PureComponent {
|
||||
static propTypes = {
|
||||
children: CustomPropTypes.Children.isRequired,
|
||||
children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf([PropTypes.node])]),
|
||||
ordered: PropTypes.bool.isRequired,
|
||||
continue: PropTypes.bool,
|
||||
index: PropTypes.number.isRequired,
|
||||
bulletWidth: PropTypes.number,
|
||||
bulletStyle: CustomPropTypes.Style,
|
||||
bulletStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
|
||||
level: PropTypes.number,
|
||||
};
|
||||
|
||||
|
||||
@@ -5,13 +5,12 @@ import React, {PureComponent} from 'react';
|
||||
import {Text, View} from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Markdown from 'app/components/markdown';
|
||||
import CustomPropTypes from 'app/constants/custom_prop_types';
|
||||
import {makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
import Markdown from '@components/markdown';
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
export default class AttachmentFields extends PureComponent {
|
||||
static propTypes = {
|
||||
baseTextStyle: CustomPropTypes.Style.isRequired,
|
||||
baseTextStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
|
||||
blockStyles: PropTypes.object.isRequired,
|
||||
fields: PropTypes.array,
|
||||
metadata: PropTypes.object,
|
||||
|
||||
@@ -5,12 +5,11 @@ import React, {PureComponent} from 'react';
|
||||
import {StyleSheet, View} from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Markdown from 'app/components/markdown';
|
||||
import CustomPropTypes from 'app/constants/custom_prop_types';
|
||||
import Markdown from '@components/markdown';
|
||||
|
||||
export default class AttachmentPreText extends PureComponent {
|
||||
static propTypes = {
|
||||
baseTextStyle: CustomPropTypes.Style.isRequired,
|
||||
baseTextStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
|
||||
blockStyles: PropTypes.object.isRequired,
|
||||
metadata: PropTypes.object,
|
||||
onPermalinkPress: PropTypes.func,
|
||||
|
||||
@@ -5,15 +5,14 @@ import React, {PureComponent} from 'react';
|
||||
import {ScrollView, StyleSheet, View} from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Markdown from 'app/components/markdown';
|
||||
import ShowMoreButton from 'app/components/show_more_button';
|
||||
import CustomPropTypes from 'app/constants/custom_prop_types';
|
||||
import Markdown from '@components/markdown';
|
||||
import ShowMoreButton from '@components/show_more_button';
|
||||
|
||||
const SHOW_MORE_HEIGHT = 60;
|
||||
|
||||
export default class AttachmentText extends PureComponent {
|
||||
static propTypes = {
|
||||
baseTextStyle: CustomPropTypes.Style.isRequired,
|
||||
baseTextStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
|
||||
blockStyles: PropTypes.object.isRequired,
|
||||
deviceHeight: PropTypes.number.isRequired,
|
||||
hasThumbnail: PropTypes.bool,
|
||||
|
||||
@@ -5,14 +5,12 @@ import React, {PureComponent} from 'react';
|
||||
import {View} from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import CustomPropTypes from 'app/constants/custom_prop_types';
|
||||
|
||||
import MessageAttachment from './message_attachment';
|
||||
|
||||
export default class MessageAttachments extends PureComponent {
|
||||
static propTypes = {
|
||||
attachments: PropTypes.array.isRequired,
|
||||
baseTextStyle: CustomPropTypes.Style,
|
||||
baseTextStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
|
||||
blockStyles: PropTypes.object,
|
||||
deviceHeight: PropTypes.number.isRequired,
|
||||
deviceWidth: PropTypes.number.isRequired,
|
||||
|
||||
@@ -5,9 +5,8 @@ import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {View} from 'react-native';
|
||||
|
||||
import CustomPropTypes from 'app/constants/custom_prop_types';
|
||||
import {getStatusColors} from 'app/utils/message_attachment_colors';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
import {getStatusColors} from '@utils/message_attachment_colors';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
import AttachmentActions from './attachment_actions';
|
||||
import AttachmentAuthor from './attachment_author';
|
||||
@@ -22,7 +21,7 @@ import AttachmentFooter from './attachment_footer';
|
||||
export default class MessageAttachment extends PureComponent {
|
||||
static propTypes = {
|
||||
attachment: PropTypes.object.isRequired,
|
||||
baseTextStyle: CustomPropTypes.Style,
|
||||
baseTextStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
|
||||
blockStyles: PropTypes.object,
|
||||
deviceHeight: PropTypes.number.isRequired,
|
||||
deviceWidth: PropTypes.number.isRequired,
|
||||
|
||||
@@ -24,7 +24,6 @@ import {INDICATOR_BAR_HEIGHT} from '@constants/view';
|
||||
import networkConnectionListener, {checkConnection} from '@utils/network';
|
||||
import {t} from '@utils/i18n';
|
||||
|
||||
import mattermostBucket from 'app/mattermost_bucket';
|
||||
import PushNotifications from '@init/push_notifications';
|
||||
|
||||
const MAX_WEBSOCKET_RETRIES = 3;
|
||||
@@ -288,13 +287,8 @@ export default class NetworkIndicator extends PureComponent {
|
||||
initializeWebSocket = async () => {
|
||||
const {actions} = this.props;
|
||||
const {closeWebSocket, initWebSocket} = actions;
|
||||
const platform = Platform.OS;
|
||||
let certificate = null;
|
||||
if (platform === 'ios') {
|
||||
certificate = await mattermostBucket.getPreference('cert');
|
||||
}
|
||||
|
||||
initWebSocket({certificate, forceConnection: true}).catch(() => {
|
||||
initWebSocket({forceConnection: true}).catch(() => {
|
||||
// we should dispatch a failure and show the app as disconnected
|
||||
closeWebSocket(true);
|
||||
});
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`PasteableTextInput should render pasteable text input 1`] = `
|
||||
<Component
|
||||
<TextInput
|
||||
allowFontScaling={true}
|
||||
onPaste={[Function]}
|
||||
rejectResponderTermination={true}
|
||||
underlineColorAndroid="transparent"
|
||||
>
|
||||
My Text
|
||||
</Component>
|
||||
</TextInput>
|
||||
`;
|
||||
|
||||
@@ -7,14 +7,11 @@ import {intlShape} from 'react-intl';
|
||||
|
||||
import {Text} from 'react-native';
|
||||
|
||||
import AtMention from '@components/at_mention';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import {General} from '@mm-redux/constants';
|
||||
|
||||
import {concatStyles} from 'app/utils/theme';
|
||||
import {t} from 'app/utils/i18n';
|
||||
|
||||
import AtMention from 'app/components/at_mention';
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import CustomPropTypes from 'app/constants/custom_prop_types';
|
||||
import {t} from '@utils/i18n';
|
||||
import {concatStyles} from '@utils/theme';
|
||||
|
||||
export default class PostAddChannelMember extends React.PureComponent {
|
||||
static propTypes = {
|
||||
@@ -23,7 +20,7 @@ export default class PostAddChannelMember extends React.PureComponent {
|
||||
removePost: PropTypes.func.isRequired,
|
||||
sendAddToChannelEphemeralPost: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
baseTextStyle: CustomPropTypes.Style,
|
||||
baseTextStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
|
||||
currentUser: PropTypes.object.isRequired,
|
||||
channelType: PropTypes.string,
|
||||
post: PropTypes.object.isRequired,
|
||||
|
||||
@@ -298,3 +298,77 @@ exports[`PostAttachmentOpenGraph should match state and snapshot, on renderImage
|
||||
</TouchableWithFeedbackIOS>
|
||||
</View>
|
||||
`;
|
||||
|
||||
exports[`PostAttachmentOpenGraph should match state and snapshot, on renderImage 3`] = `
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"borderColor": "rgba(61,60,64,0.2)",
|
||||
"borderRadius": 3,
|
||||
"borderWidth": 1,
|
||||
"flex": 1,
|
||||
"marginTop": 10,
|
||||
"padding": 10,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={1}
|
||||
style={
|
||||
Object {
|
||||
"color": "rgba(61,60,64,0.5)",
|
||||
"fontSize": 12,
|
||||
"marginBottom": 10,
|
||||
}
|
||||
}
|
||||
>
|
||||
Mattermost
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
}
|
||||
}
|
||||
>
|
||||
<TouchableWithFeedbackIOS
|
||||
onPress={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
type="opacity"
|
||||
>
|
||||
<Text
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={3}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"color": "#2389d7",
|
||||
"fontSize": 14,
|
||||
"marginBottom": 10,
|
||||
},
|
||||
Object {
|
||||
"marginRight": 0,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
Title
|
||||
</Text>
|
||||
</TouchableWithFeedbackIOS>
|
||||
</View>
|
||||
</View>
|
||||
`;
|
||||
|
||||
@@ -15,7 +15,7 @@ import {generateId} from '@utils/file';
|
||||
import {openGalleryAtIndex, calculateDimensions} from '@utils/images';
|
||||
import {getNearestPoint} from '@utils/opengraph';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {tryOpenURL} from '@utils/url';
|
||||
import {tryOpenURL, isValidUrl} from '@utils/url';
|
||||
|
||||
const MAX_IMAGE_HEIGHT = 150;
|
||||
const VIEWPORT_IMAGE_OFFSET = 93;
|
||||
@@ -93,7 +93,7 @@ export default class PostAttachmentOpenGraph extends PureComponent {
|
||||
}
|
||||
|
||||
return {
|
||||
hasImage: true,
|
||||
hasImage: Boolean(isValidUrl(imageUrl) && (ogImage.width && ogImage.height)),
|
||||
...dimensions,
|
||||
openGraphImageUrl: imageUrl,
|
||||
};
|
||||
|
||||
@@ -104,6 +104,22 @@ describe('PostAttachmentOpenGraph', () => {
|
||||
expect(wrapper.find(TouchableWithFeedback).exists()).toEqual(true);
|
||||
});
|
||||
|
||||
test('should match state and snapshot, on renderImage', () => {
|
||||
const images = [{height: 440, width: 1200, url: '%REACT_APP_WEBSITE_BANNER%'}];
|
||||
const openGraphDataWithImage = {...openGraphData, images};
|
||||
const wrapper = shallow(
|
||||
<PostAttachmentOpenGraph
|
||||
{...baseProps}
|
||||
openGraphData={openGraphDataWithImage}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
expect(wrapper.state('hasImage')).toEqual(false);
|
||||
expect(wrapper.find(FastImage).exists()).toEqual(false);
|
||||
expect(wrapper.find(TouchableWithFeedback).exists()).toEqual(true);
|
||||
});
|
||||
|
||||
test('should match state and snapshot, on renderDescription', () => {
|
||||
const wrapper = shallow(
|
||||
<PostAttachmentOpenGraph
|
||||
|
||||
@@ -14,14 +14,7 @@ exports[`renderSystemMessage uses renderer for Channel Display Name update 1`] =
|
||||
>
|
||||
<Text>
|
||||
<Text
|
||||
style={
|
||||
Array [
|
||||
Object {},
|
||||
Array [
|
||||
undefined,
|
||||
],
|
||||
]
|
||||
}
|
||||
style={Object {}}
|
||||
testID="markdown_text"
|
||||
>
|
||||
{username} updated the channel display name from: {oldDisplayName} to: {newDisplayName}
|
||||
@@ -44,14 +37,7 @@ exports[`renderSystemMessage uses renderer for Channel Header update 1`] = `
|
||||
>
|
||||
<Text>
|
||||
<Text
|
||||
style={
|
||||
Array [
|
||||
Object {},
|
||||
Array [
|
||||
undefined,
|
||||
],
|
||||
]
|
||||
}
|
||||
style={Object {}}
|
||||
testID="markdown_text"
|
||||
>
|
||||
{username} updated the channel header from: {oldHeader} to: {newHeader}
|
||||
@@ -82,14 +68,7 @@ exports[`renderSystemMessage uses renderer for OLD archived channel without a us
|
||||
>
|
||||
<Text>
|
||||
<Text
|
||||
style={
|
||||
Array [
|
||||
Object {},
|
||||
Array [
|
||||
undefined,
|
||||
],
|
||||
]
|
||||
}
|
||||
style={Object {}}
|
||||
testID="markdown_text"
|
||||
>
|
||||
{username} archived the channel
|
||||
@@ -123,14 +102,7 @@ exports[`renderSystemMessage uses renderer for archived channel 2`] = `
|
||||
>
|
||||
<Text>
|
||||
<Text
|
||||
style={
|
||||
Array [
|
||||
Object {},
|
||||
Array [
|
||||
undefined,
|
||||
],
|
||||
]
|
||||
}
|
||||
style={Object {}}
|
||||
testID="markdown_text"
|
||||
>
|
||||
{username} archived the channel
|
||||
@@ -153,14 +125,7 @@ exports[`renderSystemMessage uses renderer for unarchived channel 1`] = `
|
||||
>
|
||||
<Text>
|
||||
<Text
|
||||
style={
|
||||
Array [
|
||||
Object {},
|
||||
Array [
|
||||
undefined,
|
||||
],
|
||||
]
|
||||
}
|
||||
style={Object {}}
|
||||
testID="markdown_text"
|
||||
>
|
||||
{username} unarchived the channel
|
||||
|
||||
@@ -18,7 +18,6 @@ import ImageViewPort from '@components/image_viewport';
|
||||
import PostAttachmentImage from '@components/post_attachment_image';
|
||||
import ProgressiveImage from '@components/progressive_image';
|
||||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
import CustomPropTypes from '@constants/custom_prop_types';
|
||||
import EventEmitter from '@mm-redux/utils/event_emitter';
|
||||
import {generateId} from '@utils/file';
|
||||
import {calculateDimensions, getViewPortWidth, openGalleryAtIndex} from '@utils/images';
|
||||
@@ -36,7 +35,7 @@ export default class PostBodyAdditionalContent extends ImageViewPort {
|
||||
actions: PropTypes.shape({
|
||||
getRedirectLocation: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
baseTextStyle: CustomPropTypes.Style,
|
||||
baseTextStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
|
||||
blockStyles: PropTypes.object,
|
||||
deviceHeight: PropTypes.number.isRequired,
|
||||
deviceWidth: PropTypes.number.isRequired,
|
||||
@@ -396,7 +395,7 @@ export default class PostBodyAdditionalContent extends ImageViewPort {
|
||||
if (app_bindings && app_bindings.length) {
|
||||
return (
|
||||
<EmbeddedBindings
|
||||
embed={app_bindings}
|
||||
embeds={app_bindings}
|
||||
baseTextStyle={baseTextStyle}
|
||||
blockStyles={blockStyles}
|
||||
deviceHeight={deviceHeight}
|
||||
@@ -532,13 +531,13 @@ export default class PostBodyAdditionalContent extends ImageViewPort {
|
||||
|
||||
render() {
|
||||
let {link} = this.props;
|
||||
const {openGraphData, postProps, expandedLink} = this.props;
|
||||
const {openGraphData, postProps, expandedLink, appsEnabled} = this.props;
|
||||
const {linkLoadError} = this.state;
|
||||
if (expandedLink) {
|
||||
link = expandedLink;
|
||||
}
|
||||
|
||||
const {attachments, app_bindings, appsEnabled} = postProps;
|
||||
const {attachments, app_bindings} = postProps;
|
||||
|
||||
if (!link && !attachments && !(appsEnabled && app_bindings)) {
|
||||
return null;
|
||||
|
||||
@@ -434,8 +434,7 @@ exports[`PostHeader should match snapshot when post isBot and shouldRenderReplyB
|
||||
}
|
||||
}
|
||||
>
|
||||
<TouchableWithFeedbackIOS
|
||||
onPress={[Function]}
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
@@ -448,7 +447,6 @@ exports[`PostHeader should match snapshot when post isBot and shouldRenderReplyB
|
||||
},
|
||||
]
|
||||
}
|
||||
type="opacity"
|
||||
>
|
||||
<Text
|
||||
ellipsizeMode="tail"
|
||||
@@ -466,7 +464,7 @@ exports[`PostHeader should match snapshot when post isBot and shouldRenderReplyB
|
||||
>
|
||||
John Smith
|
||||
</Text>
|
||||
</TouchableWithFeedbackIOS>
|
||||
</View>
|
||||
<BotTag
|
||||
style={
|
||||
Object {
|
||||
@@ -589,8 +587,7 @@ exports[`PostHeader should match snapshot when post isBot and shouldRenderReplyB
|
||||
}
|
||||
}
|
||||
>
|
||||
<TouchableWithFeedbackIOS
|
||||
onPress={[Function]}
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
@@ -603,7 +600,6 @@ exports[`PostHeader should match snapshot when post isBot and shouldRenderReplyB
|
||||
},
|
||||
]
|
||||
}
|
||||
type="opacity"
|
||||
>
|
||||
<Text
|
||||
ellipsizeMode="tail"
|
||||
@@ -621,7 +617,7 @@ exports[`PostHeader should match snapshot when post isBot and shouldRenderReplyB
|
||||
>
|
||||
John Smith
|
||||
</Text>
|
||||
</TouchableWithFeedbackIOS>
|
||||
</View>
|
||||
<BotTag
|
||||
style={
|
||||
Object {
|
||||
|
||||
@@ -142,6 +142,7 @@ export default class PostHeader extends PureComponent {
|
||||
displayName,
|
||||
enablePostUsernameOverride,
|
||||
fromWebHook,
|
||||
isBot,
|
||||
isSystemMessage,
|
||||
fromAutoResponder,
|
||||
overrideUsername,
|
||||
@@ -153,7 +154,7 @@ export default class PostHeader extends PureComponent {
|
||||
const displayNameWidth = this.calcNameWidth();
|
||||
const displayNameStyle = [style.displayNameContainer, displayNameWidth];
|
||||
|
||||
if (fromAutoResponder || fromWebHook) {
|
||||
if (fromAutoResponder || fromWebHook || isBot) {
|
||||
let name = displayName;
|
||||
if (overrideUsername && enablePostUsernameOverride) {
|
||||
name = overrideUsername;
|
||||
|
||||
@@ -57,7 +57,7 @@ exports[`PostList setting channel deep link 1`] = `
|
||||
tintColor="#3d3c40"
|
||||
/>
|
||||
}
|
||||
removeClippedSubviews={false}
|
||||
removeClippedSubviews={true}
|
||||
renderItem={[Function]}
|
||||
scrollEventThrottle={60}
|
||||
style={
|
||||
@@ -134,7 +134,7 @@ exports[`PostList setting permalink deep link 1`] = `
|
||||
tintColor="#3d3c40"
|
||||
/>
|
||||
}
|
||||
removeClippedSubviews={false}
|
||||
removeClippedSubviews={true}
|
||||
renderItem={[Function]}
|
||||
scrollEventThrottle={60}
|
||||
style={
|
||||
@@ -211,7 +211,7 @@ exports[`PostList should match snapshot 1`] = `
|
||||
tintColor="#3d3c40"
|
||||
/>
|
||||
}
|
||||
removeClippedSubviews={false}
|
||||
removeClippedSubviews={true}
|
||||
renderItem={[Function]}
|
||||
scrollEventThrottle={60}
|
||||
style={
|
||||
|
||||
@@ -516,7 +516,7 @@ export default class PostList extends PureComponent {
|
||||
onScrollToIndexFailed={this.handleScrollToIndexFailed}
|
||||
ref={this.flatListRef}
|
||||
refreshControl={refreshControl}
|
||||
removeClippedSubviews={false}
|
||||
removeClippedSubviews={true}
|
||||
renderItem={this.renderItem}
|
||||
scrollEventThrottle={60}
|
||||
style={styles.flex}
|
||||
|
||||
@@ -19,18 +19,19 @@ const STATUS_BUFFER = Platform.select({
|
||||
|
||||
export default class ProfilePicture extends PureComponent {
|
||||
static propTypes = {
|
||||
isCurrentUser: PropTypes.bool.isRequired,
|
||||
isCurrentUser: PropTypes.bool,
|
||||
size: PropTypes.number,
|
||||
statusSize: PropTypes.number,
|
||||
iconSize: PropTypes.number,
|
||||
user: PropTypes.object,
|
||||
userId: PropTypes.string,
|
||||
showStatus: PropTypes.bool,
|
||||
status: PropTypes.string,
|
||||
edit: PropTypes.bool,
|
||||
imageUri: PropTypes.string,
|
||||
profileImageUri: PropTypes.string,
|
||||
profileImageRemove: PropTypes.bool,
|
||||
theme: PropTypes.object.isRequired,
|
||||
theme: PropTypes.object,
|
||||
testID: PropTypes.string,
|
||||
actions: PropTypes.shape({
|
||||
getStatusForId: PropTypes.func.isRequired,
|
||||
@@ -181,7 +182,7 @@ export default class ProfilePicture extends PureComponent {
|
||||
return (
|
||||
<View
|
||||
style={[style.container, containerStyle]}
|
||||
testID={`${testID}.${user.id}`}
|
||||
testID={`${testID}.${user?.id}`}
|
||||
>
|
||||
{image}
|
||||
{(showStatus || edit) && (user && !user.is_bot) &&
|
||||
|
||||
@@ -6,7 +6,6 @@ import PropTypes from 'prop-types';
|
||||
import {Animated, ImageBackground, Image, Platform, View, StyleSheet} from 'react-native';
|
||||
|
||||
import thumb from '@assets/images/thumb.png';
|
||||
import CustomPropTypes from '@constants/custom_prop_types';
|
||||
import RetriableFastImage from '@components/retriable_fast_image';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
@@ -17,15 +16,15 @@ export default class ProgressiveImage extends PureComponent {
|
||||
static propTypes = {
|
||||
id: PropTypes.string,
|
||||
isBackgroundImage: PropTypes.bool,
|
||||
children: CustomPropTypes.Children,
|
||||
children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf([PropTypes.node])]),
|
||||
defaultSource: PropTypes.oneOfType([PropTypes.string, PropTypes.object, PropTypes.number]), // this should be provided by the component
|
||||
imageUri: PropTypes.string,
|
||||
imageStyle: CustomPropTypes.Style,
|
||||
imageStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
|
||||
inViewPort: PropTypes.bool,
|
||||
onError: PropTypes.func,
|
||||
resizeMethod: PropTypes.string,
|
||||
resizeMode: PropTypes.string,
|
||||
style: CustomPropTypes.Style,
|
||||
style: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
|
||||
theme: PropTypes.object.isRequired,
|
||||
thumbnailUri: PropTypes.string,
|
||||
tintDefaultSource: PropTypes.bool,
|
||||
|
||||
@@ -19,7 +19,6 @@ import {SearchBar} from 'react-native-elements';
|
||||
import {memoizeResult} from '@mm-redux/utils/helpers';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import CustomPropTypes from '@constants/custom_prop_types';
|
||||
|
||||
const LEFT_COMPONENT_INITIAL_POSITION = Platform.OS === 'ios' ? 7 : 0;
|
||||
|
||||
@@ -38,9 +37,9 @@ export default class Search extends PureComponent {
|
||||
tintColorSearch: PropTypes.string,
|
||||
tintColorDelete: PropTypes.string,
|
||||
selectionColor: PropTypes.string,
|
||||
inputStyle: CustomPropTypes.Style,
|
||||
containerStyle: CustomPropTypes.Style,
|
||||
cancelButtonStyle: CustomPropTypes.Style,
|
||||
inputStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
|
||||
containerStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
|
||||
cancelButtonStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
|
||||
autoFocus: PropTypes.bool,
|
||||
placeholder: PropTypes.string,
|
||||
cancelTitle: PropTypes.oneOfType([
|
||||
|
||||
@@ -383,6 +383,7 @@ class FilteredList extends Component {
|
||||
<View style={styles.container}>
|
||||
<SectionList
|
||||
sections={dataSource}
|
||||
removeClippedSubviews={true}
|
||||
renderItem={this.renderItem}
|
||||
renderSectionHeader={this.renderSectionHeader}
|
||||
keyExtractor={this.keyExtractor}
|
||||
|
||||
@@ -21,6 +21,7 @@ exports[`ChannelsList List should match snapshot 1`] = `
|
||||
onEndReachedThreshold={2}
|
||||
onScrollBeginDrag={[Function]}
|
||||
onViewableItemsChanged={[Function]}
|
||||
removeClippedSubviews={true}
|
||||
renderItem={[Function]}
|
||||
renderSectionHeader={[Function]}
|
||||
scrollEventThrottle={50}
|
||||
|
||||
@@ -411,6 +411,7 @@ export default class List extends PureComponent {
|
||||
ref={this.setListRef}
|
||||
sections={sections}
|
||||
contentContainerStyle={{paddingBottom}}
|
||||
removeClippedSubviews={true}
|
||||
renderItem={this.renderItem}
|
||||
renderSectionHeader={this.renderSectionHeader}
|
||||
keyboardShouldPersistTaps={'always'}
|
||||
|
||||
@@ -179,6 +179,7 @@ export default class TeamsList extends PureComponent {
|
||||
extraData={this.state.serverUrl}
|
||||
contentContainerStyle={this.listContentPadding()}
|
||||
data={teamIds}
|
||||
removeClippedSubviews={true}
|
||||
renderItem={this.renderItem}
|
||||
keyExtractor={this.keyExtractor}
|
||||
viewabilityConfig={VIEWABILITY_CONFIG}
|
||||
|
||||
@@ -7,12 +7,10 @@ import React, {PureComponent} from 'react';
|
||||
import {TouchableNativeFeedback, TouchableOpacity, TouchableWithoutFeedback, View} from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import CustomPropTypes from 'app/constants/custom_prop_types';
|
||||
|
||||
export default class TouchableWithFeedbackAndroid extends PureComponent {
|
||||
static propTypes = {
|
||||
testID: PropTypes.string,
|
||||
children: CustomPropTypes.Children,
|
||||
children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf([PropTypes.node])]),
|
||||
underlayColor: PropTypes.string,
|
||||
type: PropTypes.oneOf(['native', 'opacity', 'none']),
|
||||
};
|
||||
|
||||
@@ -5,12 +5,10 @@ import React, {PureComponent} from 'react';
|
||||
import {PanResponder, TouchableHighlight, TouchableOpacity, TouchableWithoutFeedback, View} from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import CustomPropTypes from 'app/constants/custom_prop_types';
|
||||
|
||||
export default class TouchableWithFeedbackIOS extends PureComponent {
|
||||
static propTypes = {
|
||||
testID: PropTypes.string,
|
||||
children: CustomPropTypes.Children,
|
||||
children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf([PropTypes.node])]),
|
||||
cancelTouchOnPanning: PropTypes.bool,
|
||||
type: PropTypes.oneOf(['native', 'opacity', 'none']),
|
||||
};
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
export const Children = PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf([PropTypes.node])]);
|
||||
|
||||
export const Style = PropTypes.oneOfType([
|
||||
PropTypes.object, // inline style
|
||||
PropTypes.number, // style sheet entry
|
||||
PropTypes.array,
|
||||
]);
|
||||
|
||||
export default {
|
||||
Children,
|
||||
Style,
|
||||
};
|
||||
12
app/constants/custom_prop_types.ts
Normal file
12
app/constants/custom_prop_types.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {ReactNode, ReactNodeArray} from 'react';
|
||||
import {StyleProp, TextStyle, ViewStyle} from 'react-native';
|
||||
|
||||
interface CustomPropTypes {
|
||||
Children: ReactNode | ReactNodeArray;
|
||||
Style: object | number | StyleProp<TextStyle> | StyleProp<TextStyle>[] | StyleProp<ViewStyle> | StyleProp<ViewStyle>[];
|
||||
}
|
||||
|
||||
export default CustomPropTypes;
|
||||
@@ -112,9 +112,9 @@ const ViewTypes = keyMirror({
|
||||
});
|
||||
|
||||
const RequiredServer = {
|
||||
FULL_VERSION: 5.25,
|
||||
FULL_VERSION: 5.31,
|
||||
MAJOR_VERSION: 5,
|
||||
MIN_VERSION: 25,
|
||||
MIN_VERSION: 31,
|
||||
PATCH_VERSION: 0,
|
||||
};
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ class EMMProvider {
|
||||
|
||||
this.allowOtherServers = true;
|
||||
this.emmServerUrl = null;
|
||||
this.inAppSessionAuth = false;
|
||||
}
|
||||
|
||||
checkIfDeviceIsTrusted = () => {
|
||||
@@ -106,6 +107,8 @@ class EMMProvider {
|
||||
this.jailbreakProtection = managedConfig.jailbreakProtection === 'true';
|
||||
this.vendor = managedConfig.vendor || 'Mattermost';
|
||||
|
||||
this.inAppSessionAuth = managedConfig.inAppSessionAuth === 'true';
|
||||
|
||||
const credentials = await getAppCredentials();
|
||||
if (!credentials) {
|
||||
this.emmServerUrl = managedConfig.serverUrl;
|
||||
|
||||
@@ -25,7 +25,7 @@ import {logError} from './errors';
|
||||
import {bindClientFunc, forceLogoutIfNecessary} from './helpers';
|
||||
import {getMissingProfilesByIds} from './users';
|
||||
import {loadRolesIfNeeded} from './roles';
|
||||
import {analytics} from '@init/analytics.ts';
|
||||
import {analytics} from '@init/analytics';
|
||||
|
||||
export function selectChannel(channelId: string) {
|
||||
return {
|
||||
|
||||
@@ -4,7 +4,7 @@ import {GifTypes} from '@mm-redux/action_types';
|
||||
import gfycatSdk from '@mm-redux/utils/gfycat_sdk';
|
||||
import {DispatchFunc, GetStateFunc} from '@mm-redux/types/actions';
|
||||
import {GlobalState} from '@mm-redux/types/store';
|
||||
import {analytics} from '@init/analytics.ts';
|
||||
import {analytics} from '@init/analytics';
|
||||
|
||||
// APP PROPS
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import {Client4} from '@mm-redux/client';
|
||||
import {getCurrentUserId} from '@mm-redux/selectors/entities/users';
|
||||
import {getCurrentChannelId} from '@mm-redux/selectors/entities/channels';
|
||||
import {getCurrentTeamId} from '@mm-redux/selectors/entities/teams';
|
||||
import {analytics} from '@init/analytics.ts';
|
||||
import {analytics} from '@init/analytics';
|
||||
|
||||
import {batchActions, DispatchFunc, GetStateFunc, ActionFunc} from '@mm-redux/types/actions';
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ import {getMyChannelMember, markChannelAsUnread, markChannelAsRead, markChannelA
|
||||
import {getCustomEmojiByName, getCustomEmojisByName} from './emojis';
|
||||
import {logError} from './errors';
|
||||
import {forceLogoutIfNecessary} from './helpers';
|
||||
import {analytics} from '@init/analytics.ts';
|
||||
import {analytics} from '@init/analytics';
|
||||
|
||||
import {
|
||||
deletePreferences,
|
||||
|
||||
@@ -13,7 +13,7 @@ import {ActionResult, batchActions, DispatchFunc, GetStateFunc, ActionFunc} from
|
||||
import {RelationOneToOne} from '@mm-redux/types/utilities';
|
||||
import {Post} from '@mm-redux/types/posts';
|
||||
import {SearchParameter} from '@mm-redux/types/search';
|
||||
import {analytics} from '@init/analytics.ts';
|
||||
import {analytics} from '@init/analytics';
|
||||
|
||||
const WEBAPP_SEARCH_PER_PAGE = 20;
|
||||
export function getMissingChannelsFromPosts(posts: RelationOneToOne<Post, Post>): ActionFunc {
|
||||
|
||||
@@ -22,7 +22,7 @@ import {logError} from './errors';
|
||||
import {bindClientFunc, forceLogoutIfNecessary, debounce} from './helpers';
|
||||
import {getMyPreferences, makeDirectChannelVisibleIfNecessary, makeGroupMessageVisibleIfNecessary} from './preferences';
|
||||
import {Dictionary} from '@mm-redux/types/utilities';
|
||||
import {analytics} from '@init/analytics.ts';
|
||||
import {analytics} from '@init/analytics';
|
||||
|
||||
export function checkMfa(loginId: string): ActionFunc {
|
||||
return async (dispatch: DispatchFunc) => {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user