forked from Ivasoft/mattermost-mobile
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
63ce6c7afb | ||
|
|
7bea097c90 | ||
|
|
0fe72d1c10 | ||
|
|
d3211b66a3 | ||
|
|
39bdb69fdd | ||
|
|
8222390c98 | ||
|
|
456208d223 | ||
|
|
2e71fcc226 | ||
|
|
04d7834024 | ||
|
|
7888b971e1 | ||
|
|
49462bd4a0 | ||
|
|
eb8579e84c | ||
|
|
41caf0d865 | ||
|
|
6a91b6544d | ||
|
|
75628cdf2e | ||
|
|
c8ee3bc722 | ||
|
|
3011992995 | ||
|
|
e23960f27d | ||
|
|
84a292003e |
@@ -286,6 +286,14 @@ jobs:
|
||||
name: Post results to Mattermost
|
||||
command: go run ../security-automation-config/dependency-check/post_results.go
|
||||
|
||||
build-android-beta:
|
||||
executor: android
|
||||
steps:
|
||||
- build-android
|
||||
- persist
|
||||
- save:
|
||||
filename: "*.apk"
|
||||
|
||||
build-android-release:
|
||||
executor: android
|
||||
steps:
|
||||
@@ -325,6 +333,14 @@ jobs:
|
||||
- save:
|
||||
filename: "*.apk"
|
||||
|
||||
build-ios-beta:
|
||||
executor: ios
|
||||
steps:
|
||||
- build-ios
|
||||
- persist
|
||||
- save:
|
||||
filename: "*.ipa"
|
||||
|
||||
build-ios-release:
|
||||
executor: ios
|
||||
steps:
|
||||
@@ -400,6 +416,16 @@ jobs:
|
||||
target: android
|
||||
file: "*.apk"
|
||||
|
||||
deploy-android-beta:
|
||||
executor:
|
||||
name: android
|
||||
resource_class: medium
|
||||
steps:
|
||||
- deploy-to-store:
|
||||
task: "Deploy to Google Play"
|
||||
target: android
|
||||
file: "*.apk"
|
||||
|
||||
deploy-ios-release:
|
||||
executor: ios
|
||||
steps:
|
||||
@@ -408,6 +434,14 @@ jobs:
|
||||
target: ios
|
||||
file: "*.ipa"
|
||||
|
||||
deploy-ios-beta:
|
||||
executor: ios
|
||||
steps:
|
||||
- deploy-to-store:
|
||||
task: "Deploy to TestFlight"
|
||||
target: ios
|
||||
file: "*.ipa"
|
||||
|
||||
github-release:
|
||||
executor:
|
||||
name: android
|
||||
@@ -451,6 +485,27 @@ workflows:
|
||||
- /^build-android-\d+$/
|
||||
- /^build-android-release-\d+$/
|
||||
|
||||
- build-android-beta:
|
||||
context: mattermost-mobile-android-beta
|
||||
requires:
|
||||
- test
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /^build-\d+$/
|
||||
- /^build-android-\d+$/
|
||||
- /^build-android-beta-\d+$/
|
||||
- deploy-android-beta:
|
||||
context: mattermost-mobile-android-beta
|
||||
requires:
|
||||
- build-android-beta
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /^build-\d+$/
|
||||
- /^build-android-\d+$/
|
||||
- /^build-android-beta-\d+$/
|
||||
|
||||
- build-ios-release:
|
||||
context: mattermost-mobile-ios-release
|
||||
requires:
|
||||
@@ -472,6 +527,27 @@ workflows:
|
||||
- /^build-ios-\d+$/
|
||||
- /^build-ios-release-\d+$/
|
||||
|
||||
- build-ios-beta:
|
||||
context: mattermost-mobile-ios-beta
|
||||
requires:
|
||||
- test
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /^build-\d+$/
|
||||
- /^build-ios-\d+$/
|
||||
- /^build-ios-beta-\d+$/
|
||||
- deploy-ios-beta:
|
||||
context: mattermost-mobile-ios-beta
|
||||
requires:
|
||||
- build-ios-beta
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /^build-\d+$/
|
||||
- /^build-ios-\d+$/
|
||||
- /^build-ios-beta-\d+$/
|
||||
|
||||
- build-android-pr:
|
||||
context: mattermost-mobile-android-pr
|
||||
requires:
|
||||
@@ -514,6 +590,7 @@ workflows:
|
||||
only:
|
||||
- /^build-\d+$/
|
||||
- /^build-ios-\d+$/
|
||||
- /^build-ios-beta-\d+$/
|
||||
- /^build-ios-sim-\d+$/
|
||||
|
||||
- github-release:
|
||||
@@ -525,4 +602,4 @@ workflows:
|
||||
tags:
|
||||
only: /^v(\d+\.)(\d+\.)(\d+)(.*)?$/
|
||||
branches:
|
||||
only: unsigned
|
||||
only: unsigned
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
"newlines-between": "always",
|
||||
"pathGroups": [
|
||||
{
|
||||
"pattern": "@(@react-native-async-storage|@react-native-community|@react-native-cookies|@react-navigation|@rudderstack|@sentry|@testing-library)/**",
|
||||
"pattern": "@(@react-native-async-storage|@react-native-community|@react-native-cookies|@react-navigation|@rudderstack|@sentry|@testing-library|@storybook)/**",
|
||||
"group": "external",
|
||||
"position": "before"
|
||||
},
|
||||
|
||||
@@ -63,4 +63,4 @@ untyped-import
|
||||
untyped-type-import
|
||||
|
||||
[version]
|
||||
^0.162.0
|
||||
^0.158.0
|
||||
|
||||
8
.github/ISSUE_TEMPLATE.md
vendored
8
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,12 +1,12 @@
|
||||
Per Mattermost guidelines, GitHub issues are for bug reports: <https://handbook.mattermost.com/contributors/contributors/ways-to-contribute>.
|
||||
Per Mattermost guidelines, GitHub issues are for bug reports: <http://www.mattermost.org/filing-issues/>.
|
||||
|
||||
For troubleshooting see: https://forum.mattermost.com/.
|
||||
For feature proposals see: https://www.mattermost.com/feature-ideas/
|
||||
For troubleshooting see: http://forum.mattermost.org/.
|
||||
For feature proposals see: http://www.mattermost.org/feature-requests/
|
||||
|
||||
If you've found a bug--something appears unintentional--please follow these steps:
|
||||
|
||||
1. Confirm you’re filing a new issue. [Search existing tickets in Jira](https://mattermost.atlassian.net/jira/software/c/projects/MM/issues/) to ensure that the ticket does not already exist.
|
||||
2. Confirm your issue does not involve security. Otherwise, please see our [Responsible Disclosure Policy](https://mattermost.com/security-vulnerability-report/).
|
||||
2. Confirm your issue does not involve security. Otherwise, please see our [Responsible Disclosure Policy](https://about.mattermost.com/report-security-issue/).
|
||||
3. [File a new issue](https://github.com/mattermost/mattermost-mobile/issues/new) using the format below. Mattermost will confirm steps to reproduce and file in Jira, or ask for more details if there is trouble reproducing it. If there's already an existing bug in Jira, it will be linked back to the GitHub issue so you can track when it gets fixed.
|
||||
|
||||
#### Summary
|
||||
|
||||
13
.github/codeql/codeql-config.yml
vendored
13
.github/codeql/codeql-config.yml
vendored
@@ -1,13 +0,0 @@
|
||||
name: "CodeQL config"
|
||||
|
||||
query-filters:
|
||||
- exclude:
|
||||
problem.severity:
|
||||
- warning
|
||||
- recommendation
|
||||
- exclude:
|
||||
id: js/insecure-randomness
|
||||
|
||||
paths-ignore:
|
||||
- test
|
||||
- '**/*.test.*'
|
||||
23
.github/workflows/codeql-analysis.yml
vendored
23
.github/workflows/codeql-analysis.yml
vendored
@@ -9,13 +9,8 @@ on:
|
||||
schedule:
|
||||
- cron: '0 0 * * 0'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
permissions:
|
||||
security-events: write
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -23,20 +18,26 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'javascript' ]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
uses: github/codeql-action/init@v1
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
config-file: ./.github/codeql/codeql-config.yml
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||
|
||||
# Autobuild attempts to build any compiled languages
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
uses: github/codeql-action/analyze@v1
|
||||
|
||||
55
.github/workflows/scorecards-analysis.yml
vendored
55
.github/workflows/scorecards-analysis.yml
vendored
@@ -1,55 +0,0 @@
|
||||
name: Scorecards supply-chain security
|
||||
on:
|
||||
# Only the default branch is supported.
|
||||
branch_protection_rule:
|
||||
schedule:
|
||||
- cron: '22 14 * * 1'
|
||||
push:
|
||||
branches: [ master ]
|
||||
|
||||
# Declare default permissions as read only.
|
||||
permissions: read-all
|
||||
|
||||
jobs:
|
||||
analysis:
|
||||
name: Scorecards analysis
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
# Needed to upload the results to code-scanning dashboard.
|
||||
security-events: write
|
||||
actions: read
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: "Run analysis"
|
||||
uses: ossf/scorecard-action@c8416b0b2bf627c349ca92fc8e3de51a64b005cf # v1.0.2
|
||||
with:
|
||||
results_file: results.sarif
|
||||
results_format: sarif
|
||||
# Read-only PAT token. To create it,
|
||||
# follow the steps in https://github.com/ossf/scorecard-action#pat-token-creation.
|
||||
repo_token: ${{ secrets.SCORECARD_READ_TOKEN }}
|
||||
# Publish the results to enable scorecard badges. For more details, see
|
||||
# https://github.com/ossf/scorecard-action#publishing-results.
|
||||
# For private repositories, `publish_results` will automatically be set to `false`,
|
||||
# regardless of the value entered here.
|
||||
publish_results: true
|
||||
|
||||
# Upload the results as artifacts (optional).
|
||||
- name: "Upload artifact"
|
||||
uses: actions/upload-artifact@82c141cc518b40d92cc801eee768e7aafc9c2fa2 # v2.3.1
|
||||
with:
|
||||
name: SARIF file
|
||||
path: results.sarif
|
||||
retention-days: 5
|
||||
|
||||
# Upload the results to GitHub's code scanning dashboard.
|
||||
- name: "Upload to code-scanning"
|
||||
uses: github/codeql-action/upload-sarif@5f532563584d71fdef14ee64d17bafb34f751ce5 # v1.0.26
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
12
.gitignore
vendored
12
.gitignore
vendored
@@ -14,7 +14,7 @@ env.d.ts
|
||||
|
||||
# Xcode
|
||||
#
|
||||
ios/build/*
|
||||
build/
|
||||
*.pbxuser
|
||||
!default.pbxuser
|
||||
*.mode1v3
|
||||
@@ -30,7 +30,6 @@ DerivedData
|
||||
*.hmap
|
||||
*.ipa
|
||||
*.apk
|
||||
*.aab
|
||||
*.xcuserstate
|
||||
project.xcworkspace
|
||||
ios/Pods
|
||||
@@ -42,11 +41,7 @@ ios/Pods
|
||||
.gradle
|
||||
local.properties
|
||||
*.iml
|
||||
*.hprof
|
||||
.cxx/
|
||||
android/app/bin
|
||||
android/app/build
|
||||
android/build
|
||||
.settings
|
||||
.project
|
||||
.classpath
|
||||
@@ -107,8 +102,3 @@ detox/detox_pixel_4_xl_api_30
|
||||
|
||||
#editor-settings
|
||||
.vscode
|
||||
.scannerwork
|
||||
|
||||
# Notice.txt generation
|
||||
!build/notice-file
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Submit feature requests to https://www.mattermost.com/feature-ideas/. File non-security related bugs here in the following format:
|
||||
Submit feature requests to http://www.mattermost.org/feature-requests/. File non-security related bugs here in the following format:
|
||||
|
||||
#### Summary
|
||||
Issue in one concise sentence.
|
||||
|
||||
820
NOTICE.txt
820
NOTICE.txt
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,7 @@
|
||||
# Note: development on this repository has primarily moved to the `main` branch
|
||||
This `master`(now `v1`) branch is mostly in maintenance mode while we're working to getting our "v2" (code named "gekidou") to the GA stage. To contribute check out the [main](https://github.com/mattermost/mattermost-mobile/tree/main) branch in this repository.
|
||||
|
||||
# Mattermost Mobile App
|
||||
[](https://mattermost.com)
|
||||
|
||||
- **Minimum Server versions:** Current ESR version (6.3.0)
|
||||
- **Minimum Server versions:** Current ESR version (5.37.0)
|
||||
- **Supported iOS versions:** 12.1+
|
||||
- **Supported Android versions:** 7.0+
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ Security updates
|
||||
|
||||
Mattermost has a mandatory upgrade policy, and updates are only provided for the latest release. Critical updates are delivered as dot releases. Details on security updates are announced 30 days after the availability of the update.
|
||||
|
||||
For more details about the security content of past releases, see the [Security Updates](https://mattermost.com/security-updates/) page on the Mattermost website. For timely notifications about new security updates, subscribe to the [Security Bulletins Mailing List](https://mattermost.com/security-updates/#sign-up).
|
||||
For more details about the security content of past releases, see the [Security Updates](https://mattermost.com/security-updates/) page on the Mattermost website. For timely notifications about new security updates, subscribe to the [Security Bulletins Mailing List](https://about.mattermost.com/security-bulletin).
|
||||
|
||||
Contributing to this policy
|
||||
---------------------------
|
||||
|
||||
@@ -113,7 +113,7 @@ def jscFlavor = 'org.webkit:android-jsc-intl:+'
|
||||
/**
|
||||
* Whether to enable the Hermes VM.
|
||||
*
|
||||
* This should be set on project.ext.react and that value will be read here. If it is not set
|
||||
* This should be set on project.ext.react and mirrored here. If it is not set
|
||||
* on project.ext.react, JavaScript will not be compiled to Hermes Bytecode
|
||||
* and the benefits of using Hermes will therefore be sharply reduced.
|
||||
*/
|
||||
@@ -131,8 +131,9 @@ android {
|
||||
applicationId "com.mattermost.rnbeta"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 436
|
||||
versionName "1.56.0"
|
||||
missingDimensionStrategy "RNNotifications.reactNativeVersion", "reactNative60"
|
||||
versionCode 382
|
||||
versionName "1.48.2"
|
||||
multiDexEnabled = true
|
||||
testBuildType System.getProperty('testBuildType', 'debug')
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
@@ -199,11 +200,6 @@ android {
|
||||
pickFirst '**/*.so'
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
checkReleaseBuilds false
|
||||
abortOnError false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
repositories {
|
||||
|
||||
@@ -8,6 +8,6 @@
|
||||
android:usesCleartextTraffic="true"
|
||||
tools:targetApi="28"
|
||||
tools:ignore="GoogleAppIndexingWarning">
|
||||
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" android:exported="false" />
|
||||
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
|
||||
</application>
|
||||
</manifest>
|
||||
</manifest>
|
||||
|
||||
@@ -33,12 +33,10 @@
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:label="@string/app_name"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:launchMode="singleTask"
|
||||
android:taskAffinity=""
|
||||
android:exported="true"
|
||||
>
|
||||
android:taskAffinity="">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
@@ -74,9 +72,7 @@
|
||||
android:screenOrientation="portrait"
|
||||
android:theme="@style/AppTheme"
|
||||
android:taskAffinity="com.mattermost.share"
|
||||
android:launchMode="singleInstance"
|
||||
android:exported="true"
|
||||
>
|
||||
android:launchMode="singleInstance">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
||||
@@ -90,9 +86,5 @@
|
||||
<intent>
|
||||
<action android:name="com.google.android.youtube.api.service.START" />
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<data android:mimeType="*/*" />
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
</manifest>
|
||||
|
||||
@@ -8,11 +8,13 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import com.swmansion.reanimated.ReanimatedJSIModulePackage;
|
||||
import com.ammarahmed.mmkv.RNMMKVModule;
|
||||
|
||||
public class CustomMMKVJSIModulePackage extends ReanimatedJSIModulePackage {
|
||||
@Override
|
||||
public List<JSIModuleSpec> getJSIModules(ReactApplicationContext reactApplicationContext, JavaScriptContextHolder jsContext) {
|
||||
super.getJSIModules(reactApplicationContext, jsContext);
|
||||
reactApplicationContext.getNativeModule(RNMMKVModule.class).installLib(jsContext, reactApplicationContext.getFilesDir().getAbsolutePath() + "/mmkv");
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.wix.reactnativenotifications.core.notification.PushNotification;
|
||||
import com.wix.reactnativenotifications.core.NotificationIntentAdapter;
|
||||
import com.wix.reactnativenotifications.core.AppLaunchHelper;
|
||||
import com.wix.reactnativenotifications.core.AppLifecycleFacade;
|
||||
import com.wix.reactnativenotifications.core.JsIOHelper;
|
||||
@@ -247,7 +246,7 @@ public class CustomPushNotification extends PushNotification {
|
||||
}
|
||||
|
||||
private void buildNotification(Integer notificationId, boolean createSummary) {
|
||||
final PendingIntent pendingIntent = NotificationIntentAdapter.createPendingNotificationIntent(mContext, mNotificationProps);
|
||||
final PendingIntent pendingIntent = super.getCTAPendingIntent();
|
||||
final Notification notification = buildNotification(pendingIntent);
|
||||
if (createSummary) {
|
||||
final Notification summary = getNotificationSummaryBuilder(pendingIntent).build();
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.mattermost.rnbeta;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
@@ -129,20 +128,11 @@ public class CustomPushNotificationHelper {
|
||||
replyIntent.putExtra(NOTIFICATION_ID, notificationId);
|
||||
replyIntent.putExtra("pushNotification", bundle);
|
||||
|
||||
PendingIntent replyPendingIntent;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
replyPendingIntent = PendingIntent.getBroadcast(
|
||||
context,
|
||||
notificationId,
|
||||
replyIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
|
||||
} else {
|
||||
replyPendingIntent = PendingIntent.getBroadcast(
|
||||
context,
|
||||
notificationId,
|
||||
replyIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
}
|
||||
PendingIntent replyPendingIntent = PendingIntent.getBroadcast(
|
||||
context,
|
||||
notificationId,
|
||||
replyIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
|
||||
RemoteInput remoteInput = new RemoteInput.Builder(KEY_TEXT_REPLY)
|
||||
.setLabel("Reply")
|
||||
@@ -391,12 +381,10 @@ public class CustomPushNotificationHelper {
|
||||
|
||||
private static void setNotificationDeleteIntent(Context context, NotificationCompat.Builder notification, Bundle bundle, int notificationId) {
|
||||
// Let's add a delete intent when the notification is dismissed
|
||||
final String PUSH_NOTIFICATION_EXTRA_NAME = "pushNotification";
|
||||
Intent delIntent = new Intent(context, NotificationDismissService.class);
|
||||
PushNotificationProps notificationProps = new PushNotificationProps(bundle);
|
||||
delIntent.putExtra(NOTIFICATION_ID, notificationId);
|
||||
delIntent.putExtra(PUSH_NOTIFICATION_EXTRA_NAME, bundle);
|
||||
@SuppressLint("UnspecifiedImmutableFlag")
|
||||
PendingIntent deleteIntent = PendingIntent.getService(context, (int) System.currentTimeMillis(), delIntent, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE);
|
||||
PendingIntent deleteIntent = NotificationIntentAdapter.createPendingNotificationIntent(context, delIntent, notificationProps);
|
||||
notification.setDeleteIntent(deleteIntent);
|
||||
}
|
||||
|
||||
|
||||
@@ -129,8 +129,9 @@ public class NotificationReplyBroadcastReceiver extends BroadcastReceiver {
|
||||
}
|
||||
|
||||
private void recreateNotification(int notificationId, final CharSequence message) {
|
||||
final Intent cta = new Intent(mContext, ProxyService.class);
|
||||
final PushNotificationProps notificationProps = new PushNotificationProps(bundle);
|
||||
final PendingIntent pendingIntent = NotificationIntentAdapter.createPendingNotificationIntent(mContext, notificationProps);
|
||||
final PendingIntent pendingIntent = NotificationIntentAdapter.createPendingNotificationIntent(mContext, cta, notificationProps);
|
||||
NotificationCompat.Builder builder = CustomPushNotificationHelper.createNotificationBuilder(mContext, pendingIntent, bundle, false);
|
||||
Notification notification = builder.build();
|
||||
NotificationCompat.MessagingStyle messagingStyle = NotificationCompat.MessagingStyle.extractMessagingStyleFromNotification(notification);
|
||||
|
||||
@@ -167,11 +167,6 @@ public class ShareModule extends ReactContextBaseJavaModule {
|
||||
map.putString("type", type);
|
||||
map.putBoolean("isString", false);
|
||||
items.pushMap(map);
|
||||
|
||||
map = Arguments.createMap();
|
||||
map.putString("value", extra);
|
||||
map.putBoolean("isString", true);
|
||||
items.pushMap(map);
|
||||
}
|
||||
} else if (Intent.ACTION_SEND_MULTIPLE.equals(action)) {
|
||||
ArrayList<Uri> uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
# Define the library name here.
|
||||
project(rndiffapp_appmodules)
|
||||
# This file includes all the necessary to let you build your application with the New Architecture.
|
||||
include(${REACT_ANDROID_DIR}/cmake-utils/ReactNative-application.cmake)
|
||||
@@ -1,32 +0,0 @@
|
||||
#include "MainApplicationModuleProvider.h"
|
||||
|
||||
#include <rncli.h>
|
||||
#include <rncore.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
std::shared_ptr<TurboModule> MainApplicationModuleProvider(
|
||||
const std::string &moduleName,
|
||||
const JavaTurboModule::InitParams ¶ms) {
|
||||
// Here you can provide your own module provider for TurboModules coming from
|
||||
// either your application or from external libraries. The approach to follow
|
||||
// is similar to the following (for a library called `samplelibrary`:
|
||||
//
|
||||
// auto module = samplelibrary_ModuleProvider(moduleName, params);
|
||||
// if (module != nullptr) {
|
||||
// return module;
|
||||
// }
|
||||
// return rncore_ModuleProvider(moduleName, params);
|
||||
|
||||
// Module providers autolinked by RN CLI
|
||||
auto rncli_module = rncli_ModuleProvider(moduleName, params);
|
||||
if (rncli_module != nullptr) {
|
||||
return rncli_module;
|
||||
}
|
||||
|
||||
return rncore_ModuleProvider(moduleName, params);
|
||||
}
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
||||
@@ -1,16 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include <ReactCommon/JavaTurboModule.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
std::shared_ptr<TurboModule> MainApplicationModuleProvider(
|
||||
const std::string &moduleName,
|
||||
const JavaTurboModule::InitParams ¶ms);
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
||||
@@ -1,45 +0,0 @@
|
||||
#include "MainApplicationTurboModuleManagerDelegate.h"
|
||||
#include "MainApplicationModuleProvider.h"
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
jni::local_ref<MainApplicationTurboModuleManagerDelegate::jhybriddata>
|
||||
MainApplicationTurboModuleManagerDelegate::initHybrid(
|
||||
jni::alias_ref<jhybridobject>) {
|
||||
return makeCxxInstance();
|
||||
}
|
||||
|
||||
void MainApplicationTurboModuleManagerDelegate::registerNatives() {
|
||||
registerHybrid({
|
||||
makeNativeMethod(
|
||||
"initHybrid", MainApplicationTurboModuleManagerDelegate::initHybrid),
|
||||
makeNativeMethod(
|
||||
"canCreateTurboModule",
|
||||
MainApplicationTurboModuleManagerDelegate::canCreateTurboModule),
|
||||
});
|
||||
}
|
||||
|
||||
std::shared_ptr<TurboModule>
|
||||
MainApplicationTurboModuleManagerDelegate::getTurboModule(
|
||||
const std::string &name,
|
||||
const std::shared_ptr<CallInvoker> &jsInvoker) {
|
||||
// Not implemented yet: provide pure-C++ NativeModules here.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<TurboModule>
|
||||
MainApplicationTurboModuleManagerDelegate::getTurboModule(
|
||||
const std::string &name,
|
||||
const JavaTurboModule::InitParams ¶ms) {
|
||||
return MainApplicationModuleProvider(name, params);
|
||||
}
|
||||
|
||||
bool MainApplicationTurboModuleManagerDelegate::canCreateTurboModule(
|
||||
const std::string &name) {
|
||||
return getTurboModule(name, nullptr) != nullptr ||
|
||||
getTurboModule(name, {.moduleName = name}) != nullptr;
|
||||
}
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
||||
@@ -1,38 +0,0 @@
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include <ReactCommon/TurboModuleManagerDelegate.h>
|
||||
#include <fbjni/fbjni.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
class MainApplicationTurboModuleManagerDelegate
|
||||
: public jni::HybridClass<
|
||||
MainApplicationTurboModuleManagerDelegate,
|
||||
TurboModuleManagerDelegate> {
|
||||
public:
|
||||
// Adapt it to the package you used for your Java class.
|
||||
static constexpr auto kJavaDescriptor =
|
||||
"Lcom/rndiffapp/newarchitecture/modules/MainApplicationTurboModuleManagerDelegate;";
|
||||
|
||||
static jni::local_ref<jhybriddata> initHybrid(jni::alias_ref<jhybridobject>);
|
||||
|
||||
static void registerNatives();
|
||||
|
||||
std::shared_ptr<TurboModule> getTurboModule(
|
||||
const std::string &name,
|
||||
const std::shared_ptr<CallInvoker> &jsInvoker) override;
|
||||
std::shared_ptr<TurboModule> getTurboModule(
|
||||
const std::string &name,
|
||||
const JavaTurboModule::InitParams ¶ms) override;
|
||||
|
||||
/**
|
||||
* Test-only method. Allows user to verify whether a TurboModule can be
|
||||
* created by instances of this class.
|
||||
*/
|
||||
bool canCreateTurboModule(const std::string &name);
|
||||
};
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
||||
@@ -1,65 +0,0 @@
|
||||
#include "MainComponentsRegistry.h"
|
||||
|
||||
#include <CoreComponentsRegistry.h>
|
||||
#include <fbjni/fbjni.h>
|
||||
#include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h>
|
||||
#include <react/renderer/components/rncore/ComponentDescriptors.h>
|
||||
#include <rncli.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
MainComponentsRegistry::MainComponentsRegistry(ComponentFactory *delegate) {}
|
||||
|
||||
std::shared_ptr<ComponentDescriptorProviderRegistry const>
|
||||
MainComponentsRegistry::sharedProviderRegistry() {
|
||||
auto providerRegistry = CoreComponentsRegistry::sharedProviderRegistry();
|
||||
|
||||
// Autolinked providers registered by RN CLI
|
||||
rncli_registerProviders(providerRegistry);
|
||||
|
||||
// Custom Fabric Components go here. You can register custom
|
||||
// components coming from your App or from 3rd party libraries here.
|
||||
//
|
||||
// providerRegistry->add(concreteComponentDescriptorProvider<
|
||||
// AocViewerComponentDescriptor>());
|
||||
return providerRegistry;
|
||||
}
|
||||
|
||||
jni::local_ref<MainComponentsRegistry::jhybriddata>
|
||||
MainComponentsRegistry::initHybrid(
|
||||
jni::alias_ref<jclass>,
|
||||
ComponentFactory *delegate) {
|
||||
auto instance = makeCxxInstance(delegate);
|
||||
|
||||
auto buildRegistryFunction =
|
||||
[](EventDispatcher::Weak const &eventDispatcher,
|
||||
ContextContainer::Shared const &contextContainer)
|
||||
-> ComponentDescriptorRegistry::Shared {
|
||||
auto registry = MainComponentsRegistry::sharedProviderRegistry()
|
||||
->createComponentDescriptorRegistry(
|
||||
{eventDispatcher, contextContainer});
|
||||
|
||||
auto mutableRegistry =
|
||||
std::const_pointer_cast<ComponentDescriptorRegistry>(registry);
|
||||
|
||||
mutableRegistry->setFallbackComponentDescriptor(
|
||||
std::make_shared<UnimplementedNativeViewComponentDescriptor>(
|
||||
ComponentDescriptorParameters{
|
||||
eventDispatcher, contextContainer, nullptr}));
|
||||
|
||||
return registry;
|
||||
};
|
||||
|
||||
delegate->buildRegistryFunction = buildRegistryFunction;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void MainComponentsRegistry::registerNatives() {
|
||||
registerHybrid({
|
||||
makeNativeMethod("initHybrid", MainComponentsRegistry::initHybrid),
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
||||
@@ -1,36 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2014 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<inset xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:insetLeft="@dimen/abc_edit_text_inset_horizontal_material"
|
||||
android:insetRight="@dimen/abc_edit_text_inset_horizontal_material"
|
||||
android:insetTop="@dimen/abc_edit_text_inset_top_material"
|
||||
android:insetBottom="@dimen/abc_edit_text_inset_bottom_material">
|
||||
|
||||
<selector>
|
||||
<!--
|
||||
This file is a copy of abc_edit_text_material (https://bit.ly/3k8fX7I).
|
||||
The item below with state_pressed="false" and state_focused="false" causes a NullPointerException.
|
||||
NullPointerException:tempt to invoke virtual method 'android.graphics.drawable.Drawable android.graphics.drawable.Drawable$ConstantState.newDrawable(android.content.res.Resources)'
|
||||
|
||||
<item android:state_pressed="false" android:state_focused="false" android:drawable="@drawable/abc_textfield_default_mtrl_alpha"/>
|
||||
|
||||
For more info, see https://bit.ly/3CdLStv (react-native/pull/29452) and https://bit.ly/3nxOMoR.
|
||||
-->
|
||||
<item android:state_enabled="false" android:drawable="@drawable/abc_textfield_default_mtrl_alpha"/>
|
||||
<item android:drawable="@drawable/abc_textfield_activated_mtrl_alpha"/>
|
||||
</selector>
|
||||
|
||||
</inset>
|
||||
@@ -4,7 +4,7 @@
|
||||
<style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="android:windowBackground">@color/splashscreen_bg</item>
|
||||
<item name="android:editTextBackground">@drawable/rn_edit_text_material</item>
|
||||
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<files-path name="files" path="." />
|
||||
<external-files-path name="external_files" path="." />
|
||||
<external-path name="external" path="." />
|
||||
<cache-path name="cache" path="." />
|
||||
<root-path name="root" path="." />
|
||||
</paths>
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
buildscript {
|
||||
ext {
|
||||
buildToolsVersion = "31.0.0"
|
||||
buildToolsVersion = "30.0.2"
|
||||
minSdkVersion = 24
|
||||
compileSdkVersion = 31
|
||||
targetSdkVersion = 31
|
||||
supportLibVersion = "31.0.0"
|
||||
compileSdkVersion = 30
|
||||
targetSdkVersion = 30
|
||||
supportLibVersion = "28.0.0"
|
||||
kotlinVersion = "1.5.30"
|
||||
firebaseVersion = "21.0.0"
|
||||
RNNKotlinVersion = kotlinVersion
|
||||
@@ -14,13 +14,14 @@ buildscript {
|
||||
|
||||
}
|
||||
repositories {
|
||||
jcenter()
|
||||
google()
|
||||
mavenCentral()
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:4.2.2'
|
||||
classpath 'com.google.gms:google-services:4.3.10'
|
||||
classpath 'com.google.gms:google-services:4.2.0'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
@@ -31,9 +32,9 @@ buildscript {
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
mavenLocal()
|
||||
jcenter()
|
||||
maven {
|
||||
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
|
||||
url("$rootDir/../node_modules/react-native/android")
|
||||
@@ -48,13 +49,6 @@ allprojects {
|
||||
// prebuilt libv8android.so
|
||||
// url("$rootDir/../node_modules/v8-android/dist")
|
||||
}
|
||||
mavenCentral {
|
||||
// We don't want to fetch react-native from Maven Central as there are
|
||||
// older versions over there.
|
||||
content {
|
||||
excludeGroup "com.facebook.react"
|
||||
}
|
||||
}
|
||||
maven {
|
||||
url "https://www.jitpack.io"
|
||||
}
|
||||
|
||||
@@ -9,8 +9,9 @@
|
||||
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
# Default value: -Xmx512m -XX:MaxMetaspaceSize=256m
|
||||
org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m
|
||||
# Default value: -Xmx10248m -XX:MaxPermSize=256m
|
||||
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
||||
org.gradle.jvmargs=-Xmx2048M
|
||||
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
|
||||
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-7.3.3-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.9-all.zip
|
||||
|
||||
259
android/gradlew
vendored
259
android/gradlew
vendored
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
# Copyright 2015 the original author or authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -17,113 +17,78 @@
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=${0##*/}
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
} >&2
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
} >&2
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
@@ -132,7 +97,7 @@ Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
@@ -140,95 +105,79 @@ location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=`save "$@"`
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
@@ -3,75 +3,17 @@
|
||||
|
||||
import {sendEphemeralPost} from '@actions/views/post';
|
||||
import {Client4} from '@client/rest';
|
||||
import {AppCallResponseTypes} from '@mm-redux/constants/apps';
|
||||
import {AppCallTypes, AppCallResponseTypes} from '@mm-redux/constants/apps';
|
||||
import {ActionFunc, DispatchFunc} from '@mm-redux/types/actions';
|
||||
import {AppCallResponse, AppCallRequest, AppContext, AppBinding} from '@mm-redux/types/apps';
|
||||
import {AppCallResponse, AppCallRequest, AppCallType, AppContext} from '@mm-redux/types/apps';
|
||||
import {CommandArgs} from '@mm-redux/types/integrations';
|
||||
import {Post} from '@mm-redux/types/posts';
|
||||
import {cleanForm, createCallRequest, makeCallErrorResponse} from '@utils/apps';
|
||||
import {cleanForm, makeCallErrorResponse} from '@utils/apps';
|
||||
|
||||
export function handleBindingClick<Res=unknown>(binding: AppBinding, context: AppContext, intl: any): ActionFunc {
|
||||
return async (dispatch: DispatchFunc) => {
|
||||
// Fetch form
|
||||
if (binding.form?.source) {
|
||||
const callRequest = createCallRequest(
|
||||
binding.form.source,
|
||||
context,
|
||||
);
|
||||
|
||||
const res = await dispatch(doAppFetchForm<Res>(callRequest, intl));
|
||||
return res;
|
||||
}
|
||||
|
||||
// Open form
|
||||
if (binding.form) {
|
||||
// This should come properly formed, but using preventive checks
|
||||
if (!binding.form?.submit) {
|
||||
const errMsg = intl.formatMessage({
|
||||
id: 'apps.error.malformed_binding',
|
||||
defaultMessage: 'This binding is not properly formed. Contact the App developer.',
|
||||
});
|
||||
return {error: makeCallErrorResponse(errMsg)};
|
||||
}
|
||||
|
||||
const res: AppCallResponse = {
|
||||
type: AppCallResponseTypes.FORM,
|
||||
form: binding.form,
|
||||
};
|
||||
return {data: res};
|
||||
}
|
||||
|
||||
// Submit binding
|
||||
// This should come properly formed, but using preventive checks
|
||||
if (!binding.submit) {
|
||||
const errMsg = intl.formatMessage({
|
||||
id: 'apps.error.malformed_binding',
|
||||
defaultMessage: 'This binding is not properly formed. Contact the App developer.',
|
||||
});
|
||||
return {error: makeCallErrorResponse(errMsg)};
|
||||
}
|
||||
|
||||
const callRequest = createCallRequest(
|
||||
binding.submit,
|
||||
context,
|
||||
);
|
||||
|
||||
const res = await dispatch(doAppSubmit<Res>(callRequest, intl));
|
||||
return res;
|
||||
};
|
||||
}
|
||||
|
||||
export function doAppSubmit<Res=unknown>(inCall: AppCallRequest, intl: any): ActionFunc {
|
||||
export function doAppCall<Res=unknown>(call: AppCallRequest, type: AppCallType, intl: any): ActionFunc {
|
||||
return async () => {
|
||||
try {
|
||||
const call: AppCallRequest = {
|
||||
...inCall,
|
||||
context: {
|
||||
...inCall.context,
|
||||
track_as_submit: true,
|
||||
},
|
||||
};
|
||||
const res = await Client4.executeAppCall(call, true) as AppCallResponse<Res>;
|
||||
const res = await Client4.executeAppCall(call, type) as AppCallResponse<Res>;
|
||||
const responseType = res.type || AppCallResponseTypes.OK;
|
||||
|
||||
switch (responseType) {
|
||||
@@ -80,10 +22,10 @@ export function doAppSubmit<Res=unknown>(inCall: AppCallRequest, intl: any): Act
|
||||
case AppCallResponseTypes.ERROR:
|
||||
return {error: res};
|
||||
case AppCallResponseTypes.FORM: {
|
||||
if (!res.form?.submit) {
|
||||
if (!res.form) {
|
||||
const errMsg = intl.formatMessage({
|
||||
id: 'apps.error.responses.form.no_form',
|
||||
defaultMessage: 'Response type is `form`, but no valid form was included in response.',
|
||||
defaultMessage: 'Response type is `form`, but no form was included in response.',
|
||||
});
|
||||
return {error: makeCallErrorResponse(errMsg)};
|
||||
}
|
||||
@@ -101,6 +43,14 @@ export function doAppSubmit<Res=unknown>(inCall: AppCallRequest, intl: any): Act
|
||||
return {error: makeCallErrorResponse(errMsg)};
|
||||
}
|
||||
|
||||
if (type !== AppCallTypes.SUBMIT) {
|
||||
const errMsg = intl.formatMessage({
|
||||
id: 'apps.error.responses.navigate.no_submit',
|
||||
defaultMessage: 'Response type is `navigate`, but the call was not a submission.',
|
||||
});
|
||||
return {error: makeCallErrorResponse(errMsg)};
|
||||
}
|
||||
|
||||
return {data: res};
|
||||
default: {
|
||||
const errMsg = intl.formatMessage({
|
||||
@@ -122,73 +72,6 @@ export function doAppSubmit<Res=unknown>(inCall: AppCallRequest, intl: any): Act
|
||||
};
|
||||
}
|
||||
|
||||
export function doAppFetchForm<Res=unknown>(call: AppCallRequest, intl: any): ActionFunc {
|
||||
return async () => {
|
||||
try {
|
||||
const res = await Client4.executeAppCall(call, false) as AppCallResponse<Res>;
|
||||
const responseType = res.type || AppCallResponseTypes.OK;
|
||||
|
||||
switch (responseType) {
|
||||
case AppCallResponseTypes.ERROR:
|
||||
return {error: res};
|
||||
case AppCallResponseTypes.FORM:
|
||||
if (!res.form?.submit) {
|
||||
const errMsg = intl.formatMessage({
|
||||
id: 'apps.error.responses.form.no_form',
|
||||
defaultMessage: 'Response type is `form`, but no valid form was included in response.',
|
||||
});
|
||||
return {error: makeCallErrorResponse(errMsg)};
|
||||
}
|
||||
cleanForm(res.form);
|
||||
return {data: res};
|
||||
default: {
|
||||
const errMsg = intl.formatMessage({
|
||||
id: 'apps.error.responses.unknown_type',
|
||||
defaultMessage: 'App response type not supported. Response type: {type}.',
|
||||
}, {type: responseType});
|
||||
return {error: makeCallErrorResponse(errMsg)};
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
const errMsg = error.message || intl.formatMessage({
|
||||
id: 'apps.error.responses.unexpected_error',
|
||||
defaultMessage: 'Received an unexpected error.',
|
||||
});
|
||||
return {error: makeCallErrorResponse(errMsg)};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function doAppLookup<Res=unknown>(call: AppCallRequest, intl: any): ActionFunc {
|
||||
return async () => {
|
||||
try {
|
||||
const res = await Client4.executeAppCall(call, false) as AppCallResponse<Res>;
|
||||
const responseType = res.type || AppCallResponseTypes.OK;
|
||||
|
||||
switch (responseType) {
|
||||
case AppCallResponseTypes.OK:
|
||||
return {data: res};
|
||||
case AppCallResponseTypes.ERROR:
|
||||
return {error: res};
|
||||
|
||||
default: {
|
||||
const errMsg = intl.formatMessage({
|
||||
id: 'apps.error.responses.unknown_type',
|
||||
defaultMessage: 'App response type not supported. Response type: {type}.',
|
||||
}, {type: responseType});
|
||||
return {error: makeCallErrorResponse(errMsg)};
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
const errMsg = error.message || intl.formatMessage({
|
||||
id: 'apps.error.responses.unexpected_error',
|
||||
defaultMessage: 'Received an unexpected error.',
|
||||
});
|
||||
return {error: makeCallErrorResponse(errMsg)};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function postEphemeralCallResponseForPost(response: AppCallResponse, message: string, post: Post): ActionFunc {
|
||||
return (dispatch: DispatchFunc) => {
|
||||
return dispatch(sendEphemeralPost(
|
||||
|
||||
@@ -19,19 +19,19 @@ import {getPreferenceKey} from '@mm-redux/utils/preference_utils';
|
||||
import {isDirectChannelVisible, isGroupChannelVisible} from '@utils/channels';
|
||||
import {buildPreference} from '@utils/preferences';
|
||||
|
||||
export async function loadSidebarDirectMessagesProfiles(state: GlobalState, channels: Channel[], channelMembers: ChannelMembership[]) {
|
||||
export async function loadSidebarDirectMessagesProfiles(state: GlobalState, channels: Array<Channel>, channelMembers: Array<ChannelMembership>) {
|
||||
const currentUserId = getCurrentUserId(state);
|
||||
const usersInChannel: RelationOneToMany<Channel, UserProfile> = getUserIdsInChannels(state);
|
||||
const directChannels = Object.values(channels).filter((c) => c.type === General.DM_CHANNEL || c.type === General.GM_CHANNEL);
|
||||
const prefs: PreferenceType[] = [];
|
||||
const prefs: Array<PreferenceType> = [];
|
||||
const promises: Array<Promise<ActionResult>> = []; //only fetch profiles that we don't have and the Direct channel should be visible
|
||||
const actions = [];
|
||||
const userIds: string[] = [];
|
||||
const userIds: Array<string> = [];
|
||||
|
||||
// Prepare preferences and start fetching profiles to batch them
|
||||
directChannels.forEach((c) => {
|
||||
const profileIds = Array.from(usersInChannel[c.id] || []);
|
||||
const profilesInChannel: string[] = profileIds.filter((u: string) => u !== currentUserId);
|
||||
const profilesInChannel: Array<string> = profileIds.filter((u: string) => u !== currentUserId);
|
||||
userIds.push(...profilesInChannel);
|
||||
|
||||
switch (c.type) {
|
||||
@@ -118,7 +118,7 @@ export async function fetchMyChannelMember(channelId: string) {
|
||||
}
|
||||
}
|
||||
|
||||
export function markChannelAsUnread(state: GlobalState, teamId: string, channelId: string, mentions: string[], isRoot = false): GenericAction[] {
|
||||
export function markChannelAsUnread(state: GlobalState, teamId: string, channelId: string, mentions: Array<string>, isRoot = false): Array<GenericAction> {
|
||||
const {myMembers} = state.entities.channels;
|
||||
const {currentUserId} = state.entities.users;
|
||||
|
||||
@@ -214,8 +214,8 @@ export async function makeGroupMessageVisibleIfNecessary(state: GlobalState, cha
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchChannelAndMyMember(channelId: string): Promise<GenericAction[]> {
|
||||
const actions: GenericAction[] = [];
|
||||
export async function fetchChannelAndMyMember(channelId: string): Promise<Array<GenericAction>> {
|
||||
const actions: Array<GenericAction> = [];
|
||||
|
||||
try {
|
||||
const [channel, member] = await Promise.all([
|
||||
@@ -246,9 +246,9 @@ export async function fetchChannelAndMyMember(channelId: string): Promise<Generi
|
||||
return actions;
|
||||
}
|
||||
|
||||
export async function getAddedDmUsersIfNecessary(state: GlobalState, preferences: PreferenceType[]): Promise<GenericAction[]> {
|
||||
export async function getAddedDmUsersIfNecessary(state: GlobalState, preferences: PreferenceType[]): Promise<Array<GenericAction>> {
|
||||
const userIds: string[] = [];
|
||||
const actions: GenericAction[] = [];
|
||||
const actions: Array<GenericAction> = [];
|
||||
|
||||
for (const preference of preferences) {
|
||||
if (preference.category === Preferences.CATEGORY_DIRECT_CHANNEL_SHOW && preference.value === 'true') {
|
||||
@@ -319,7 +319,7 @@ export function lastChannelIdForTeam(state: GlobalState, teamId: string): string
|
||||
return firstChannel;
|
||||
}
|
||||
|
||||
function fetchDirectMessageProfileIfNeeded(state: GlobalState, channel: Channel, channelMembers: ChannelMembership[], profilesInChannel: string[]) {
|
||||
function fetchDirectMessageProfileIfNeeded(state: GlobalState, channel: Channel, channelMembers: Array<ChannelMembership>, profilesInChannel: Array<string>) {
|
||||
const currentUserId = getCurrentUserId(state);
|
||||
const myPreferences = getMyPreferences(state);
|
||||
const users = getUsers(state);
|
||||
@@ -351,7 +351,7 @@ function fetchDirectMessageProfileIfNeeded(state: GlobalState, channel: Channel,
|
||||
return {preferences};
|
||||
}
|
||||
|
||||
function fetchGroupMessageProfilesIfNeeded(state: GlobalState, channel: Channel, channelMembers: ChannelMembership[], profilesInChannel: string[]) {
|
||||
function fetchGroupMessageProfilesIfNeeded(state: GlobalState, channel: Channel, channelMembers: Array<ChannelMembership>, profilesInChannel: Array<string>) {
|
||||
const currentUserId = getCurrentUserId(state);
|
||||
const myPreferences = getMyPreferences(state);
|
||||
const config = getConfig(state);
|
||||
|
||||
@@ -373,7 +373,7 @@ export function showSearchModal(initialValue = '') {
|
||||
showModal(name, title, passProps, options);
|
||||
}
|
||||
|
||||
export const showAppForm = async (form, context, theme) => {
|
||||
export const showAppForm = async (form, call, theme) => {
|
||||
const closeButton = await CompassIcon.getImageSource('close', 24, theme.sidebarHeaderTextColor);
|
||||
|
||||
let submitButtons;
|
||||
@@ -397,7 +397,7 @@ export const showAppForm = async (form, context, theme) => {
|
||||
},
|
||||
};
|
||||
|
||||
const passProps = {form, context};
|
||||
const passProps = {form, call};
|
||||
showModal('AppForm', form.title, passProps, options);
|
||||
};
|
||||
|
||||
|
||||
@@ -232,6 +232,7 @@ export function handleSelectChannel(channelId) {
|
||||
dispatch(batchActions(actions, 'BATCH_SWITCH_CHANNEL'));
|
||||
|
||||
if (appsEnabled(state)) {
|
||||
//TODO improve sync method
|
||||
dispatch(fetchAppBindings(currentUserId, channelId));
|
||||
}
|
||||
console.log('channel switch to', channel?.display_name, channelId, (Date.now() - dt), 'ms'); //eslint-disable-line
|
||||
|
||||
@@ -166,9 +166,6 @@ describe('Actions.Views.Channel', () => {
|
||||
[currentTeamId]: {},
|
||||
},
|
||||
},
|
||||
apps: {
|
||||
pluginEnabled: true,
|
||||
},
|
||||
general: {
|
||||
config: {
|
||||
EnableLegacySidebar: 'true',
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
|
||||
import {intlShape} from 'react-intl';
|
||||
|
||||
import {doAppSubmit, postEphemeralCallResponseForCommandArgs} from '@actions/apps';
|
||||
import {doAppCall, postEphemeralCallResponseForCommandArgs} from '@actions/apps';
|
||||
import {AppCommandParser} from '@components/autocomplete/slash_suggestion/app_command_parser/app_command_parser';
|
||||
import {IntegrationTypes} from '@mm-redux/action_types';
|
||||
import {executeCommand as executeCommandService} from '@mm-redux/actions/integrations';
|
||||
import {AppCallResponseTypes} from '@mm-redux/constants/apps';
|
||||
import {AppCallResponseTypes, AppCallTypes} from '@mm-redux/constants/apps';
|
||||
import {getCurrentTeamId} from '@mm-redux/selectors/entities/teams';
|
||||
import {DispatchFunc, GetStateFunc, ActionFunc} from '@mm-redux/types/actions';
|
||||
import {AppCallResponse} from '@mm-redux/types/apps';
|
||||
@@ -41,19 +41,19 @@ export function executeCommand(message: string, channelId: string, rootId: strin
|
||||
if (appsAreEnabled) {
|
||||
const parser = new AppCommandParser({dispatch, getState}, intl, args.channel_id, args.team_id, args.root_id);
|
||||
if (parser.isAppCommand(msg)) {
|
||||
const {creq, errorMessage} = await parser.composeCommandSubmitCall(msg);
|
||||
const {call, errorMessage} = await parser.composeCallFromCommand(msg);
|
||||
const createErrorMessage = (errMessage: string) => {
|
||||
return {error: {message: errMessage}};
|
||||
};
|
||||
|
||||
if (!creq) {
|
||||
if (!call) {
|
||||
return createErrorMessage(errorMessage!);
|
||||
}
|
||||
|
||||
const res = await dispatch(doAppSubmit(creq, intl));
|
||||
const res = await dispatch(doAppCall(call, AppCallTypes.SUBMIT, intl));
|
||||
if (res.error) {
|
||||
const errorResponse = res.error as AppCallResponse;
|
||||
return createErrorMessage(errorResponse.text || intl.formatMessage({
|
||||
return createErrorMessage(errorResponse.error || intl.formatMessage({
|
||||
id: 'apps.error.unknown',
|
||||
defaultMessage: 'Unknown error.',
|
||||
}));
|
||||
@@ -61,14 +61,14 @@ export function executeCommand(message: string, channelId: string, rootId: strin
|
||||
const callResp = res.data as AppCallResponse;
|
||||
switch (callResp.type) {
|
||||
case AppCallResponseTypes.OK:
|
||||
if (callResp.text) {
|
||||
dispatch(postEphemeralCallResponseForCommandArgs(callResp, callResp.text, args));
|
||||
if (callResp.markdown) {
|
||||
dispatch(postEphemeralCallResponseForCommandArgs(callResp, callResp.markdown, args));
|
||||
}
|
||||
return {data: {}};
|
||||
case AppCallResponseTypes.FORM:
|
||||
return {data: {
|
||||
form: callResp.form,
|
||||
call: creq,
|
||||
call,
|
||||
}};
|
||||
case AppCallResponseTypes.NAVIGATE:
|
||||
return {data: {
|
||||
|
||||
@@ -3,13 +3,10 @@
|
||||
|
||||
import {intlShape} from 'react-intl';
|
||||
import {Keyboard} from 'react-native';
|
||||
import {Navigation} from 'react-native-navigation';
|
||||
|
||||
import {showModalOverCurrentContext} from '@actions/navigation';
|
||||
import {dismissAllModals, showModalOverCurrentContext} from '@actions/navigation';
|
||||
import {loadChannelsByTeamName} from '@actions/views/channel';
|
||||
import {getPost as fetchPost, selectFocusedPostId} from '@mm-redux/actions/posts';
|
||||
import {getPost} from '@mm-redux/selectors/entities/posts';
|
||||
import {isCollapsedThreadsEnabled} from '@mm-redux/selectors/entities/preferences';
|
||||
import {selectFocusedPostId} from '@mm-redux/actions/posts';
|
||||
import {getCurrentTeam} from '@mm-redux/selectors/entities/teams';
|
||||
import {permalinkBadTeam} from '@utils/general';
|
||||
import {changeOpacity} from '@utils/theme';
|
||||
@@ -20,50 +17,26 @@ let showingPermalink = false;
|
||||
|
||||
export function showPermalink(intl: typeof intlShape, teamName: string, postId: string, openAsPermalink = true) {
|
||||
return async (dispatch: DispatchFunc, getState: GetStateFunc) => {
|
||||
const state = getState();
|
||||
|
||||
let name = teamName;
|
||||
if (!name) {
|
||||
name = getCurrentTeam(state).name;
|
||||
name = getCurrentTeam(getState()).name;
|
||||
}
|
||||
|
||||
const loadTeam = await dispatch(loadChannelsByTeamName(name, permalinkBadTeam.bind(null, intl)));
|
||||
|
||||
let isThreadPost;
|
||||
|
||||
const collapsedThreadsEnabled = isCollapsedThreadsEnabled(state);
|
||||
if (collapsedThreadsEnabled) {
|
||||
let post = getPost(state, postId);
|
||||
if (!post) {
|
||||
const {data} = await dispatch(fetchPost(postId));
|
||||
if (data) {
|
||||
post = data;
|
||||
}
|
||||
}
|
||||
if (post) {
|
||||
isThreadPost = Boolean(post.root_id);
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
if (!loadTeam.error) {
|
||||
Keyboard.dismiss();
|
||||
dispatch(selectFocusedPostId(postId));
|
||||
if (showingPermalink) {
|
||||
await dismissAllModals();
|
||||
}
|
||||
|
||||
const screen = 'Permalink';
|
||||
const passProps = {
|
||||
isPermalink: openAsPermalink,
|
||||
isThreadPost,
|
||||
focusedPostId: postId,
|
||||
teamName,
|
||||
};
|
||||
|
||||
if (showingPermalink) {
|
||||
Navigation.updateProps(screen, passProps);
|
||||
return {};
|
||||
}
|
||||
|
||||
const options = {
|
||||
layout: {
|
||||
componentBackgroundColor: changeOpacity('#000', 0.2),
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import deepEqual from 'deep-equal';
|
||||
import {batchActions} from 'redux-batched-actions';
|
||||
|
||||
import {Client4} from '@client/rest';
|
||||
@@ -32,10 +31,7 @@ export function startDataCleanup() {
|
||||
|
||||
export function loadConfigAndLicense() {
|
||||
return async (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const {currentUserId} = state.entities.users;
|
||||
const {general} = state.entities;
|
||||
const actions = [];
|
||||
const {currentUserId} = getState().entities.users;
|
||||
|
||||
try {
|
||||
const [config, license] = await Promise.all([
|
||||
@@ -43,31 +39,23 @@ export function loadConfigAndLicense() {
|
||||
Client4.getClientLicenseOld(),
|
||||
]);
|
||||
|
||||
if (!deepEqual(general.config, config)) {
|
||||
actions.push({
|
||||
type: GeneralTypes.CLIENT_CONFIG_RECEIVED,
|
||||
data: config,
|
||||
});
|
||||
}
|
||||
const actions = [{
|
||||
type: GeneralTypes.CLIENT_CONFIG_RECEIVED,
|
||||
data: config,
|
||||
}, {
|
||||
type: GeneralTypes.CLIENT_LICENSE_RECEIVED,
|
||||
data: license,
|
||||
}];
|
||||
|
||||
if (!deepEqual(general.license, license)) {
|
||||
actions.push({
|
||||
type: GeneralTypes.CLIENT_LICENSE_RECEIVED,
|
||||
data: license,
|
||||
});
|
||||
|
||||
if (currentUserId) {
|
||||
if (license?.IsLicensed === 'true' && license?.DataRetention === 'true') {
|
||||
dispatch(getDataRetentionPolicy());
|
||||
} else {
|
||||
actions.push({type: GeneralTypes.RECEIVED_DATA_RETENTION_POLICY, data: {}});
|
||||
}
|
||||
if (currentUserId) {
|
||||
if (license?.IsLicensed === 'true' && license?.DataRetention === 'true') {
|
||||
dispatch(getDataRetentionPolicy());
|
||||
} else {
|
||||
actions.push({type: GeneralTypes.RECEIVED_DATA_RETENTION_POLICY, data: {}});
|
||||
}
|
||||
}
|
||||
|
||||
if (actions.length) {
|
||||
dispatch(batchActions(actions, 'BATCH_LOAD_CONFIG_AND_LICENSE'));
|
||||
}
|
||||
dispatch(batchActions(actions, 'BATCH_LOAD_CONFIG_AND_LICENSE'));
|
||||
|
||||
return {config, license};
|
||||
} catch (error) {
|
||||
@@ -76,7 +64,7 @@ export function loadConfigAndLicense() {
|
||||
};
|
||||
}
|
||||
|
||||
export function loadFromPushNotification(notification, isInitialNotification, skipChannelSwitch = false) {
|
||||
export function loadFromPushNotification(notification, isInitialNotification) {
|
||||
return async (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const {payload} = notification;
|
||||
@@ -108,14 +96,12 @@ export function loadFromPushNotification(notification, isInitialNotification, sk
|
||||
await Promise.all(loading);
|
||||
}
|
||||
|
||||
if (!skipChannelSwitch) {
|
||||
dispatch(handleSelectTeamAndChannel(teamId, channelId));
|
||||
dispatch(selectPost(''));
|
||||
dispatch(handleSelectTeamAndChannel(teamId, channelId));
|
||||
dispatch(selectPost(''));
|
||||
|
||||
const {root_id: rootId} = notification.payload || {};
|
||||
if (isCollapsedThreadsEnabled(state) && rootId) {
|
||||
dispatch(selectPost(rootId));
|
||||
}
|
||||
const {root_id: rootId} = notification.payload || {};
|
||||
if (isCollapsedThreadsEnabled(state) && rootId) {
|
||||
dispatch(selectPost(rootId));
|
||||
}
|
||||
return {data: true};
|
||||
};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import deepEqual from 'deep-equal';
|
||||
import {batchActions} from 'redux-batched-actions';
|
||||
|
||||
import {handleCRTPreferenceChange} from '@actions/views/crt';
|
||||
@@ -122,12 +121,9 @@ export function loadMe(user, deviceToken, skipDispatch = false) {
|
||||
data.teams = teams;
|
||||
data.teamMembers = teamMembers;
|
||||
data.teamUnreads = teamUnreads;
|
||||
data.config = config;
|
||||
data.url = Client4.getUrl();
|
||||
|
||||
if (!deepEqual(state.entities?.general?.config, config)) {
|
||||
data.config = config;
|
||||
}
|
||||
|
||||
actions.push({
|
||||
type: UserTypes.LOGIN,
|
||||
data,
|
||||
|
||||
@@ -1,29 +1,27 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import AppsTypes from '@mm-redux/action_types/apps';
|
||||
import {refreshAppBindings} from '@mm-redux/actions/apps';
|
||||
import {fetchAppBindings, fetchThreadAppBindings} from '@mm-redux/actions/apps';
|
||||
import {getThreadAppsBindingsChannelId} from '@mm-redux/selectors/entities/apps';
|
||||
import {getCurrentChannelId} from '@mm-redux/selectors/entities/common';
|
||||
import {getCurrentUserId} from '@mm-redux/selectors/entities/users';
|
||||
import {ActionResult, DispatchFunc, GetStateFunc} from '@mm-redux/types/actions';
|
||||
import {appsEnabled} from '@utils/apps';
|
||||
|
||||
export function handleRefreshAppsBindings() {
|
||||
return (dispatch: DispatchFunc, getState: GetStateFunc): ActionResult => {
|
||||
if (appsEnabled(getState())) {
|
||||
dispatch(refreshAppBindings());
|
||||
const state = getState();
|
||||
if (!appsEnabled(state)) {
|
||||
return {data: true};
|
||||
}
|
||||
|
||||
dispatch(fetchAppBindings(getCurrentUserId(state), getCurrentChannelId(state)));
|
||||
|
||||
const threadChannelID = getThreadAppsBindingsChannelId(state);
|
||||
if (threadChannelID) {
|
||||
dispatch(fetchThreadAppBindings(getCurrentUserId(state), threadChannelID));
|
||||
}
|
||||
|
||||
return {data: true};
|
||||
};
|
||||
}
|
||||
|
||||
export function handleAppsPluginEnabled() {
|
||||
return {
|
||||
type: AppsTypes.APPS_PLUGIN_ENABLED,
|
||||
};
|
||||
}
|
||||
|
||||
export function handleAppsPluginDisabled() {
|
||||
return {
|
||||
type: AppsTypes.APPS_PLUGIN_DISABLED,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ export function handleChannelDeletedEvent(msg: WebSocketMessage) {
|
||||
const currentTeamId = getCurrentTeamId(state);
|
||||
const config = getConfig(state);
|
||||
const viewArchivedChannels = config.ExperimentalViewArchivedChannels === 'true';
|
||||
const actions: GenericAction[] = [{
|
||||
const actions: Array<GenericAction> = [{
|
||||
type: ChannelTypes.RECEIVED_CHANNEL_DELETED,
|
||||
data: {
|
||||
id: msg.data.channel_id,
|
||||
@@ -94,7 +94,7 @@ export function handleChannelMemberUpdatedEvent(msg: WebSocketMessage) {
|
||||
try {
|
||||
const channelMember = JSON.parse(msg.data.channelMember);
|
||||
const rolesToLoad = channelMember.roles.split(' ');
|
||||
const actions: GenericAction[] = [{
|
||||
const actions: Array<GenericAction> = [{
|
||||
type: ChannelTypes.RECEIVED_MY_CHANNEL_MEMBER,
|
||||
data: channelMember,
|
||||
}];
|
||||
@@ -129,7 +129,7 @@ export function handleChannelUnarchiveEvent(msg: WebSocketMessage) {
|
||||
const viewArchivedChannels = config.ExperimentalViewArchivedChannels === 'true';
|
||||
|
||||
if (msg.broadcast.team_id === currentTeamId) {
|
||||
const actions: GenericAction[] = [{
|
||||
const actions: Array<GenericAction> = [{
|
||||
type: ChannelTypes.RECEIVED_CHANNEL_UNARCHIVED,
|
||||
data: {
|
||||
id: msg.data.channel_id,
|
||||
|
||||
@@ -6,7 +6,7 @@ import {ActionResult, DispatchFunc, batchActions} from '@mm-redux/types/actions'
|
||||
import {WebSocketMessage} from '@mm-redux/types/websocket';
|
||||
|
||||
export function handleGroupUpdatedEvent(msg: WebSocketMessage) {
|
||||
return (dispatch: DispatchFunc): ActionResult => {
|
||||
return (dispatch: DispatchFunc) : ActionResult => {
|
||||
const data = JSON.parse(msg.data.group);
|
||||
dispatch(batchActions([
|
||||
{
|
||||
|
||||
@@ -7,12 +7,11 @@ import {loadMe} from '@actions/views/user';
|
||||
import {Client4} from '@client/rest';
|
||||
import {WebsocketEvents} from '@constants';
|
||||
import {ChannelTypes, GeneralTypes, PreferenceTypes, TeamTypes, UserTypes, RoleTypes} from '@mm-redux/action_types';
|
||||
import {refreshAppBindings} from '@mm-redux/actions/apps';
|
||||
import {getThreads} from '@mm-redux/actions/threads';
|
||||
import {getProfilesByIds, getStatusesByIds} from '@mm-redux/actions/users';
|
||||
import {General} from '@mm-redux/constants';
|
||||
import {getCurrentChannelId, getCurrentChannelStats} from '@mm-redux/selectors/entities/channels';
|
||||
import {getConfig} from '@mm-redux/selectors/entities/general';
|
||||
import {getConfig, getFeatureFlagValue} from '@mm-redux/selectors/entities/general';
|
||||
import {getPostIdsInChannel} from '@mm-redux/selectors/entities/posts';
|
||||
import {isCollapsedThreadsEnabled} from '@mm-redux/selectors/entities/preferences';
|
||||
import {getCurrentTeamId} from '@mm-redux/selectors/entities/teams';
|
||||
@@ -24,10 +23,9 @@ import {TeamMembership} from '@mm-redux/types/teams';
|
||||
import {WebSocketMessage} from '@mm-redux/types/websocket';
|
||||
import EventEmitter from '@mm-redux/utils/event_emitter';
|
||||
import {removeUserFromList} from '@mm-redux/utils/user_utils';
|
||||
import {batchLoadCalls} from '@mmproducts/calls/store/actions/calls';
|
||||
import {loadCalls} from '@mmproducts/calls/store/actions/calls';
|
||||
import {
|
||||
handleCallStarted,
|
||||
handleCallEnded,
|
||||
handleCallUserConnected,
|
||||
handleCallUserDisconnected,
|
||||
handleCallUserMuted,
|
||||
@@ -37,15 +35,12 @@ import {
|
||||
handleCallChannelEnabled,
|
||||
handleCallChannelDisabled,
|
||||
handleCallScreenOn,
|
||||
handleCallScreenOff, handleCallUserRaiseHand, handleCallUserUnraiseHand,
|
||||
handleCallScreenOff,
|
||||
} from '@mmproducts/calls/store/actions/websockets';
|
||||
import {isSupportedServer} from '@mmproducts/calls/store/selectors/calls';
|
||||
import {appsConfiguredAsEnabled} from '@utils/apps';
|
||||
import {getChannelSinceValue} from '@utils/channels';
|
||||
import {semverFromServerVersion} from '@utils/general';
|
||||
import websocketClient from '@websocket';
|
||||
|
||||
import {handleAppsPluginDisabled, handleAppsPluginEnabled, handleRefreshAppsBindings} from './apps';
|
||||
import {handleRefreshAppsBindings} from './apps';
|
||||
import {handleSidebarCategoryCreated, handleSidebarCategoryDeleted, handleSidebarCategoryOrderUpdated, handleSidebarCategoryUpdated} from './categories';
|
||||
import {
|
||||
handleChannelConvertedEvent,
|
||||
@@ -116,7 +111,7 @@ export function doFirstConnect(now: number) {
|
||||
return async (dispatch: DispatchFunc, getState: GetStateFunc): Promise<ActionResult> => {
|
||||
const state = getState();
|
||||
const {lastDisconnectAt} = state.websocket;
|
||||
const actions: GenericAction[] = [wsConnected(now)];
|
||||
const actions: Array<GenericAction> = [wsConnected(now)];
|
||||
|
||||
if (lastDisconnectAt) {
|
||||
const currentUserId = getCurrentUserId(state);
|
||||
@@ -154,25 +149,20 @@ export function doReconnect(now: number) {
|
||||
const currentChannelId = getCurrentChannelId(state);
|
||||
const currentUserId = getCurrentUserId(state);
|
||||
const users = getUsers(state);
|
||||
const isSupportedServerCalls = isSupportedServer(state);
|
||||
const {lastDisconnectAt} = state.websocket;
|
||||
const actions: GenericAction[] = [];
|
||||
const actions: Array<GenericAction> = [];
|
||||
|
||||
dispatch(batchActions([
|
||||
wsConnected(now),
|
||||
setChannelRetryFailed(false),
|
||||
], 'BATCH_WS_SUCCESS'));
|
||||
|
||||
if (appsConfiguredAsEnabled(state)) {
|
||||
dispatch(refreshAppBindings());
|
||||
}
|
||||
|
||||
try {
|
||||
const {data: me}: any = await dispatch(loadMe(null, null, true));
|
||||
|
||||
if (!me.error) {
|
||||
if (isSupportedServerCalls) {
|
||||
dispatch(batchLoadCalls(true));
|
||||
if (getFeatureFlagValue(getState(), 'CallsMobile') === 'true') {
|
||||
dispatch(loadCalls());
|
||||
}
|
||||
|
||||
const roles = [];
|
||||
@@ -439,10 +429,6 @@ function handleEvent(msg: WebSocketMessage) {
|
||||
return dispatch(handleThreadFollowChanged(msg));
|
||||
case WebsocketEvents.APPS_FRAMEWORK_REFRESH_BINDINGS:
|
||||
return dispatch(handleRefreshAppsBindings());
|
||||
case WebsocketEvents.APPS_FRAMEWORK_PLUGIN_ENABLED:
|
||||
return dispatch(handleAppsPluginEnabled());
|
||||
case WebsocketEvents.APPS_FRAMEWORK_PLUGIN_DISABLED:
|
||||
return dispatch(handleAppsPluginDisabled());
|
||||
case WebsocketEvents.SIDEBAR_CATEGORY_CREATED:
|
||||
return dispatch(handleSidebarCategoryCreated(msg));
|
||||
case WebsocketEvents.SIDEBAR_CATEGORY_UPDATED:
|
||||
@@ -451,39 +437,33 @@ function handleEvent(msg: WebSocketMessage) {
|
||||
return dispatch(handleSidebarCategoryDeleted(msg));
|
||||
case WebsocketEvents.SIDEBAR_CATEGORY_ORDER_UPDATED:
|
||||
return dispatch(handleSidebarCategoryOrderUpdated(msg));
|
||||
}
|
||||
|
||||
// Calls ws events:
|
||||
case WebsocketEvents.CALLS_CHANNEL_ENABLED:
|
||||
return dispatch(handleCallChannelEnabled(msg));
|
||||
case WebsocketEvents.CALLS_CHANNEL_DISABLED:
|
||||
return dispatch(handleCallChannelDisabled(msg));
|
||||
case WebsocketEvents.CALLS_USER_CONNECTED:
|
||||
handleCallUserConnected(dispatch, getState, msg);
|
||||
break;
|
||||
case WebsocketEvents.CALLS_USER_DISCONNECTED:
|
||||
return dispatch(handleCallUserDisconnected(msg));
|
||||
case WebsocketEvents.CALLS_USER_MUTED:
|
||||
return dispatch(handleCallUserMuted(msg));
|
||||
case WebsocketEvents.CALLS_USER_UNMUTED:
|
||||
return dispatch(handleCallUserUnmuted(msg));
|
||||
case WebsocketEvents.CALLS_USER_VOICE_ON:
|
||||
handleCallUserVoiceOn(msg);
|
||||
break;
|
||||
case WebsocketEvents.CALLS_USER_VOICE_OFF:
|
||||
handleCallUserVoiceOff(msg);
|
||||
break;
|
||||
case WebsocketEvents.CALLS_CALL_START:
|
||||
return dispatch(handleCallStarted(msg));
|
||||
case WebsocketEvents.CALLS_CALL_END:
|
||||
return dispatch(handleCallEnded(msg));
|
||||
case WebsocketEvents.CALLS_SCREEN_ON:
|
||||
return dispatch(handleCallScreenOn(msg));
|
||||
case WebsocketEvents.CALLS_SCREEN_OFF:
|
||||
return dispatch(handleCallScreenOff(msg));
|
||||
case WebsocketEvents.CALLS_USER_RAISE_HAND:
|
||||
return dispatch(handleCallUserRaiseHand(msg));
|
||||
case WebsocketEvents.CALLS_USER_UNRAISE_HAND:
|
||||
return dispatch(handleCallUserUnraiseHand(msg));
|
||||
if (getFeatureFlagValue(getState(), 'CallsMobile') === 'true') {
|
||||
switch (msg.event) {
|
||||
case WebsocketEvents.CALLS_CHANNEL_ENABLED:
|
||||
return dispatch(handleCallChannelEnabled(msg));
|
||||
case WebsocketEvents.CALLS_CHANNEL_DISABLED:
|
||||
return dispatch(handleCallChannelDisabled(msg));
|
||||
case WebsocketEvents.CALLS_USER_CONNECTED:
|
||||
return dispatch(handleCallUserConnected(msg));
|
||||
case WebsocketEvents.CALLS_USER_DISCONNECTED:
|
||||
return dispatch(handleCallUserDisconnected(msg));
|
||||
case WebsocketEvents.CALLS_USER_MUTED:
|
||||
return dispatch(handleCallUserMuted(msg));
|
||||
case WebsocketEvents.CALLS_USER_UNMUTED:
|
||||
return dispatch(handleCallUserUnmuted(msg));
|
||||
case WebsocketEvents.CALLS_USER_VOICE_ON:
|
||||
return dispatch(handleCallUserVoiceOn(msg));
|
||||
case WebsocketEvents.CALLS_USER_VOICE_OFF:
|
||||
return dispatch(handleCallUserVoiceOff(msg));
|
||||
case WebsocketEvents.CALLS_CALL_START:
|
||||
return dispatch(handleCallStarted(msg));
|
||||
case WebsocketEvents.CALLS_SCREEN_ON:
|
||||
return dispatch(handleCallScreenOn(msg));
|
||||
case WebsocketEvents.CALLS_SCREEN_OFF:
|
||||
return dispatch(handleCallScreenOff(msg));
|
||||
}
|
||||
}
|
||||
|
||||
return {data: true};
|
||||
@@ -491,8 +471,11 @@ function handleEvent(msg: WebSocketMessage) {
|
||||
}
|
||||
|
||||
function handleHelloEvent(msg: WebSocketMessage) {
|
||||
const serverVersion = semverFromServerVersion(msg.data.server_version);
|
||||
EventEmitter.emit(General.SERVER_VERSION_CHANGED, serverVersion);
|
||||
const serverVersion = msg.data.server_version;
|
||||
if (serverVersion && Client4.serverVersion !== serverVersion) {
|
||||
Client4.serverVersion = serverVersion;
|
||||
EventEmitter.emit(General.SERVER_VERSION_CHANGED, serverVersion);
|
||||
}
|
||||
}
|
||||
|
||||
// Helpers
|
||||
|
||||
@@ -43,7 +43,7 @@ export function handleNewPostEvent(msg: WebSocketMessage) {
|
||||
ownPost: data.user_id === currentUserId,
|
||||
};
|
||||
|
||||
const actions: GenericAction[] = [];
|
||||
const actions: Array<GenericAction> = [];
|
||||
|
||||
const exists = selectPost(state, post.pending_post_id);
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import {WebSocketMessage} from '@mm-redux/types/websocket';
|
||||
export function handlePreferenceChangedEvent(msg: WebSocketMessage) {
|
||||
return async (dispatch: DispatchFunc, getState: GetStateFunc): Promise<ActionResult> => {
|
||||
const preference = JSON.parse(msg.data.preference);
|
||||
const actions: GenericAction[] = [{
|
||||
const actions: Array<GenericAction> = [{
|
||||
type: PreferenceTypes.RECEIVED_PREFERENCES,
|
||||
data: [preference],
|
||||
}];
|
||||
@@ -36,7 +36,7 @@ export function handlePreferencesChangedEvent(msg: WebSocketMessage) {
|
||||
return async (dispatch: DispatchFunc, getState: GetStateFunc): Promise<ActionResult> => {
|
||||
const preferences: PreferenceType[] = JSON.parse(msg.data.preferences);
|
||||
const posts = getAllPosts(getState());
|
||||
const actions: GenericAction[] = [{
|
||||
const actions: Array<GenericAction> = [{
|
||||
type: PreferenceTypes.RECEIVED_PREFERENCES,
|
||||
data: preferences,
|
||||
}];
|
||||
|
||||
@@ -19,7 +19,7 @@ export function handleLeaveTeamEvent(msg: Partial<WebSocketMessage>) {
|
||||
const currentUser = getCurrentUser(state);
|
||||
|
||||
if (currentUser.id === msg.data.user_id) {
|
||||
const actions: GenericAction[] = [{type: TeamTypes.LEAVE_TEAM, data: teams[msg.data.team_id]}];
|
||||
const actions: Array<GenericAction> = [{type: TeamTypes.LEAVE_TEAM, data: teams[msg.data.team_id]}];
|
||||
if (isGuest(currentUser.roles)) {
|
||||
const notVisible = await notVisibleUsersActions(state);
|
||||
if (notVisible.length) {
|
||||
|
||||
@@ -31,7 +31,7 @@ export function handleUserAddedEvent(msg: WebSocketMessage) {
|
||||
const currentTeamId = getCurrentTeamId(state);
|
||||
const currentUserId = getCurrentUserId(state);
|
||||
const teamId = msg.data.team_id;
|
||||
const actions: GenericAction[] = [{
|
||||
const actions: Array<GenericAction> = [{
|
||||
type: ChannelTypes.CHANNEL_MEMBER_ADDED,
|
||||
data: {
|
||||
channel_id: msg.broadcast.channel_id,
|
||||
@@ -71,7 +71,7 @@ export function handleUserRemovedEvent(msg: WebSocketMessage) {
|
||||
const currentChannelId = getCurrentChannelId(state);
|
||||
const currentTeamId = getCurrentTeamId(state);
|
||||
const currentUser = getCurrentUser(state);
|
||||
const actions: GenericAction[] = [];
|
||||
const actions: Array<GenericAction> = [];
|
||||
let channelId;
|
||||
let userId;
|
||||
|
||||
|
||||
@@ -84,7 +84,6 @@ describe('Actions.Websocket doReconnect', () => {
|
||||
const channel2 = TestHelper.fakeChannelWithId(team.id);
|
||||
const cMember1 = TestHelper.fakeChannelMember(me.id, channel1.id);
|
||||
const cMember2 = TestHelper.fakeChannelMember(me.id, channel2.id);
|
||||
const post1 = TestHelper.fakePost(channel1.id);
|
||||
|
||||
const currentTeamId = team.id;
|
||||
const currentUserId = me.id;
|
||||
@@ -93,9 +92,7 @@ describe('Actions.Websocket doReconnect', () => {
|
||||
const initialState = {
|
||||
entities: {
|
||||
general: {
|
||||
config: {
|
||||
FeatureFlagAppsEnabled: 'false',
|
||||
},
|
||||
config: {},
|
||||
},
|
||||
teams: {
|
||||
currentTeamId,
|
||||
@@ -109,7 +106,7 @@ describe('Actions.Websocket doReconnect', () => {
|
||||
channels: {
|
||||
currentChannelId,
|
||||
channels: {
|
||||
currentChannelId: channel1.id,
|
||||
currentChannelId: channel1,
|
||||
},
|
||||
},
|
||||
users: {
|
||||
@@ -122,20 +119,8 @@ describe('Actions.Websocket doReconnect', () => {
|
||||
myPreferences: {},
|
||||
},
|
||||
posts: {
|
||||
posts: {
|
||||
[post1.id]: post1,
|
||||
},
|
||||
postsInChannel: {
|
||||
[channel1.id]: [
|
||||
{
|
||||
recent: true,
|
||||
order: [post1.id],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
apps: {
|
||||
pluginEnabled: true,
|
||||
posts: {},
|
||||
postsInChannel: {},
|
||||
},
|
||||
},
|
||||
websocket: {
|
||||
@@ -143,13 +128,6 @@ describe('Actions.Websocket doReconnect', () => {
|
||||
lastConnectAt: 0,
|
||||
lastDisconnectAt: 0,
|
||||
},
|
||||
views: {
|
||||
channel: {
|
||||
lastGetPosts: {
|
||||
[channel1.id]: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
@@ -192,12 +170,14 @@ describe('Actions.Websocket doReconnect', () => {
|
||||
await TestHelper.tearDown();
|
||||
});
|
||||
|
||||
it('handle doReconnect base case', async () => {
|
||||
it('handle doReconnect', async () => {
|
||||
const state = {...initialState};
|
||||
const testStore = await mockStore(state);
|
||||
const timestamp = 1000;
|
||||
const expectedActions = [
|
||||
'BATCH_WS_SUCCESS',
|
||||
];
|
||||
const expectedMissingActions = [
|
||||
'BATCH_WS_RECONNECT',
|
||||
];
|
||||
|
||||
@@ -217,6 +197,7 @@ describe('Actions.Websocket doReconnect', () => {
|
||||
await TestHelper.wait(300);
|
||||
const actionTypes = testStore.getActions().map((a) => a.type);
|
||||
expect(actionTypes).toEqual(expectedActions);
|
||||
expect(actionTypes).not.toEqual(expect.arrayContaining(expectedMissingActions));
|
||||
});
|
||||
|
||||
it('handle doReconnect after the current channel was archived or the user left it', async () => {
|
||||
@@ -279,6 +260,8 @@ describe('Actions.Websocket doReconnect', () => {
|
||||
const timestamp = 1000;
|
||||
const expectedActions = [
|
||||
'BATCH_WS_SUCCESS',
|
||||
];
|
||||
const expectedMissingActions = [
|
||||
'BATCH_WS_RECONNECT',
|
||||
];
|
||||
|
||||
@@ -299,6 +282,7 @@ describe('Actions.Websocket doReconnect', () => {
|
||||
|
||||
const actions = testStore.getActions().map((a) => a.type);
|
||||
expect(actions).toEqual(expect.arrayContaining(expectedActions));
|
||||
expect(actions).not.toEqual(expect.arrayContaining(expectedMissingActions));
|
||||
});
|
||||
|
||||
it('handle doReconnect after the current channel was archived and setting is off', async () => {
|
||||
|
||||
@@ -3,21 +3,21 @@
|
||||
|
||||
import {buildQueryString} from '@mm-redux/utils/helpers';
|
||||
|
||||
import type {AppBinding, AppCallRequest, AppCallResponse} from '@mm-redux/types/apps';
|
||||
import type {AppBinding, AppCallRequest, AppCallResponse, AppCallType} from '@mm-redux/types/apps';
|
||||
|
||||
export interface ClientAppsMix {
|
||||
executeAppCall: (call: AppCallRequest, trackAsSubmit: boolean) => Promise<AppCallResponse>;
|
||||
executeAppCall: (call: AppCallRequest, type: AppCallType) => Promise<AppCallResponse>;
|
||||
getAppsBindings: (userID: string, channelID: string, teamID: string) => Promise<AppBinding[]>;
|
||||
}
|
||||
|
||||
const ClientApps = (superclass: any) => class extends superclass {
|
||||
executeAppCall = async (call: AppCallRequest, trackAsSubmit: boolean) => {
|
||||
executeAppCall = async (call: AppCallRequest, type: AppCallType) => {
|
||||
const callCopy = {
|
||||
...call,
|
||||
path: `${call.path}/${type}`,
|
||||
context: {
|
||||
...call.context,
|
||||
user_agent: 'mobile',
|
||||
track_as_submit: trackAsSubmit,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -25,7 +25,7 @@ const ClientApps = (superclass: any) => class extends superclass {
|
||||
`${this.getAppsProxyRoute()}/api/v1/call`,
|
||||
{method: 'post', body: JSON.stringify(callCopy)},
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
getAppsBindings = async (userID: string, channelID: string, teamID: string) => {
|
||||
const params = {
|
||||
@@ -39,7 +39,7 @@ const ClientApps = (superclass: any) => class extends superclass {
|
||||
`${this.getAppsProxyRoute()}/api/v1/bindings${buildQueryString(params)}`,
|
||||
{method: 'get'},
|
||||
);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export default ClientApps;
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
import {RNFetchBlobFetchRepsonse} from 'rn-fetch-blob';
|
||||
import urlParse from 'url-parse';
|
||||
|
||||
import Calls from '@constants/calls';
|
||||
import {Options} from '@mm-redux/types/client4';
|
||||
|
||||
import * as ClientConstants from './constants';
|
||||
@@ -92,7 +91,7 @@ export default class ClientBase {
|
||||
|
||||
getWebSocketUrl = () => {
|
||||
return `${this.getBaseRoute()}/websocket`;
|
||||
};
|
||||
}
|
||||
|
||||
setAcceptLanguage(locale: string) {
|
||||
this.defaultHeaders['Accept-Language'] = locale;
|
||||
@@ -287,16 +286,12 @@ export default class ClientBase {
|
||||
return `${this.getUserThreadsRoute(userId, teamId)}/${threadId}`;
|
||||
}
|
||||
|
||||
getPluginsRoute() {
|
||||
return `${this.getBaseRoute()}/plugins`;
|
||||
}
|
||||
|
||||
getAppsProxyRoute() {
|
||||
return `${this.url}/plugins/com.mattermost.apps`;
|
||||
}
|
||||
|
||||
getCallsRoute() {
|
||||
return `${this.url}/plugins/${Calls.PluginId}`;
|
||||
return `${this.url}/plugins/com.mattermost.calls`;
|
||||
}
|
||||
|
||||
// Client Helpers
|
||||
|
||||
@@ -19,21 +19,21 @@ const ClientBots = (superclass: any) => class extends superclass {
|
||||
`${this.getBotRoute(botUserId)}`,
|
||||
{method: 'get'},
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
getBots = async (page = 0, perPage = PER_PAGE_DEFAULT) => {
|
||||
return this.doFetch(
|
||||
`${this.getBotsRoute()}${buildQueryString({page, per_page: perPage})}`,
|
||||
{method: 'get'},
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
getBotsIncludeDeleted = async (page = 0, perPage = PER_PAGE_DEFAULT) => {
|
||||
return this.doFetch(
|
||||
`${this.getBotsRoute()}${buildQueryString({include_deleted: true, page, per_page: perPage})}`,
|
||||
{method: 'get'},
|
||||
);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export default ClientBots;
|
||||
|
||||
@@ -119,7 +119,12 @@ const ClientChannels = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
convertChannelToPrivate = async (channelId: string) => {
|
||||
return this.updateChannelPrivacy(channelId, 'P');
|
||||
analytics.trackAPI('api_channels_convert_to_private', {channel_id: channelId});
|
||||
|
||||
return this.doFetch(
|
||||
`${this.getChannelRoute(channelId)}/convert`,
|
||||
{method: 'post'},
|
||||
);
|
||||
};
|
||||
|
||||
updateChannelPrivacy = async (channelId: string, privacy: any) => {
|
||||
|
||||
@@ -41,7 +41,7 @@ const ClientFiles = (superclass: any) => class extends superclass {
|
||||
`${this.getFileRoute(fileId)}/link`,
|
||||
{method: 'get'},
|
||||
);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export default ClientFiles;
|
||||
|
||||
@@ -27,7 +27,7 @@ const ClientGroups = (superclass: any) => class extends superclass {
|
||||
`${this.getUsersRoute()}/${userID}/groups`,
|
||||
{method: 'get'},
|
||||
);
|
||||
};
|
||||
}
|
||||
getAllGroupsAssociatedToTeam = async (teamID: string, filterAllowReference = false) => {
|
||||
return this.doFetch(
|
||||
`${this.getBaseRoute()}/teams/${teamID}/groups${buildQueryString({paginate: false, filter_allow_reference: filterAllowReference})}`,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import ClientPlugins, {ClientPluginsMix} from '@client/rest/plugins';
|
||||
import ClientCalls, {ClientCallsMix} from '@mmproducts/calls/client/rest';
|
||||
import mix from '@utils/mix';
|
||||
|
||||
@@ -37,8 +36,7 @@ interface Client extends ClientBase,
|
||||
ClientTeamsMix,
|
||||
ClientTosMix,
|
||||
ClientUsersMix,
|
||||
ClientCallsMix,
|
||||
ClientPluginsMix
|
||||
ClientCallsMix
|
||||
{}
|
||||
|
||||
class Client extends mix(ClientBase).with(
|
||||
@@ -57,7 +55,6 @@ class Client extends mix(ClientBase).with(
|
||||
ClientTos,
|
||||
ClientUsers,
|
||||
ClientCalls,
|
||||
ClientPlugins,
|
||||
) {}
|
||||
|
||||
const Client4 = new Client();
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {ClientPluginManifest} from '@mm-redux/types/plugins';
|
||||
|
||||
export interface ClientPluginsMix {
|
||||
getPluginsManifests: () => Promise<ClientPluginManifest[]>;
|
||||
}
|
||||
|
||||
const ClientPlugins = (superclass: any) => class extends superclass {
|
||||
getPluginsManifests = async () => {
|
||||
return this.doFetch(
|
||||
`${this.getPluginsRoute()}/webapp`,
|
||||
{method: 'get'},
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
export default ClientPlugins;
|
||||
@@ -202,7 +202,7 @@ const ClientPosts = (superclass: any) => class extends superclass {
|
||||
`${this.getUserRoute(userId)}/posts/${postId}/set_unread`,
|
||||
{method: 'post', body: JSON.stringify({collapsed_threads_supported: true})},
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
pinPost = async (postId: string) => {
|
||||
analytics.trackAPI('api_posts_pin');
|
||||
|
||||
@@ -12,14 +12,14 @@ const ClientTos = (superclass: any) => class extends superclass {
|
||||
`${this.getUserRoute('me')}/terms_of_service`,
|
||||
{method: 'post', body: JSON.stringify({termsOfServiceId, accepted})},
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
getTermsOfService = async () => {
|
||||
return this.doFetch(
|
||||
`${this.getBaseRoute()}/terms_of_service`,
|
||||
{method: 'get'},
|
||||
);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export default ClientTos;
|
||||
|
||||
@@ -66,14 +66,14 @@ const ClientUsers = (superclass: any) => class extends superclass {
|
||||
`${this.getUsersRoute()}${buildQueryString(queryParams)}`,
|
||||
{method: 'post', body: JSON.stringify(user)},
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
patchMe = async (userPatch: Partial<UserProfile>) => {
|
||||
return this.doFetch(
|
||||
`${this.getUserRoute('me')}/patch`,
|
||||
{method: 'put', body: JSON.stringify(userPatch)},
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
patchUser = async (userPatch: Partial<UserProfile> & {id: string}) => {
|
||||
analytics.trackAPI('api_users_patch');
|
||||
@@ -82,7 +82,7 @@ const ClientUsers = (superclass: any) => class extends superclass {
|
||||
`${this.getUserRoute(userPatch.id)}/patch`,
|
||||
{method: 'put', body: JSON.stringify(userPatch)},
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
updateUser = async (user: UserProfile) => {
|
||||
analytics.trackAPI('api_users_update');
|
||||
@@ -91,7 +91,7 @@ const ClientUsers = (superclass: any) => class extends superclass {
|
||||
`${this.getUserRoute(user.id)}`,
|
||||
{method: 'put', body: JSON.stringify(user)},
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
demoteUserToGuest = async (userId: string) => {
|
||||
analytics.trackAPI('api_users_demote_user_to_guest');
|
||||
@@ -100,7 +100,7 @@ const ClientUsers = (superclass: any) => class extends superclass {
|
||||
`${this.getUserRoute(userId)}/demote`,
|
||||
{method: 'post'},
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
getKnownUsers = async () => {
|
||||
analytics.trackAPI('api_get_known_users');
|
||||
@@ -108,7 +108,7 @@ const ClientUsers = (superclass: any) => class extends superclass {
|
||||
`${this.getUsersRoute()}/known`,
|
||||
{method: 'get'},
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
sendPasswordResetEmail = async (email: string) => {
|
||||
analytics.trackAPI('api_users_send_password_reset');
|
||||
@@ -117,7 +117,7 @@ const ClientUsers = (superclass: any) => class extends superclass {
|
||||
`${this.getUsersRoute()}/password/reset/send`,
|
||||
{method: 'post', body: JSON.stringify({email})},
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
setDefaultProfileImage = async (userId: string) => {
|
||||
analytics.trackAPI('api_users_set_default_profile_picture');
|
||||
|
||||
@@ -137,7 +137,7 @@ export default class AtMention extends React.PureComponent {
|
||||
this.setState({
|
||||
user,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const {isSearchResult, mentionName, mentionStyle, onPostPress, teammateNameDisplay, textStyle, mentionKeys, theme} = this.props;
|
||||
|
||||
@@ -146,7 +146,7 @@ export default class AttachmentButton extends PureComponent {
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
attachPhotoFromCamera = () => {
|
||||
return this.attachFileFromCamera('camera', 'photo');
|
||||
@@ -274,7 +274,7 @@ export default class AttachmentButton extends PureComponent {
|
||||
}
|
||||
|
||||
return files;
|
||||
};
|
||||
}
|
||||
|
||||
hasPhotoPermission = async (source, mediaType = '') => {
|
||||
if (Platform.OS === 'ios') {
|
||||
|
||||
@@ -142,7 +142,7 @@ export default class Autocomplete extends PureComponent {
|
||||
|
||||
handleAppCommandCountChange = (appCommandCount) => {
|
||||
this.setState({appCommandCount});
|
||||
};
|
||||
}
|
||||
|
||||
handleCommandCountChange = (commandCount) => {
|
||||
this.setState({commandCount});
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import moment from 'moment-timezone';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, {PureComponent} from 'react';
|
||||
import {intlShape} from 'react-intl';
|
||||
@@ -37,8 +36,6 @@ export default class DateSuggestion extends PureComponent {
|
||||
|
||||
this.state = {
|
||||
mentionComplete: false,
|
||||
initialVisibleMonth: (new Date()).toISOString(),
|
||||
futureScrollRange: 0,
|
||||
active: false,
|
||||
sections: [],
|
||||
};
|
||||
@@ -77,7 +74,7 @@ export default class DateSuggestion extends PureComponent {
|
||||
|
||||
setCalendarActive = (active) => {
|
||||
this.setState({active});
|
||||
};
|
||||
}
|
||||
|
||||
completeMention = (day) => {
|
||||
const mention = day.dateString;
|
||||
@@ -101,11 +98,7 @@ export default class DateSuggestion extends PureComponent {
|
||||
|
||||
onChangeText(completedDraft, true);
|
||||
this.props.onResultCountChange(1);
|
||||
|
||||
const initialVisibleMonth = (new Date(day.dateString)).toISOString();
|
||||
const futureScrollRange = moment().diff(moment(day.dateString), 'month');
|
||||
|
||||
this.setState({mentionComplete: true, initialVisibleMonth, futureScrollRange});
|
||||
this.setState({mentionComplete: true});
|
||||
};
|
||||
|
||||
resetComponent() {
|
||||
@@ -165,10 +158,10 @@ export default class DateSuggestion extends PureComponent {
|
||||
{Boolean(calendarWidth) &&
|
||||
<CalendarList
|
||||
testID='autocomplete.date_suggestion'
|
||||
current={this.state.initialVisibleMonth}
|
||||
current={currentDate}
|
||||
maxDate={currentDate}
|
||||
pastScrollRange={24}
|
||||
futureScrollRange={this.state.futureScrollRange}
|
||||
futureScrollRange={0}
|
||||
scrollingEnabled={true}
|
||||
calendarWidth={calendarWidth}
|
||||
pagingEnabled={true}
|
||||
|
||||
@@ -140,7 +140,7 @@ export default class EmojiSuggestion extends PureComponent {
|
||||
});
|
||||
};
|
||||
|
||||
getItemLayout = ({index}) => ({length: 40, offset: 40 * index, index});
|
||||
getItemLayout = ({index}) => ({length: 40, offset: 40 * index, index})
|
||||
|
||||
keyExtractor = (item) => item;
|
||||
|
||||
@@ -174,7 +174,7 @@ export default class EmojiSuggestion extends PureComponent {
|
||||
});
|
||||
|
||||
this.props.onResultCountChange(0);
|
||||
};
|
||||
}
|
||||
|
||||
searchEmojis = (searchTerm) => {
|
||||
const {emojis} = this.props;
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
} from './app_command_parser';
|
||||
import {
|
||||
AppCallResponseTypes,
|
||||
AppCallTypes,
|
||||
AutocompleteSuggestion,
|
||||
} from './app_command_parser_dependencies';
|
||||
import {
|
||||
@@ -36,7 +37,6 @@ describe('AppCommandParser', () => {
|
||||
bindings,
|
||||
threadBindings: bindings,
|
||||
threadBindingsForms: {},
|
||||
pluginEnabled: true,
|
||||
},
|
||||
},
|
||||
} as any;
|
||||
@@ -243,7 +243,7 @@ describe('AppCommandParser', () => {
|
||||
autocomplete: {verify: (parsed: ParsedCommand): void => {
|
||||
expect(parsed.state).toBe(ParseState.EndValue);
|
||||
expect(parsed.binding?.label).toBe('create');
|
||||
expect(parsed.resolvedForm?.submit?.path).toBe('/create-issue');
|
||||
expect(parsed.form?.call?.path).toBe('/create-issue');
|
||||
expect(parsed.incomplete).toBe('epic2');
|
||||
expect(parsed.incompleteStart).toBe(75);
|
||||
expect(parsed.values?.project).toBe('P 1');
|
||||
@@ -254,7 +254,7 @@ describe('AppCommandParser', () => {
|
||||
submit: {verify: (parsed: ParsedCommand): void => {
|
||||
expect(parsed.state).toBe(ParseState.EndValue);
|
||||
expect(parsed.binding?.label).toBe('create');
|
||||
expect(parsed.resolvedForm?.submit?.path).toBe('/create-issue');
|
||||
expect(parsed.form?.call?.path).toBe('/create-issue');
|
||||
expect(parsed.values?.project).toBe('P 1');
|
||||
expect(parsed.values?.epic).toBe('epic2');
|
||||
expect(parsed.values?.summary).toBe('SUM MA RY');
|
||||
@@ -267,7 +267,7 @@ describe('AppCommandParser', () => {
|
||||
autocomplete: {verify: (parsed: ParsedCommand): void => {
|
||||
expect(parsed.state).toBe(ParseState.EndValue);
|
||||
expect(parsed.binding?.label).toBe('create');
|
||||
expect(parsed.resolvedForm?.submit?.path).toBe('/create-issue');
|
||||
expect(parsed.form?.call?.path).toBe('/create-issue');
|
||||
expect(parsed.incomplete).toBe('epic2');
|
||||
expect(parsed.incompleteStart).toBe(75);
|
||||
expect(parsed.values?.project).toBe('P 1');
|
||||
@@ -278,7 +278,7 @@ describe('AppCommandParser', () => {
|
||||
submit: {verify: (parsed: ParsedCommand): void => {
|
||||
expect(parsed.state).toBe(ParseState.EndValue);
|
||||
expect(parsed.binding?.label).toBe('create');
|
||||
expect(parsed.resolvedForm?.submit?.path).toBe('/create-issue');
|
||||
expect(parsed.form?.call?.path).toBe('/create-issue');
|
||||
expect(parsed.values?.project).toBe('P 1');
|
||||
expect(parsed.values?.epic).toBe('epic2');
|
||||
expect(parsed.values?.summary).toBe('SUM MA RY');
|
||||
@@ -291,7 +291,7 @@ describe('AppCommandParser', () => {
|
||||
autocomplete: {verify: (parsed: ParsedCommand): void => {
|
||||
expect(parsed.state).toBe(ParseState.EndValue);
|
||||
expect(parsed.binding?.label).toBe('create');
|
||||
expect(parsed.resolvedForm?.submit?.path).toBe('/create-issue');
|
||||
expect(parsed.form?.call?.path).toBe('/create-issue');
|
||||
expect(parsed.incomplete).toBe('M');
|
||||
expect(parsed.incompleteStart).toBe(65);
|
||||
expect(parsed.values?.project).toBe('KT');
|
||||
@@ -300,7 +300,7 @@ describe('AppCommandParser', () => {
|
||||
submit: {verify: (parsed: ParsedCommand): void => {
|
||||
expect(parsed.state).toBe(ParseState.EndValue);
|
||||
expect(parsed.binding?.label).toBe('create');
|
||||
expect(parsed.resolvedForm?.submit?.path).toBe('/create-issue');
|
||||
expect(parsed.form?.call?.path).toBe('/create-issue');
|
||||
expect(parsed.values?.epic).toBe('M');
|
||||
}},
|
||||
},
|
||||
@@ -310,7 +310,7 @@ describe('AppCommandParser', () => {
|
||||
autocomplete: {verify: (parsed: ParsedCommand): void => {
|
||||
expect(parsed.state).toBe(ParseState.EndValue);
|
||||
expect(parsed.binding?.label).toBe('view');
|
||||
expect(parsed.resolvedForm?.submit?.path).toBe('/view-issue');
|
||||
expect(parsed.form?.call?.path).toBe('/view-issue');
|
||||
expect(parsed.incomplete).toBe('MM-123');
|
||||
expect(parsed.incompleteStart).toBe(33);
|
||||
expect(parsed.values?.project).toBe('P 1');
|
||||
@@ -319,7 +319,7 @@ describe('AppCommandParser', () => {
|
||||
submit: {verify: (parsed: ParsedCommand): void => {
|
||||
expect(parsed.state).toBe(ParseState.EndValue);
|
||||
expect(parsed.binding?.label).toBe('view');
|
||||
expect(parsed.resolvedForm?.submit?.path).toBe('/view-issue');
|
||||
expect(parsed.form?.call?.path).toBe('/view-issue');
|
||||
expect(parsed.values?.project).toBe('P 1');
|
||||
expect(parsed.values?.issue).toBe('MM-123');
|
||||
}},
|
||||
@@ -330,7 +330,7 @@ describe('AppCommandParser', () => {
|
||||
submit: {verify: (parsed: ParsedCommand): void => {
|
||||
expect(parsed.state).toBe(ParseState.StartParameter);
|
||||
expect(parsed.binding?.label).toBe('view');
|
||||
expect(parsed.resolvedForm?.submit?.path).toBe('/view-issue');
|
||||
expect(parsed.form?.call?.path).toBe('/view-issue');
|
||||
expect(parsed.incomplete).toBe('');
|
||||
expect(parsed.incompleteStart).toBe(17);
|
||||
expect(parsed.values).toEqual({});
|
||||
@@ -342,14 +342,14 @@ describe('AppCommandParser', () => {
|
||||
autocomplete: {verify: (parsed: ParsedCommand): void => {
|
||||
expect(parsed.state).toBe(ParseState.FlagValueSeparator);
|
||||
expect(parsed.binding?.label).toBe('create');
|
||||
expect(parsed.resolvedForm?.submit?.path).toBe('/create-issue');
|
||||
expect(parsed.form?.call?.path).toBe('/create-issue');
|
||||
expect(parsed.incomplete).toBe('');
|
||||
expect(parsed.values).toEqual({});
|
||||
}},
|
||||
submit: {verify: (parsed: ParsedCommand): void => {
|
||||
expect(parsed.state).toBe(ParseState.EndValue);
|
||||
expect(parsed.binding?.label).toBe('create');
|
||||
expect(parsed.resolvedForm?.submit?.path).toBe('/create-issue');
|
||||
expect(parsed.form?.call?.path).toBe('/create-issue');
|
||||
expect(parsed.incomplete).toBe('');
|
||||
expect(parsed.values).toEqual({
|
||||
summary: '',
|
||||
@@ -362,7 +362,7 @@ describe('AppCommandParser', () => {
|
||||
autocomplete: {verify: (parsed: ParsedCommand): void => {
|
||||
expect(parsed.state).toBe(ParseState.TickValue);
|
||||
expect(parsed.binding?.label).toBe('view');
|
||||
expect(parsed.resolvedForm?.submit?.path).toBe('/view-issue');
|
||||
expect(parsed.form?.call?.path).toBe('/view-issue');
|
||||
expect(parsed.incomplete).toBe('P 1');
|
||||
expect(parsed.incompleteStart).toBe(27);
|
||||
expect(parsed.values?.project).toBe(undefined);
|
||||
@@ -376,7 +376,7 @@ describe('AppCommandParser', () => {
|
||||
autocomplete: {verify: (parsed: ParsedCommand): void => {
|
||||
expect(parsed.state).toBe(ParseState.QuotedValue);
|
||||
expect(parsed.binding?.label).toBe('view');
|
||||
expect(parsed.resolvedForm?.submit?.path).toBe('/view-issue');
|
||||
expect(parsed.form?.call?.path).toBe('/view-issue');
|
||||
expect(parsed.incomplete).toBe('P 1');
|
||||
expect(parsed.incompleteStart).toBe(27);
|
||||
expect(parsed.values?.project).toBe(undefined);
|
||||
@@ -390,7 +390,7 @@ describe('AppCommandParser', () => {
|
||||
autocomplete: {verify: (parsed: ParsedCommand): void => {
|
||||
expect(parsed.state).toBe(ParseState.EndQuotedValue);
|
||||
expect(parsed.binding?.label).toBe('view');
|
||||
expect(parsed.resolvedForm?.submit?.path).toBe('/view-issue');
|
||||
expect(parsed.form?.call?.path).toBe('/view-issue');
|
||||
expect(parsed.incomplete).toBe('P 1');
|
||||
expect(parsed.incompleteStart).toBe(27);
|
||||
expect(parsed.values?.project).toBe(undefined);
|
||||
@@ -399,7 +399,7 @@ describe('AppCommandParser', () => {
|
||||
submit: {verify: (parsed: ParsedCommand): void => {
|
||||
expect(parsed.state).toBe(ParseState.EndQuotedValue);
|
||||
expect(parsed.binding?.label).toBe('view');
|
||||
expect(parsed.resolvedForm?.submit?.path).toBe('/view-issue');
|
||||
expect(parsed.form?.call?.path).toBe('/view-issue');
|
||||
expect(parsed.values?.project).toBe('P 1');
|
||||
expect(parsed.values?.issue).toBe(undefined);
|
||||
}},
|
||||
@@ -923,7 +923,7 @@ describe('AppCommandParser', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('composeCommandSubmitCall', () => {
|
||||
describe('composeCallFromCommand', () => {
|
||||
const base = {
|
||||
context: {
|
||||
app_id: 'jira',
|
||||
@@ -939,9 +939,13 @@ describe('AppCommandParser', () => {
|
||||
const cmd = '/jira issue create';
|
||||
const values = {};
|
||||
|
||||
const {creq} = await parser.composeCommandSubmitCall(cmd);
|
||||
expect(creq).toEqual({
|
||||
const {call} = await parser.composeCallFromCommand(cmd);
|
||||
expect(call).toEqual({
|
||||
...base,
|
||||
context: {
|
||||
...base.context,
|
||||
location: '/command/jira/issue/create',
|
||||
},
|
||||
raw_command: cmd,
|
||||
expand: {},
|
||||
query: undefined,
|
||||
@@ -958,13 +962,17 @@ describe('AppCommandParser', () => {
|
||||
label: 'Dylan Epic',
|
||||
value: 'epic1',
|
||||
},
|
||||
verbose: true,
|
||||
verbose: 'true',
|
||||
project: '',
|
||||
};
|
||||
|
||||
const {creq} = await parser.composeCommandSubmitCall(cmd);
|
||||
expect(creq).toEqual({
|
||||
const {call} = await parser.composeCallFromCommand(cmd);
|
||||
expect(call).toEqual({
|
||||
...base,
|
||||
context: {
|
||||
...base.context,
|
||||
location: '/command/jira/issue/create',
|
||||
},
|
||||
expand: {},
|
||||
selected_field: undefined,
|
||||
query: undefined,
|
||||
@@ -1001,7 +1009,7 @@ describe('AppCommandParser', () => {
|
||||
team_id: 'team_id',
|
||||
},
|
||||
expand: {},
|
||||
path: '/create-issue-lookup',
|
||||
path: '/create-issue',
|
||||
query: 'special',
|
||||
raw_command: '/jira issue create --summary "The summary" --epic epic1 --project special',
|
||||
selected_field: 'project',
|
||||
@@ -1012,7 +1020,7 @@ describe('AppCommandParser', () => {
|
||||
value: 'epic1',
|
||||
},
|
||||
},
|
||||
}, false);
|
||||
}, AppCallTypes.LOOKUP);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,22 +3,18 @@
|
||||
|
||||
import {intlShape} from 'react-intl';
|
||||
|
||||
export {doAppFetchForm, doAppLookup} from '@actions/apps';
|
||||
|
||||
export {getChannelByNameAndTeamName, getChannel, autocompleteChannels} from '@mm-redux/actions/channels';
|
||||
import {getUserByUsername, getUser, autocompleteUsers, autocompleteUsersInChannel} from '@mm-redux/actions/users';
|
||||
export {AppsTypes} from '@mm-redux/action_types';
|
||||
export {makeAppBindingsSelector, makeRHSAppBindingSelector, getAppCommandForm, getAppRHSCommandForm} from '@mm-redux/selectors/entities/apps';
|
||||
export {getChannel as selectChannel, getCurrentChannel, getChannelByName as selectChannelByName} from '@mm-redux/selectors/entities/channels';
|
||||
export {getPost} from '@mm-redux/selectors/entities/posts';
|
||||
import {getUserByUsername, getUser, autocompleteUsers} from '@mm-redux/actions/users';
|
||||
import {getCurrentTeamId, getCurrentTeam} from '@mm-redux/selectors/entities/teams';
|
||||
export {getUserByUsername as selectUserByUsername, getUser as selectUser} from '@mm-redux/selectors/entities/users';
|
||||
import {DispatchFunc} from '@mm-redux/types/actions';
|
||||
import ReduxStore from '@store/store';
|
||||
import {
|
||||
ActionFunc,
|
||||
DispatchFunc,
|
||||
} from '@mm-redux/types/actions';
|
||||
import Store from '@store/store';
|
||||
|
||||
import type {ParsedCommand} from './app_command_parser';
|
||||
import type {AutocompleteSuggestion} from '@mm-redux/types/integrations';
|
||||
|
||||
export type {
|
||||
AppCall,
|
||||
AppCallRequest,
|
||||
AppBinding,
|
||||
AppField,
|
||||
@@ -33,22 +29,36 @@ export type {
|
||||
AutocompleteUserSelect,
|
||||
AutocompleteChannelSelect,
|
||||
AppLookupResponse,
|
||||
UserAutocomplete,
|
||||
} from '@mm-redux/types/apps';
|
||||
export type {Channel} from '@mm-redux/types/channels';
|
||||
import type {AutocompleteSuggestion} from '@mm-redux/types/integrations';
|
||||
import type {GlobalState} from '@mm-redux/types/store';
|
||||
|
||||
export type {
|
||||
DispatchFunc,
|
||||
GlobalState,
|
||||
};
|
||||
DoAppCallResult,
|
||||
} from 'types/actions/apps';
|
||||
|
||||
export {AppsTypes} from '@mm-redux/action_types';
|
||||
|
||||
export type {AutocompleteSuggestion};
|
||||
|
||||
export type {DoAppCallResult} from 'types/actions/apps';
|
||||
export type {
|
||||
Channel,
|
||||
} from '@mm-redux/types/channels';
|
||||
|
||||
export type {
|
||||
GlobalState,
|
||||
} from '@mm-redux/types/store';
|
||||
|
||||
export type {
|
||||
DispatchFunc,
|
||||
};
|
||||
|
||||
export type {
|
||||
UserProfile,
|
||||
} from '@mm-redux/types/users';
|
||||
|
||||
export {
|
||||
AppBindingLocations,
|
||||
AppCallTypes,
|
||||
AppFieldTypes,
|
||||
AppCallResponseTypes,
|
||||
COMMAND_SUGGESTION_ERROR,
|
||||
@@ -56,37 +66,43 @@ export {
|
||||
COMMAND_SUGGESTION_USER,
|
||||
} from '@mm-redux/constants/apps';
|
||||
|
||||
export {makeAppBindingsSelector, makeRHSAppBindingSelector, getAppCommandForm, getAppRHSCommandForm} from '@mm-redux/selectors/entities/apps';
|
||||
|
||||
export {getPost} from '@mm-redux/selectors/entities/posts';
|
||||
export {getChannel as selectChannel, getCurrentChannel, getChannelByName as selectChannelByName} from '@mm-redux/selectors/entities/channels';
|
||||
|
||||
export {
|
||||
getCurrentTeamId,
|
||||
getCurrentTeam,
|
||||
};
|
||||
|
||||
export {getUserByUsername as selectUserByUsername, getUser as selectUser} from '@mm-redux/selectors/entities/users';
|
||||
|
||||
export {
|
||||
getUserByUsername,
|
||||
getUser,
|
||||
autocompleteUsers,
|
||||
autocompleteUsersInChannel,
|
||||
};
|
||||
|
||||
export {getChannelByNameAndTeamName, getChannel, autocompleteChannels} from '@mm-redux/actions/channels';
|
||||
|
||||
export {doAppCall} from '@actions/apps';
|
||||
export {
|
||||
createCallRequest,
|
||||
filterEmptyOptions,
|
||||
} from '@utils/apps';
|
||||
|
||||
export interface Store {
|
||||
dispatch: DispatchFunc;
|
||||
getState: () => GlobalState;
|
||||
}
|
||||
export const getStore = () => Store.redux;
|
||||
|
||||
export const getStore = () => ReduxStore.redux as Store;
|
||||
export const autocompleteUsersInChannel = (prefix: string, channelID: string): ActionFunc => {
|
||||
return async (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const currentTeamID = getCurrentTeamId(state);
|
||||
return dispatch(autocompleteUsers(prefix, currentTeamID, channelID));
|
||||
};
|
||||
};
|
||||
|
||||
export const EXECUTE_CURRENT_COMMAND_ITEM_ID = '_execute_current_command';
|
||||
export const OPEN_COMMAND_IN_MODAL_ITEM_ID = '_open_command_in_modal';
|
||||
|
||||
export const getOpenInModalSuggestion = (_: ParsedCommand): AutocompleteSuggestion | null => { // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
// Not supported on mobile yet
|
||||
return null;
|
||||
};
|
||||
|
||||
export type ExtendedAutocompleteSuggestion = AutocompleteSuggestion & {
|
||||
type?: string;
|
||||
|
||||
@@ -27,6 +27,7 @@ export const reduxTestState = {
|
||||
display_name: 'Default',
|
||||
delete_at: 0,
|
||||
type: 'O',
|
||||
total_msg_count: 10,
|
||||
team_id: 'team_id',
|
||||
},
|
||||
current_user_id__existingId: {
|
||||
@@ -35,16 +36,13 @@ export const reduxTestState = {
|
||||
display_name: 'Default',
|
||||
delete_at: 0,
|
||||
type: '0',
|
||||
total_msg_count: 0,
|
||||
team_id: 'team_id',
|
||||
},
|
||||
},
|
||||
channelsInTeam: {
|
||||
'team-id': ['current_channel_id'],
|
||||
},
|
||||
messageCounts: {
|
||||
current_channel_id: {total: 10},
|
||||
current_user_id__existingId: {total: 0},
|
||||
},
|
||||
},
|
||||
teams: {
|
||||
currentTeamId: 'team-id',
|
||||
@@ -105,7 +103,7 @@ export const viewCommand: AppBinding = {
|
||||
location: '/command/jira/issue/view',
|
||||
description: 'View details of a Jira issue',
|
||||
form: {
|
||||
submit: {
|
||||
call: {
|
||||
path: '/view-issue',
|
||||
},
|
||||
fields: [
|
||||
@@ -116,9 +114,6 @@ export const viewCommand: AppBinding = {
|
||||
type: AppFieldTypes.DYNAMIC_SELECT,
|
||||
hint: 'The Jira project hint',
|
||||
is_required: true,
|
||||
lookup: {
|
||||
path: '/view-issue-lookup',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'issue',
|
||||
@@ -140,7 +135,7 @@ export const createCommand: AppBinding = {
|
||||
icon: 'Create icon',
|
||||
hint: 'Create hint',
|
||||
form: {
|
||||
submit: {
|
||||
call: {
|
||||
path: '/create-issue',
|
||||
},
|
||||
fields: [
|
||||
@@ -150,9 +145,6 @@ export const createCommand: AppBinding = {
|
||||
description: 'The Jira project description',
|
||||
type: AppFieldTypes.DYNAMIC_SELECT,
|
||||
hint: 'The Jira project hint',
|
||||
lookup: {
|
||||
path: '/create-issue-lookup',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'summary',
|
||||
@@ -197,7 +189,7 @@ export const restCommand: AppBinding = {
|
||||
icon: 'rest icon',
|
||||
hint: 'rest hint',
|
||||
form: {
|
||||
submit: {
|
||||
call: {
|
||||
path: '/create-issue',
|
||||
},
|
||||
fields: [
|
||||
@@ -260,9 +252,6 @@ export const testBindings: AppBinding[] = [
|
||||
label: 'sub1',
|
||||
description: 'Some Description',
|
||||
form: {
|
||||
submit: {
|
||||
path: '/submit_other',
|
||||
},
|
||||
fields: [{
|
||||
name: 'fieldname',
|
||||
label: 'fieldlabel',
|
||||
|
||||
@@ -32,7 +32,6 @@ const makeStore = async (bindings: AppBinding[]) => {
|
||||
bindingsForms: {},
|
||||
threadBindings: bindings,
|
||||
threadBindingsForms: {},
|
||||
pluginEnabled: true,
|
||||
},
|
||||
},
|
||||
} as any;
|
||||
|
||||
@@ -109,13 +109,13 @@ export default class AppSlashSuggestion extends PureComponent<Props, State> {
|
||||
isAppCommand = (pretext: string, channelID: string, teamID = '', rootID?: string) => {
|
||||
this.appCommandParser.setChannelContext(channelID, teamID, rootID);
|
||||
return this.appCommandParser.isAppCommand(pretext);
|
||||
};
|
||||
}
|
||||
|
||||
fetchAndShowAppCommandSuggestions = async (pretext: string, channelID: string, teamID = '', rootID?: string) => {
|
||||
this.appCommandParser.setChannelContext(channelID, teamID, rootID);
|
||||
const suggestions = await this.appCommandParser.getSuggestions(pretext);
|
||||
this.updateSuggestions(suggestions);
|
||||
};
|
||||
}
|
||||
|
||||
updateSuggestions = (matches: ExtendedAutocompleteSuggestion[]) => {
|
||||
this.setState({
|
||||
@@ -123,7 +123,7 @@ export default class AppSlashSuggestion extends PureComponent<Props, State> {
|
||||
dataSource: matches,
|
||||
});
|
||||
this.props.onResultCountChange(matches.length);
|
||||
};
|
||||
}
|
||||
|
||||
completeSuggestion = (command: string) => {
|
||||
const {onChangeText} = this.props;
|
||||
@@ -157,13 +157,13 @@ export default class AppSlashSuggestion extends PureComponent<Props, State> {
|
||||
return () => {
|
||||
this.completeSuggestion(base);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
completeChannelMention = (base: string): (channelName: string) => void => {
|
||||
return () => {
|
||||
this.completeSuggestion(base);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
keyExtractor = (item: ExtendedAutocompleteSuggestion): string => item.Suggestion + item.type + item.item;
|
||||
|
||||
@@ -198,7 +198,7 @@ export default class AppSlashSuggestion extends PureComponent<Props, State> {
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const {maxListHeight, theme, nestedScrollEnabled} = this.props;
|
||||
|
||||
@@ -28,10 +28,7 @@ const makeStore = async (bindings: AppBinding[]) => {
|
||||
...reduxTestState,
|
||||
entities: {
|
||||
...reduxTestState.entities,
|
||||
apps: {
|
||||
bindings,
|
||||
pluginEnabled: true,
|
||||
},
|
||||
apps: {bindings},
|
||||
},
|
||||
} as any;
|
||||
const testStore = await mockStore(initialState);
|
||||
|
||||
@@ -144,13 +144,13 @@ export default class SlashSuggestion extends PureComponent<Props, State> {
|
||||
});
|
||||
|
||||
this.updateSuggestions(matches);
|
||||
};
|
||||
}
|
||||
|
||||
getAppBaseCommandSuggestions = (pretext: string, channelID: string, teamID = '', rootID?: string): AutocompleteSuggestion[] => {
|
||||
this.appCommandParser.setChannelContext(channelID, teamID, rootID);
|
||||
const suggestions = this.appCommandParser.getSuggestionsBase(pretext);
|
||||
return suggestions;
|
||||
};
|
||||
}
|
||||
|
||||
updateSuggestions = (matches: AutocompleteSuggestion[]) => {
|
||||
this.setState({
|
||||
@@ -158,7 +158,7 @@ export default class SlashSuggestion extends PureComponent<Props, State> {
|
||||
dataSource: matches,
|
||||
});
|
||||
this.props.onResultCountChange(matches.length);
|
||||
};
|
||||
}
|
||||
|
||||
filterCommands = (matchTerm: string, commands: Command[]): AutocompleteSuggestion[] => {
|
||||
const data = commands.filter((command) => {
|
||||
@@ -179,11 +179,11 @@ export default class SlashSuggestion extends PureComponent<Props, State> {
|
||||
IconData: item.icon_url || item.autocomplete_icon_data || '',
|
||||
};
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
contains = (matches: AutocompleteSuggestion[], complete: string): boolean => {
|
||||
return matches.findIndex((match) => match.Complete === complete) !== -1;
|
||||
};
|
||||
}
|
||||
|
||||
completeSuggestion = (command: string) => {
|
||||
const {onChangeText} = this.props;
|
||||
@@ -219,7 +219,7 @@ export default class SlashSuggestion extends PureComponent<Props, State> {
|
||||
complete={item.Complete}
|
||||
icon={item.IconData}
|
||||
/>
|
||||
);
|
||||
)
|
||||
|
||||
render() {
|
||||
const {maxListHeight, theme, nestedScrollEnabled} = this.props;
|
||||
|
||||
@@ -32,13 +32,12 @@ type Props = {
|
||||
placeholder?: string;
|
||||
dataSource?: string;
|
||||
options?: DialogOption[];
|
||||
selected?: DialogOption | DialogOption[] | null;
|
||||
selected?: DialogOption | DialogOption[];
|
||||
optional?: boolean;
|
||||
showRequiredAsterisk?: boolean;
|
||||
teammateNameDisplay?: string;
|
||||
theme: Theme;
|
||||
onSelected?: ((item: DialogOption) => void) | ((item: DialogOption[]) => void);
|
||||
onClear?: () => void;
|
||||
helpText?: string;
|
||||
errorText?: string;
|
||||
roundedBorders?: boolean;
|
||||
@@ -48,7 +47,7 @@ type Props = {
|
||||
|
||||
type State = {
|
||||
selectedText: string;
|
||||
selected?: DialogOption | DialogOption[] | null;
|
||||
selected?: DialogOption | DialogOption[];
|
||||
}
|
||||
|
||||
export default class AutocompleteSelector extends PureComponent<Props, State> {
|
||||
@@ -71,15 +70,7 @@ export default class AutocompleteSelector extends PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(props: Props, state: State) {
|
||||
if (props.selected === state.selected) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!props.selected) {
|
||||
if (state.selected) {
|
||||
return {selected: props.selected};
|
||||
}
|
||||
|
||||
if (!props.selected || props.selected === state.selected) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -108,11 +99,6 @@ export default class AutocompleteSelector extends PureComponent<Props, State> {
|
||||
};
|
||||
}
|
||||
|
||||
handleClear = () => {
|
||||
this.setState({selectedText: ''});
|
||||
this.props.onClear?.();
|
||||
};
|
||||
|
||||
handleSelect = (selected: Selection) => {
|
||||
if (!selected) {
|
||||
return;
|
||||
@@ -244,14 +230,14 @@ export default class AutocompleteSelector extends PureComponent<Props, State> {
|
||||
showRequiredAsterisk,
|
||||
roundedBorders,
|
||||
disabled,
|
||||
selected,
|
||||
onClear,
|
||||
} = this.props;
|
||||
const {selectedText} = this.state;
|
||||
const style = getStyleSheet(theme);
|
||||
const textStyles = getMarkdownTextStyles(theme);
|
||||
const blockStyles = getMarkdownBlockStyles(theme);
|
||||
|
||||
const chevron = Platform.select({ios: 'chevron-right', default: 'chevron-down'});
|
||||
|
||||
let text = placeholder || intl.formatMessage({id: 'mobile.action_menu.select', defaultMessage: 'Select an option'});
|
||||
let selectedStyle = style.dropdownPlaceholder;
|
||||
|
||||
@@ -340,21 +326,11 @@ export default class AutocompleteSelector extends PureComponent<Props, State> {
|
||||
>
|
||||
{text}
|
||||
</Text>
|
||||
{!disabled && onClear && selected && (
|
||||
<TouchableWithFeedback
|
||||
type={'opacity'}
|
||||
onPress={this.handleClear}
|
||||
disabled={disabled}
|
||||
style={style.clearx}
|
||||
hitSlop={clearXHitSlop}
|
||||
>
|
||||
<CompassIcon
|
||||
name='close-circle'
|
||||
color={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
size={20}
|
||||
/>
|
||||
</TouchableWithFeedback>
|
||||
)}
|
||||
<CompassIcon
|
||||
name={chevron}
|
||||
color={changeOpacity(theme.centerChannelColor, 0.32)}
|
||||
style={style.icon}
|
||||
/>
|
||||
</View>
|
||||
</TouchableWithFeedback>
|
||||
{helpTextContent}
|
||||
@@ -390,21 +366,18 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
dropdownPlaceholder: {
|
||||
top: 3,
|
||||
marginLeft: 5,
|
||||
paddingRight: 55,
|
||||
color: changeOpacity(theme.centerChannelColor, 0.5),
|
||||
},
|
||||
dropdownSelected: {
|
||||
top: 3,
|
||||
marginLeft: 5,
|
||||
paddingRight: 55,
|
||||
color: theme.centerChannelColor,
|
||||
},
|
||||
clearx: {
|
||||
icon: {
|
||||
position: 'absolute',
|
||||
top: 1,
|
||||
right: 5,
|
||||
padding: 8,
|
||||
marginRight: 7,
|
||||
top: 6,
|
||||
right: 12,
|
||||
fontSize: 28,
|
||||
},
|
||||
labelContainer: {
|
||||
flexDirection: 'row',
|
||||
@@ -446,10 +419,3 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const clearXHitSlop = {
|
||||
left: 30,
|
||||
right: 20,
|
||||
top: 20,
|
||||
bottom: 20,
|
||||
};
|
||||
|
||||
27
app/components/avatars/avatars.stories.tsx
Normal file
27
app/components/avatars/avatars.stories.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withKnobs, number, select} from '@storybook/addon-knobs';
|
||||
import {storiesOf} from '@storybook/react-native';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import {Preferences} from '@mm-redux/constants';
|
||||
import {getProfiles} from '@mm-redux/selectors/entities/users';
|
||||
import {UserProfile} from '@mm-redux/types/users';
|
||||
import Store from '@store/store';
|
||||
|
||||
import Avatars from './avatars';
|
||||
|
||||
const state = Store.redux?.getState();
|
||||
const users = getProfiles(state, {});
|
||||
const userIds = users.map((user:UserProfile) => user.id);
|
||||
|
||||
storiesOf('Avatars', module).
|
||||
addDecorator(withKnobs).
|
||||
add('Avatars', () => (
|
||||
<Avatars
|
||||
userIds={userIds.slice(0, number(`number of named participants (max ${userIds.length})`, userIds.length))}
|
||||
theme={select('theme', Preferences.THEMES, Preferences.THEMES.denim)}
|
||||
/>
|
||||
));
|
||||
@@ -83,7 +83,7 @@ export default class ChannelLink extends React.PureComponent {
|
||||
this.props.onChannelLinkPress(channel);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const channel = this.state.channel;
|
||||
|
||||
@@ -92,7 +92,7 @@ export default class ChannelLoader extends PureComponent {
|
||||
delay: 500,
|
||||
useNativeDriver: false,
|
||||
}).start();
|
||||
};
|
||||
}
|
||||
|
||||
buildSections({key, style, bg, color}) {
|
||||
return (
|
||||
@@ -128,7 +128,7 @@ export default class ChannelLoader extends PureComponent {
|
||||
const {height} = e.nativeEvent.layout;
|
||||
const maxRows = calculateMaxRows(height);
|
||||
this.setState({maxRows});
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
|
||||
@@ -21,7 +21,6 @@ import FormattedText from '@components/formatted_text';
|
||||
import Loading from '@components/loading';
|
||||
import StatusBar from '@components/status_bar';
|
||||
import TextInputWithLocalizedPlaceholder from '@components/text_input_with_localized_placeholder';
|
||||
import {ViewTypes} from '@constants';
|
||||
import DEVICE from '@constants/device';
|
||||
import {General} from '@mm-redux/constants';
|
||||
import {t} from '@utils/i18n';
|
||||
@@ -131,7 +130,7 @@ export default class EditChannelInfo extends PureComponent {
|
||||
return;
|
||||
}
|
||||
|
||||
const displayNameExists = displayName && displayName.length >= ViewTypes.MIN_CHANNELNAME_LENGTH;
|
||||
const displayNameExists = displayName && displayName.length >= 2;
|
||||
this.props.enableRightButton(displayNameExists);
|
||||
};
|
||||
|
||||
@@ -164,7 +163,7 @@ export default class EditChannelInfo extends PureComponent {
|
||||
|
||||
onHeaderLayout = ({nativeEvent}) => {
|
||||
this.setState({headerPosition: nativeEvent.layout.y});
|
||||
};
|
||||
}
|
||||
|
||||
onKeyboardDidShow = () => {
|
||||
this.setState({keyboardVisible: true});
|
||||
@@ -173,15 +172,15 @@ export default class EditChannelInfo extends PureComponent {
|
||||
this.setState({headerHasFocus: false});
|
||||
this.scrollHeaderToTop();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
onKeyboardDidHide = () => {
|
||||
this.setState({keyboardVisible: false});
|
||||
};
|
||||
}
|
||||
|
||||
onKeyboardOffsetChanged = (keyboardPosition) => {
|
||||
this.setState({keyboardPosition});
|
||||
};
|
||||
}
|
||||
|
||||
onHeaderFocus = () => {
|
||||
if (this.state.keyboardVisible) {
|
||||
@@ -195,7 +194,7 @@ export default class EditChannelInfo extends PureComponent {
|
||||
if (this.scroll.current) {
|
||||
this.scroll.current.scrollTo({x: 0, y: this.state.headerPosition});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
|
||||
@@ -109,7 +109,7 @@ export default class EmojiPicker extends PureComponent {
|
||||
|
||||
setSearchBarRef = (ref) => {
|
||||
this.searchBarRef = ref;
|
||||
};
|
||||
}
|
||||
|
||||
setSectionListRef = (ref) => {
|
||||
this.sectionListRef = ref;
|
||||
@@ -121,7 +121,7 @@ export default class EmojiPicker extends PureComponent {
|
||||
const emojis = this.renderableEmojis(this.props.emojisBySection, this.props.deviceWidth);
|
||||
this.setState({emojis});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
renderableEmojis = (emojis, deviceWidth) => {
|
||||
const numberOfColumns = this.getNumberOfColumns(deviceWidth);
|
||||
@@ -501,7 +501,7 @@ export default class EmojiPicker extends PureComponent {
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const getStyleSheetFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
|
||||
@@ -18,7 +18,7 @@ export default class EmojiPickerRow extends Component {
|
||||
emojiSize: PropTypes.number.isRequired,
|
||||
items: PropTypes.array.isRequired,
|
||||
onEmojiPress: PropTypes.func.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps) {
|
||||
return !shallowEqual(this.props.items, nextProps.items);
|
||||
|
||||
@@ -62,7 +62,7 @@ export default class ErrorList extends PureComponent {
|
||||
clearErrors: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
errors: PropTypes.array.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
renderErrorsList() {
|
||||
const {errors} = this.props;
|
||||
|
||||
@@ -54,7 +54,7 @@ class FormattedMarkdownText extends React.PureComponent {
|
||||
|
||||
createParser = () => {
|
||||
return new Parser();
|
||||
};
|
||||
}
|
||||
|
||||
createRenderer = () => {
|
||||
return new Renderer({
|
||||
@@ -73,39 +73,39 @@ class FormattedMarkdownText extends React.PureComponent {
|
||||
atMention: this.renderAtMention,
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
computeTextStyle = (baseStyle, context) => {
|
||||
return concatStyles(baseStyle, context.map((type) => this.textStyles[type]));
|
||||
};
|
||||
}
|
||||
|
||||
renderText = ({context, literal}) => {
|
||||
const style = this.computeTextStyle(this.props.style || this.baseTextStyle, context);
|
||||
return <Text style={style}>{literal}</Text>;
|
||||
};
|
||||
}
|
||||
|
||||
renderCodeSpan = ({context, literal}) => {
|
||||
const style = this.computeTextStyle([this.baseTextStyle, this.textStyles.code], context);
|
||||
return <Text style={style}>{literal}</Text>;
|
||||
};
|
||||
}
|
||||
|
||||
renderLink = ({children, href}) => {
|
||||
const url = href[0] === TARGET_BLANK_URL_PREFIX ? href.substring(1, href.length) : href;
|
||||
return <MarkdownLink href={url}>{children}</MarkdownLink>;
|
||||
};
|
||||
}
|
||||
|
||||
renderBreak = () => {
|
||||
return <Text>{'\n'}</Text>;
|
||||
};
|
||||
}
|
||||
|
||||
renderParagraph = ({children}) => {
|
||||
return <Text>{children}</Text>;
|
||||
};
|
||||
}
|
||||
|
||||
renderHTML = (props) => {
|
||||
console.warn(`HTML used in FormattedMarkdownText component with id ${this.props.id}`); // eslint-disable-line no-console
|
||||
return this.renderText(props);
|
||||
};
|
||||
}
|
||||
|
||||
renderAtMention = ({context, mentionName}) => {
|
||||
const style = getStyleSheet(this.props.theme);
|
||||
@@ -118,7 +118,7 @@ class FormattedMarkdownText extends React.PureComponent {
|
||||
textStyle={[this.computeTextStyle(this.props.baseTextStyle, context), style.atMentionOpacity]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const {id, defaultMessage, values, theme} = this.props;
|
||||
|
||||
@@ -66,7 +66,7 @@ describe('Friendly Date', () => {
|
||||
expect(daysAgoMaxText.getByText('30 days ago')).toBeTruthy();
|
||||
|
||||
const monthsAgo = new Date();
|
||||
monthsAgo.setDate(monthsAgo.getDate() - 60);
|
||||
monthsAgo.setMonth(monthsAgo.getMonth() - 2);
|
||||
const monthsAgoText = renderWithIntl(
|
||||
<FriendlyDate value={monthsAgo}/>,
|
||||
);
|
||||
|
||||
@@ -37,7 +37,7 @@ export function getFriendlyDate(intl: typeof intlShape, inputDate: number | Date
|
||||
|
||||
// Message: Minutes Ago
|
||||
if (difference < DateTypes.SECONDS.HOUR) {
|
||||
const minutes = Math.floor(Math.round((10 * difference) / DateTypes.SECONDS.MINUTE) / 10);
|
||||
const minutes = Math.floor(difference / DateTypes.SECONDS.MINUTE);
|
||||
return intl.formatMessage({
|
||||
id: 'friendly_date.minsAgo',
|
||||
defaultMessage: '{count} {count, plural, one {min} other {mins}} ago',
|
||||
@@ -48,7 +48,7 @@ export function getFriendlyDate(intl: typeof intlShape, inputDate: number | Date
|
||||
|
||||
// Message: Hours Ago
|
||||
if (difference < DateTypes.SECONDS.DAY) {
|
||||
const hours = Math.floor(Math.round((10 * difference) / DateTypes.SECONDS.HOUR) / 10);
|
||||
const hours = Math.floor(difference / DateTypes.SECONDS.HOUR);
|
||||
return intl.formatMessage({
|
||||
id: 'friendly_date.hoursAgo',
|
||||
defaultMessage: '{count} {count, plural, one {hour} other {hours}} ago',
|
||||
@@ -67,7 +67,7 @@ export function getFriendlyDate(intl: typeof intlShape, inputDate: number | Date
|
||||
}
|
||||
const completedAMonth = today.getMonth() !== date.getMonth() && today.getDate() >= date.getDate();
|
||||
if (!completedAMonth) {
|
||||
const days = Math.floor(Math.round((10 * difference) / DateTypes.SECONDS.DAY) / 10) || 1;
|
||||
const days = Math.floor(difference / DateTypes.SECONDS.DAY) || 1;
|
||||
return intl.formatMessage({
|
||||
id: 'friendly_date.daysAgo',
|
||||
defaultMessage: '{count} {count, plural, one {day} other {days}} ago',
|
||||
@@ -83,7 +83,7 @@ export function getFriendlyDate(intl: typeof intlShape, inputDate: number | Date
|
||||
today.getMonth() >= date.getMonth() &&
|
||||
today.getDate() >= date.getDate();
|
||||
if (!completedAnYear) {
|
||||
const months = Math.floor(Math.round((10 * difference) / DateTypes.SECONDS.DAYS_30) / 10) || 1;
|
||||
const months = Math.floor(difference / DateTypes.SECONDS.DAYS_30) || 1;
|
||||
return intl.formatMessage({
|
||||
id: 'friendly_date.monthsAgo',
|
||||
defaultMessage: '{count} {count, plural, one {month} other {months}} ago',
|
||||
@@ -94,7 +94,7 @@ export function getFriendlyDate(intl: typeof intlShape, inputDate: number | Date
|
||||
}
|
||||
|
||||
// Message: Years Ago
|
||||
const years = Math.floor(Math.round((10 * difference) / DateTypes.SECONDS.DAYS_365) / 10) || 1;
|
||||
const years = Math.floor(difference / DateTypes.SECONDS.DAYS_365) || 1;
|
||||
return intl.formatMessage({
|
||||
id: 'friendly_date.yearsAgo',
|
||||
defaultMessage: '{count} {count, plural, one {year} other {years}} ago',
|
||||
|
||||
@@ -28,12 +28,12 @@ type Props = {
|
||||
markAllThreadsInTeamRead: (userId: $ID<UserProfile>, teamId: $ID<Team>) => void;
|
||||
selectPost: (postId: string) => void;
|
||||
};
|
||||
allThreadIds: Array<$ID<UserThread>>;
|
||||
allThreadIds: $ID<UserThread>[];
|
||||
intl: typeof intlShape;
|
||||
teamId: $ID<Team>;
|
||||
theme: Theme;
|
||||
threadCount: ThreadsState['counts'][$ID<Team>];
|
||||
unreadThreadIds: Array<$ID<UserThread>>;
|
||||
unreadThreadIds: $ID<UserThread>[];
|
||||
userId: $ID<UserProfile>;
|
||||
viewingUnreads: boolean;
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ export type Props = {
|
||||
onRefresh: () => void;
|
||||
testID: string;
|
||||
theme: Theme;
|
||||
threadIds: Array<$ID<UserThread>>;
|
||||
threadIds: $ID<UserThread>[];
|
||||
viewAllThreads: () => void;
|
||||
viewUnreadThreads: () => void;
|
||||
viewingUnreads: boolean;
|
||||
|
||||
@@ -92,13 +92,13 @@ export default class InteractiveDialogController extends PureComponent {
|
||||
};
|
||||
|
||||
showModal('InteractiveDialog', dialog.title, null, options);
|
||||
};
|
||||
}
|
||||
|
||||
handleCancel = (dialog, url) => {
|
||||
if (dialog.notify_on_cancel) {
|
||||
this.props.actions.submitInteractiveDialog({...dialog, url, cancelled: true});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return null;
|
||||
|
||||
18
app/components/loading.stories.tsx
Normal file
18
app/components/loading.stories.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withKnobs, select} from '@storybook/addon-knobs';
|
||||
import {storiesOf} from '@storybook/react-native';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import Loading from './loading';
|
||||
|
||||
storiesOf('Loading Icon Story', module).
|
||||
addDecorator(withKnobs).
|
||||
add('loading icon', () => (
|
||||
<Loading
|
||||
size={select('size', {Large: 'large', Small: 'small'}, 'large')}
|
||||
color={select('color', {Red: 'red', Blue: 'blue', Yellow: 'yellow', Black: 'black'}, 'red')}
|
||||
/>
|
||||
));
|
||||
@@ -17,8 +17,6 @@ function mapStateToProps(state, ownProps) {
|
||||
mentionKeys: ownProps.mentionKeys || getAllUserMentionKeys(state),
|
||||
minimumHashtagLength: MinimumHashtagLength ? parseInt(MinimumHashtagLength, 10) : 3,
|
||||
theme: getTheme(state),
|
||||
enableLatex: getConfig(state).EnableLatex === 'true',
|
||||
enableInlineLatex: getConfig(state).EnableInlineLatex === 'true',
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,278 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`LatexCodeBlock should have 2 lines 1`] = `
|
||||
<TouchableWithFeedbackIOS
|
||||
onLongPress={[Function]}
|
||||
onPress={[Function]}
|
||||
type="opacity"
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"borderColor": "rgba(63,67,80,0.15)",
|
||||
"borderRadius": 3,
|
||||
"borderWidth": 0.5,
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
"flexDirection": "column",
|
||||
"paddingLeft": 6,
|
||||
"paddingVertical": 4,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "flex-start",
|
||||
"marginLeft": 5,
|
||||
"paddingVertical": 4,
|
||||
}
|
||||
}
|
||||
>
|
||||
<ForwardRef
|
||||
color="black"
|
||||
math="\\\\frac{2}{1} = 2"
|
||||
onError={[Function]}
|
||||
renderError={[Function]}
|
||||
resizeMode="cover"
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "flex-start",
|
||||
"marginLeft": 5,
|
||||
"paddingVertical": 4,
|
||||
}
|
||||
}
|
||||
>
|
||||
<ForwardRef
|
||||
color="black"
|
||||
math="2_{nd} = line"
|
||||
onError={[Function]}
|
||||
renderError={[Function]}
|
||||
resizeMode="cover"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "#192a4d",
|
||||
"justifyContent": "center",
|
||||
"opacity": 0.8,
|
||||
"padding": 6,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"color": "#ffffff",
|
||||
"fontSize": 12,
|
||||
}
|
||||
}
|
||||
>
|
||||
LaTeX
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableWithFeedbackIOS>
|
||||
`;
|
||||
|
||||
exports[`LatexCodeBlock should have moreLinesText 1`] = `
|
||||
<TouchableWithFeedbackIOS
|
||||
onLongPress={[Function]}
|
||||
onPress={[Function]}
|
||||
type="opacity"
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"borderColor": "rgba(63,67,80,0.15)",
|
||||
"borderRadius": 3,
|
||||
"borderWidth": 0.5,
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
"flexDirection": "column",
|
||||
"paddingLeft": 6,
|
||||
"paddingVertical": 4,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "flex-start",
|
||||
"marginLeft": 5,
|
||||
"paddingVertical": 4,
|
||||
}
|
||||
}
|
||||
>
|
||||
<ForwardRef
|
||||
color="black"
|
||||
math="\\\\frac{5}{2} = 2"
|
||||
onError={[Function]}
|
||||
renderError={[Function]}
|
||||
resizeMode="cover"
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "flex-start",
|
||||
"marginLeft": 5,
|
||||
"paddingVertical": 4,
|
||||
}
|
||||
}
|
||||
>
|
||||
<ForwardRef
|
||||
color="black"
|
||||
math="2_{nd} = line"
|
||||
onError={[Function]}
|
||||
renderError={[Function]}
|
||||
resizeMode="cover"
|
||||
/>
|
||||
</View>
|
||||
<InjectIntl(FormattedText)
|
||||
defaultMessage="+{count, number} more {count, plural, one {line} other {lines}}"
|
||||
id="mobile.markdown.code.plusMoreLines"
|
||||
style={
|
||||
Object {
|
||||
"color": "rgba(63,67,80,0.4)",
|
||||
"fontSize": 11,
|
||||
"marginTop": 2,
|
||||
}
|
||||
}
|
||||
values={
|
||||
Object {
|
||||
"count": 1,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "#192a4d",
|
||||
"justifyContent": "center",
|
||||
"opacity": 0.8,
|
||||
"padding": 6,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"color": "#ffffff",
|
||||
"fontSize": 12,
|
||||
}
|
||||
}
|
||||
>
|
||||
LaTeX
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableWithFeedbackIOS>
|
||||
`;
|
||||
|
||||
exports[`LatexCodeBlock should match snapshot 1`] = `
|
||||
<TouchableWithFeedbackIOS
|
||||
onLongPress={[Function]}
|
||||
onPress={[Function]}
|
||||
type="opacity"
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"borderColor": "rgba(63,67,80,0.15)",
|
||||
"borderRadius": 3,
|
||||
"borderWidth": 0.5,
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
"flexDirection": "column",
|
||||
"paddingLeft": 6,
|
||||
"paddingVertical": 4,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "flex-start",
|
||||
"marginLeft": 5,
|
||||
"paddingVertical": 4,
|
||||
}
|
||||
}
|
||||
>
|
||||
<ForwardRef
|
||||
color="black"
|
||||
math="\\\\frac{1}{2} == 0.5^{0*\\\\pi} - 0.5"
|
||||
onError={[Function]}
|
||||
renderError={[Function]}
|
||||
resizeMode="cover"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "#192a4d",
|
||||
"justifyContent": "center",
|
||||
"opacity": 0.8,
|
||||
"padding": 6,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"color": "#ffffff",
|
||||
"fontSize": 12,
|
||||
}
|
||||
}
|
||||
>
|
||||
LaTeX
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableWithFeedbackIOS>
|
||||
`;
|
||||
@@ -1,16 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
|
||||
import LatexCodeBlock from './latex_code_block';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
theme: getTheme(state),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(LatexCodeBlock);
|
||||
@@ -1,209 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {PropTypes} from 'prop-types';
|
||||
import React from 'react';
|
||||
import {Keyboard, View, Text, StyleSheet, Platform} from 'react-native';
|
||||
import MathView from 'react-native-math-view';
|
||||
|
||||
import {goToScreen} from '@actions/navigation';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
import {splitLatexCodeInLines} from '@utils/latex';
|
||||
import {getDisplayNameForLanguage} from '@utils/markdown';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
import MarkdownCodeBlock from '../markdown_code_block/markdown_code_block';
|
||||
|
||||
const MAX_LINES = 2;
|
||||
|
||||
export default class LatexCodeBlock extends MarkdownCodeBlock {
|
||||
static propTypes = {
|
||||
theme: PropTypes.object.isRequired,
|
||||
content: PropTypes.string.isRequired,
|
||||
textStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
|
||||
};
|
||||
|
||||
handlePress = preventDoubleTap(() => {
|
||||
const {content} = this.props;
|
||||
const {intl} = this.context;
|
||||
const screen = 'Latex';
|
||||
const passProps = {
|
||||
content,
|
||||
};
|
||||
|
||||
const languageDisplayName = getDisplayNameForLanguage('latex');
|
||||
let title;
|
||||
if (languageDisplayName) {
|
||||
title = intl.formatMessage(
|
||||
{
|
||||
id: 'mobile.routes.code',
|
||||
defaultMessage: '{language} Code',
|
||||
},
|
||||
{
|
||||
language: languageDisplayName,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
title = intl.formatMessage({
|
||||
id: 'mobile.routes.code.noLanguage',
|
||||
defaultMessage: 'LaTeX Code',
|
||||
});
|
||||
}
|
||||
|
||||
Keyboard.dismiss();
|
||||
requestAnimationFrame(() => {
|
||||
goToScreen(screen, title, passProps);
|
||||
});
|
||||
});
|
||||
|
||||
splitContent = (content) => {
|
||||
const lines = splitLatexCodeInLines(content);
|
||||
|
||||
const numberOfLines = lines.length;
|
||||
|
||||
if (numberOfLines > MAX_LINES) {
|
||||
return {
|
||||
content: lines.slice(0, MAX_LINES),
|
||||
numberOfLines,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
content: lines,
|
||||
numberOfLines,
|
||||
};
|
||||
};
|
||||
|
||||
onErrorMessage = (errorMsg) => {
|
||||
const style = getStyleSheet(this.props.theme);
|
||||
|
||||
return <Text style={style.errorText}>{'Error: ' + errorMsg.message}</Text>;
|
||||
};
|
||||
|
||||
onRenderErrorMessage = (errorMsg) => {
|
||||
const style = getStyleSheet(this.props.theme);
|
||||
|
||||
return <Text style={style.errorText}>{'Render error: ' + errorMsg.error.message}</Text>;
|
||||
};
|
||||
|
||||
render() {
|
||||
const style = getStyleSheet(this.props.theme);
|
||||
|
||||
let language = null;
|
||||
const languageDisplayName = getDisplayNameForLanguage('latex');
|
||||
|
||||
if (languageDisplayName) {
|
||||
language = (
|
||||
<View style={style.language}>
|
||||
<Text style={style.languageText}>
|
||||
{languageDisplayName}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const {content, numberOfLines} = this.splitContent(this.props.content);
|
||||
|
||||
let plusMoreLines = null;
|
||||
if (numberOfLines > MAX_LINES) {
|
||||
plusMoreLines = (
|
||||
<FormattedText
|
||||
style={style.plusMoreLinesText}
|
||||
id='mobile.markdown.code.plusMoreLines'
|
||||
defaultMessage='+{count, number} more {count, plural, one {line} other {lines}}'
|
||||
values={{
|
||||
count: numberOfLines - MAX_LINES,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Note on the error behavior of math view:
|
||||
* - onError returns an Error object
|
||||
* - renderError returns an options object with an error attribute that contains the real Error.
|
||||
*/
|
||||
return (
|
||||
<TouchableWithFeedback
|
||||
onPress={this.handlePress}
|
||||
onLongPress={this.handleLongPress}
|
||||
type={'opacity'}
|
||||
>
|
||||
<View style={style.container}>
|
||||
<View style={style.rightColumn}>
|
||||
{content.map((latexCode) => (
|
||||
<View
|
||||
style={style.code}
|
||||
key={latexCode}
|
||||
>
|
||||
<MathView
|
||||
math={latexCode}
|
||||
onError={this.onErrorMessage}
|
||||
renderError={this.onRenderErrorMessage}
|
||||
resizeMode={'cover'}
|
||||
/>
|
||||
</View>
|
||||
))}
|
||||
{plusMoreLines}
|
||||
</View>
|
||||
{language}
|
||||
</View>
|
||||
</TouchableWithFeedback>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
const codeVerticalPadding = Platform.select({
|
||||
ios: 4,
|
||||
android: 0,
|
||||
});
|
||||
|
||||
return {
|
||||
container: {
|
||||
borderColor: changeOpacity(theme.centerChannelColor, 0.15),
|
||||
borderRadius: 3,
|
||||
borderWidth: StyleSheet.hairlineWidth,
|
||||
flexDirection: 'row',
|
||||
flex: 1,
|
||||
},
|
||||
rightColumn: {
|
||||
flexDirection: 'column',
|
||||
flex: 1,
|
||||
paddingLeft: 6,
|
||||
paddingVertical: 4,
|
||||
},
|
||||
code: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-start',
|
||||
marginLeft: 5,
|
||||
paddingVertical: codeVerticalPadding,
|
||||
},
|
||||
plusMoreLinesText: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.4),
|
||||
fontSize: 11,
|
||||
marginTop: 2,
|
||||
},
|
||||
language: {
|
||||
alignItems: 'center',
|
||||
backgroundColor: theme.sidebarHeaderBg,
|
||||
justifyContent: 'center',
|
||||
opacity: 0.8,
|
||||
padding: 6,
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
top: 0,
|
||||
},
|
||||
languageText: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
fontSize: 12,
|
||||
},
|
||||
errorText: {
|
||||
fontSize: 14,
|
||||
marginHorizontal: 5,
|
||||
color: theme.errorTextColor,
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -1,50 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import Preferences from '@mm-redux/constants/preferences';
|
||||
|
||||
import LatexCodeBlock from './latex_code_block';
|
||||
|
||||
describe('LatexCodeBlock', () => {
|
||||
const baseProps = {
|
||||
theme: Preferences.THEMES.denim,
|
||||
language: 'latex',
|
||||
textStyle: {},
|
||||
};
|
||||
|
||||
test('should match snapshot', () => {
|
||||
const wrapper = shallow(
|
||||
<LatexCodeBlock
|
||||
{...baseProps}
|
||||
content={'\\frac{1}{2} == 0.5^{0*\\pi} - 0.5'}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should have 2 lines', () => {
|
||||
const wrapper = shallow(
|
||||
<LatexCodeBlock
|
||||
{...baseProps}
|
||||
content={'\\frac{2}{1} = 2 \\\\ 2_{nd} = line'}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should have moreLinesText', () => {
|
||||
const wrapper = shallow(
|
||||
<LatexCodeBlock
|
||||
{...baseProps}
|
||||
content={'\\frac{5}{2} = 2 \\\\ 2_{nd} = line \\\\ \\pi = 3.14'}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,71 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`LatexCodeBlock should have maxWidth 10 1`] = `
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"flexDirection": "row",
|
||||
"flexWrap": "wrap",
|
||||
"marginBottom": -7,
|
||||
},
|
||||
Object {
|
||||
"height": undefined,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<ForwardRef
|
||||
color="black"
|
||||
math="\\\\frac{1}{2} == 0.5^{0*\\\\pi} - 0.5"
|
||||
onError={[Function]}
|
||||
renderError={[Function]}
|
||||
resizeMode="contain"
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"marginBottom": -7,
|
||||
},
|
||||
Object {
|
||||
"maxWidth": 10,
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
`;
|
||||
|
||||
exports[`LatexCodeBlock should match snapshot 1`] = `
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"flexDirection": "row",
|
||||
"flexWrap": "wrap",
|
||||
"marginBottom": -7,
|
||||
},
|
||||
Object {
|
||||
"height": undefined,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<ForwardRef
|
||||
color="black"
|
||||
math="\\\\frac{1}{2} == 0.5^{0*\\\\pi} - 0.5"
|
||||
onError={[Function]}
|
||||
renderError={[Function]}
|
||||
resizeMode="contain"
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"marginBottom": -7,
|
||||
},
|
||||
Object {
|
||||
"maxWidth": "100%",
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
`;
|
||||
@@ -1,16 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
|
||||
import LatexInline from './latex_inline';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
theme: getTheme(state),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(LatexInline);
|
||||
@@ -1,78 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React, {PureComponent} from 'react';
|
||||
import {Platform, Text, View} from 'react-native';
|
||||
import MathView from 'react-native-math-view';
|
||||
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
export default class LatexInline extends PureComponent {
|
||||
static propTypes = {
|
||||
content: PropTypes.string.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
onLayout: PropTypes.func,
|
||||
maxMathWidth: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
||||
mathHeight: PropTypes.number,
|
||||
};
|
||||
|
||||
onErrorMessage = (errorMsg) => {
|
||||
const style = getStyleSheet(this.props.theme);
|
||||
|
||||
return <Text style={style.errorText}>{'Latex error: ' + errorMsg.message}</Text>;
|
||||
};
|
||||
|
||||
onRenderErrorMessage = (errorMsg) => {
|
||||
const style = getStyleSheet(this.props.theme);
|
||||
|
||||
return <Text style={style.errorText}>{'Latex render error: ' + errorMsg.error.message}</Text>;
|
||||
};
|
||||
|
||||
render() {
|
||||
const style = getStyleSheet(this.props.theme);
|
||||
|
||||
const viewStyleObj = Platform.select({
|
||||
ios: () => [style.viewStyle, {height: this.props.mathHeight}],
|
||||
android: () => [style.viewStyle],
|
||||
})();
|
||||
|
||||
return (
|
||||
<View
|
||||
style={viewStyleObj}
|
||||
key={this.props.content}
|
||||
onLayout={this.props.onLayout}
|
||||
>
|
||||
<MathView
|
||||
style={[style.mathStyle, {maxWidth: this.props.maxMathWidth ? this.props.maxMathWidth : '100%'}]}
|
||||
math={this.props.content}
|
||||
onError={this.onErrorMessage}
|
||||
renderError={this.onRenderErrorMessage}
|
||||
resizeMode={'contain'}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
mathStyle: {
|
||||
marginBottom: -7,
|
||||
},
|
||||
viewStyle: {
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
...Platform.select({
|
||||
ios: {
|
||||
marginBottom: -7,
|
||||
},
|
||||
}),
|
||||
},
|
||||
errorText: {
|
||||
flexDirection: 'row',
|
||||
color: theme.errorTextColor,
|
||||
flexWrap: 'wrap',
|
||||
},
|
||||
};
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user