Compare commits

...

36 Commits

Author SHA1 Message Date
Elias Nahum
83112f5ab4 Bump app version number to 1.42.1 (#5343) 2021-04-19 12:04:40 -04:00
Mattermost Build
6058181d00 Bump app build number to 354 (#5341) (#5342)
(cherry picked from commit a7e0ee262b)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2021-04-19 11:57:14 -04:00
Mattermost Build
19cd3722e5 Fixes for release 1.42 (#5339) (#5340)
* Downgrade MMKV to 0.4.4

* Handle undefined in getMyTeams selector

* Set minimum server version to 5.31.3 and update unsupported URL

(cherry picked from commit 0a2a490e81)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2021-04-19 11:50:57 -04:00
Mattermost Build
da6f326e17 Bump app build number to 353 (#5331) (#5332)
(cherry picked from commit 11b1cc8ad8)

Co-authored-by: Miguel Alatzar <migbot@users.noreply.github.com>
2021-04-15 16:57:24 -04:00
Mattermost Build
4b6db7d9b1 Fix at_mention and channel_mention autocomplete on iOS (#5327) (#5329)
(cherry picked from commit c9a02d5c8f)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2021-04-15 10:47:44 -07:00
Mattermost Build
c74450adfd Bump app build number to 352 (#5324) (#5325)
(cherry picked from commit 154b2e6adb)

Co-authored-by: Miguel Alatzar <migbot@users.noreply.github.com>
2021-04-14 14:35:19 -07:00
Mattermost Build
0adf3618e2 Set url trailing slash (#5322) (#5323) 2021-04-14 16:48:36 -04:00
Mattermost Build
c3569e27a9 Use RNN 7.11.3 (#5317) (#5319) 2021-04-13 21:40:54 -04:00
Mattermost Build
385b667d65 Bump app build number to 351 (#5309) (#5311)
(cherry picked from commit 26f6880c04)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2021-04-12 13:09:00 -04:00
Mattermost Build
116a07e1c8 Translations update from Weblate (#5307) (#5310)
* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (704 of 704 strings)

Translation: mattermost-languages-shipped/mattermost-mobile
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_master/pt_BR/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (703 of 703 strings)

Translation: mattermost-languages-shipped/mattermost-mobile
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_master/pt_BR/

* Translated using Weblate (Romanian)

Currently translated at 100.0% (704 of 704 strings)

Translation: mattermost-languages-shipped/mattermost-mobile
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_master/ro/

Translated using Weblate (Romanian)

Currently translated at 94.1% (663 of 704 strings)

Translation: mattermost-languages-shipped/mattermost-mobile
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_master/ro/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (704 of 704 strings)

Translation: mattermost-languages-shipped/mattermost-mobile
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_master/nl/

* Translated using Weblate (Russian)

Currently translated at 99.8% (703 of 704 strings)

Translation: mattermost-languages-shipped/mattermost-mobile
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_master/ru/

Translated using Weblate (Russian)

Currently translated at 100.0% (704 of 704 strings)

Translation: mattermost-languages-shipped/mattermost-mobile
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_master/ru/

* Translated using Weblate (Turkish)

Currently translated at 99.8% (703 of 704 strings)

Translation: mattermost-languages-shipped/mattermost-mobile
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_master/tr/

* Translated using Weblate (Japanese)

Currently translated at 100.0% (704 of 704 strings)

Translation: mattermost-languages-shipped/mattermost-mobile
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_master/ja/

* Translated using Weblate (Swedish)

Currently translated at 100.0% (704 of 704 strings)

Translation: mattermost-languages-shipped/mattermost-mobile
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_master/sv/

Translated using Weblate (Swedish)

Currently translated at 100.0% (704 of 704 strings)

Translation: mattermost-languages-shipped/mattermost-mobile
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_master/sv/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (704 of 704 strings)

Translation: mattermost-languages-shipped/mattermost-mobile
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_master/nl/

* Translated using Weblate (Swedish)

Currently translated at 100.0% (704 of 704 strings)

Translation: mattermost-languages-shipped/mattermost-mobile
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_master/sv/

* Translated using Weblate (Russian)

Currently translated at 100.0% (704 of 704 strings)

Translation: mattermost-languages-shipped/mattermost-mobile
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_master/ru/

* Translated using Weblate (Bulgarian)

Currently translated at 100.0% (704 of 704 strings)

Translation: mattermost-languages-shipped/mattermost-mobile
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_master/bg/

Co-authored-by: rodrigocorsi <rodrigocorsi@gmail.com>
Co-authored-by: Viorel-Cosmin Miron <cosmin@uhlhost.net>
Co-authored-by: Tom De Moor <tom@controlaltdieliet.be>
Co-authored-by: Edward Smirnov <ed@microolap.com>
Co-authored-by: Kaya Zeren <kayazeren@gmail.com>
Co-authored-by: kaakaa <stooner.hoe@gmail.com>
Co-authored-by: MArtin Johnson <martinjohnson@bahnhof.se>
Co-authored-by: Nikolai Zahariev <nikolaiz@yahoo.com>
(cherry picked from commit ff4214e331)

Co-authored-by: Weblate (bot) <hosted@weblate.org>
2021-04-12 13:08:51 -04:00
Mattermost Build
bd767ad7fa MM-34714 fix mention highlight to dismiss mentions for non existent users (#5300) (#5305)
(cherry picked from commit 06f8f0b3a5)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2021-04-10 11:56:57 -04:00
Mattermost Build
f0fb2abba4 MM-34681 Validate OpenGraph Image before attempting to load (#5294) (#5299)
(cherry picked from commit 5156480220)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2021-04-10 11:56:47 -04:00
Mattermost Build
95e0e45fc7 MM-34585 Use removeClippedSubviews from Lists to improve scroll perf (#5199) (#5298)
(cherry picked from commit 9c043f1b9f)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2021-04-10 11:56:37 -04:00
Mattermost Build
c6df27e51b Fix testID (#5302) (#5303)
(cherry picked from commit aecbe9af8e)

Co-authored-by: Miguel Alatzar <migbot@users.noreply.github.com>
2021-04-09 14:30:15 -07:00
Elias Nahum
2851c50347 Bump app build number to 350 (#5296) 2021-04-08 11:54:29 -04:00
Mattermost Build
044a066c4a MM-34508 in-app browser emm configuration (#5274) (#5290)
(cherry picked from commit 66ebe3683b)

Co-authored-by: Anurag Shivarathri <anurag6713@gmail.com>
2021-04-07 14:21:53 -04:00
Mattermost Build
81b1ba8489 Fix propTypes warnings (#5285) (#5287)
(cherry picked from commit c6953bef47)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2021-04-07 10:41:03 -04:00
Mattermost Build
c2d513be36 Automated cherry pick of #5278 (#5288)
(cherry picked from commit c8f8d2c35c)

Co-authored-by: Ben Schumacher <ben.schumacher@mattermost.com>
2021-04-07 09:33:23 +02:00
Mattermost Build
7010672260 Update NOTICE.txt (#5284) (#5286) 2021-04-06 21:17:05 -04:00
Mattermost Build
bb0e056fba Update dependencies (#5266) (#5283)
* Update dependencies

* Fix lint, use npm@6

* Fix unit tests

* Dowgrade Fastlane

* Fix Fastlane script

* use android:api-29-node ci image

* Infer gradle json file from apk output folder

* Fastlane to Parse new version of gradle output-metadata.json

(cherry picked from commit f8a0f29237)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2021-04-06 11:27:26 -04:00
Mattermost Build
e48371646b Cancel gallery prepare file when Android's back button is pressed (#5270) (#5282)
(cherry picked from commit b5a248072d)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2021-04-06 11:12:07 -04:00
Mattermost Build
5421ddb2e6 MM-31874 Properly use theme mention highlight color (#5268) (#5281)
(cherry picked from commit 5bff6f9e58)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2021-04-06 11:11:53 -04:00
Mattermost Build
17e62878ab Fix modifier typo in search screen (#5267) (#5280)
(cherry picked from commit ec9c2fba3d)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2021-04-06 11:11:21 -04:00
Mattermost Build
ed153010f5 MM-32725 Reduce KeyStore qty access (#5261) (#5279)
(cherry picked from commit 4ce430bdb8)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2021-04-06 07:31:10 -07:00
Weblate (bot)
57eb0ddfd8 Translations update from Weblate (#5275)
* Translated using Weblate (German)

Currently translated at 87.2% (611 of 700 strings)

Translation: mattermost-languages-shipped/mattermost-mobile
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_master/de/

* Translated using Weblate (Russian)

Currently translated at 100.0% (703 of 703 strings)

Translation: mattermost-languages-shipped/mattermost-mobile
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_master/ru/

Translated using Weblate (Russian)

Currently translated at 100.0% (700 of 700 strings)

Translation: mattermost-languages-shipped/mattermost-mobile
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_master/ru/

* Translated using Weblate (Turkish)

Currently translated at 100.0% (700 of 700 strings)

Translation: mattermost-languages-shipped/mattermost-mobile
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_master/tr/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 98.1% (687 of 700 strings)

Translation: mattermost-languages-shipped/mattermost-mobile
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_master/zh_Hans/

* Translated using Weblate (Swedish)

Currently translated at 100.0% (700 of 700 strings)

Translation: mattermost-languages-shipped/mattermost-mobile
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_master/sv/

* Translated using Weblate (Spanish)

Currently translated at 94.3% (663 of 703 strings)

Translation: mattermost-languages-shipped/mattermost-mobile
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_master/es/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (703 of 703 strings)

Translation: mattermost-languages-shipped/mattermost-mobile
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_master/nl/

Co-authored-by: Elisabeth Kulzer <elisabeth.kulzer@mattermost.com>
Co-authored-by: Edward Smirnov <ed@microolap.com>
Co-authored-by: Kaya Zeren <kayazeren@gmail.com>
Co-authored-by: aeomin <lin@aeomin.net>
Co-authored-by: MArtin Johnson <martinjohnson@bahnhof.se>
Co-authored-by: Elias  Nahum <elias@mattermost.com>
Co-authored-by: Tom De Moor <tom@controlaltdieliet.be>
2021-04-06 11:39:26 +02:00
Mattermost Build
77b3dfff2d Copy temp file if isMediaDocument contentUri is null (#5269) (#5271) 2021-04-03 15:02:27 -03:00
Mattermost Build
c9e6cd8073 Post app call response ephemeral messages as bot (#5256) (#5265)
* post app call response ephemeral messages as bot

* add check for isBot

* add SendEphemeralPost func type

* snapshot

(cherry picked from commit f766f47358)

Co-authored-by: Michael Kochell <6913320+mickmister@users.noreply.github.com>
2021-04-02 14:10:14 -04:00
Mattermost Build
91b4621433 Add parsing error to suggestions, add more information to text suggestions and avoid empty suggestions (#5251) (#5264)
* Add parsing error to suggestions and avoid empty suggestions

* Fix merge issue

* Address feedback and send fetch form errors as suggestion errors

* Fix test

(cherry picked from commit 777b35bda3)

Co-authored-by: Daniel Espino García <larkox@gmail.com>
2021-04-01 15:58:03 -04:00
Mattermost Build
5aeae45bd3 [MM-33892] Replace em dash with double dash for commands (#5259) (#5263)
* replace em dash with double dash for commands

* style

* missing semi

(cherry picked from commit e49c0314cc)

Co-authored-by: Michael Kochell <6913320+mickmister@users.noreply.github.com>
2021-04-01 15:02:36 -04:00
Mattermost Build
f57fa09257 Fix embedded bindings (#5244) (#5254)
(cherry picked from commit 64ccb58684)

Co-authored-by: Daniel Espino García <larkox@gmail.com>
2021-03-29 15:53:43 +02:00
Weblate (bot)
40d94ee170 Translations update from Weblate (#5250)
* Translated using Weblate (Russian)

Currently translated at 100.0% (698 of 698 strings)

Translation: mattermost-languages-shipped/mattermost-mobile
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_master/ru/

Translated using Weblate (Russian)

Currently translated at 96.8% (676 of 698 strings)

Translation: mattermost-languages-shipped/mattermost-mobile
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_master/ru/

* Translated using Weblate (Turkish)

Currently translated at 100.0% (700 of 700 strings)

Translation: mattermost-languages-shipped/mattermost-mobile
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_master/tr/

Translated using Weblate (Turkish)

Currently translated at 100.0% (698 of 698 strings)

Translation: mattermost-languages-shipped/mattermost-mobile
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_master/tr/

* Translated using Weblate (Japanese)

Currently translated at 94.9% (663 of 698 strings)

Translation: mattermost-languages-shipped/mattermost-mobile
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_master/ja/

* Added translation using Weblate (English (Australia))

Translated using Weblate (Dutch)

Currently translated at 96.1% (671 of 698 strings)

Translation: mattermost-languages-shipped/mattermost-mobile
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_master/nl/

* Translated using Weblate (English (Australia))

Currently translated at 100.0% (698 of 698 strings)

Translation: mattermost-languages-shipped/mattermost-mobile
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_master/en_AU/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 95.1% (664 of 698 strings)

Translation: mattermost-languages-shipped/mattermost-mobile
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_master/zh_Hans/

* Translated using Weblate (English (Australia))

Currently translated at 100.0% (700 of 700 strings)

Translation: mattermost-languages-shipped/mattermost-mobile
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_master/en_AU/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (700 of 700 strings)

Translation: mattermost-languages-shipped/mattermost-mobile
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_master/nl/

Deleted translation using Weblate (English (Australia))

* Translated using Weblate (Korean)

Currently translated at 93.5% (655 of 700 strings)

Translation: mattermost-languages-shipped/mattermost-mobile
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_master/ko/

* Translated using Weblate (Swedish)

Currently translated at 100.0% (700 of 700 strings)

Translation: mattermost-languages-shipped/mattermost-mobile
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_master/sv/

Translated using Weblate (Swedish)

Currently translated at 96.7% (677 of 700 strings)

Translation: mattermost-languages-shipped/mattermost-mobile
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_master/sv/

Translated using Weblate (Swedish)

Currently translated at 96.0% (672 of 700 strings)

Translation: mattermost-languages-shipped/mattermost-mobile
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_master/sv/

* Translated using Weblate (Ukrainian)

Currently translated at 90.2% (632 of 700 strings)

Translation: mattermost-languages-shipped/mattermost-mobile
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_master/uk/

Co-authored-by: Edward Smirnov <ed@microolap.com>
Co-authored-by: Kaya Zeren <kayazeren@gmail.com>
Co-authored-by: kaakaa <stooner.hoe@gmail.com>
Co-authored-by: Tom De Moor <tom@controlaltdieliet.be>
Co-authored-by: Matthew Williams <en_AU@controlaltdieliet.be>
Co-authored-by: aeomin <lin@aeomin.net>
Co-authored-by: Matthew Williams <Matthew.Williams@outlook.com.au>
Co-authored-by: teamzamong <heekang@korea.ac.kr>
Co-authored-by: MArtin Johnson <martinjohnson@bahnhof.se>
Co-authored-by: Yuriy Putsan <putsan57@gmail.com>
2021-03-29 13:35:05 +02:00
Mattermost Build
4e7d5a8907 Handle double submit on modals (#5236) (#5253)
* Handle double submit on modals

* Use class variable instead of state

* Handle doubletap before async function

* Keep the submitting state after modal dismiss

* Properly use prevent double tap

(cherry picked from commit 0f2c43f19a)

Co-authored-by: Daniel Espino García <larkox@gmail.com>
2021-03-29 13:13:08 +02:00
Mattermost Build
8e88ab6e96 Make app command parse error message more readable (#5238) (#5252)
* Make app command parse error message more readable

* Update i18n

(cherry picked from commit 26ffdbde91)

Co-authored-by: Daniel Espino García <larkox@gmail.com>
2021-03-29 13:09:46 +02:00
Mattermost Build
f054dfd8fb Bump app build number to 348 (#5248) (#5249)
(cherry picked from commit c11f3dc501)

Co-authored-by: Miguel Alatzar <migbot@users.noreply.github.com>
2021-03-26 15:13:30 -07:00
Mattermost Build
c16f3b2d8e Bump app version number to 1.42.0 (#5246) (#5247)
(cherry picked from commit 51a2940c41)

Co-authored-by: Miguel Alatzar <migbot@users.noreply.github.com>
2021-03-26 15:06:45 -07:00
Mattermost Build
eb0937d7ad [MM-33742] Update RequiredServer (#5242) (#5245)
* Update RequiredServer

* Update version in README and fastlane docs

(cherry picked from commit fa6103c1b3)

Co-authored-by: Miguel Alatzar <migbot@users.noreply.github.com>
2021-03-26 14:53:58 -07:00
182 changed files with 9943 additions and 16621 deletions

View File

@@ -23,7 +23,7 @@ executors:
NODE_ENV: production
BABEL_ENV: production
macos:
xcode: "12.0.0"
xcode: "12.1.0"
working_directory: ~/mattermost-mobile
shell: /bin/bash --login -o pipefail
@@ -550,14 +550,14 @@ workflows:
- test
filters:
branches:
only: /^build-pr-.*/
only: /^(build|android)-pr-.*/
- build-ios-pr:
context: mattermost-mobile-ios-pr
requires:
- test
filters:
branches:
only: /^build-pr-.*/
only: /^(build|ios)-pr-.*/
- build-android-unsigned:
context: mattermost-mobile-unsigned

View File

@@ -6,8 +6,8 @@
],
"parser": "@typescript-eslint/parser",
"plugins": [
"mattermost",
"@typescript-eslint"
"@typescript-eslint",
"mattermost"
],
"settings": {
"react": {

View File

@@ -8,10 +8,6 @@
; Ignore polyfills
node_modules/react-native/Libraries/polyfills/.*
; These should not be required directly
; require from fbjs/lib instead: require('fbjs/lib/warning')
node_modules/warning/.*
; Flow doesn't support platforms
.*/Libraries/Utilities/LoadingView.js
@@ -30,6 +26,8 @@ emoji=true
esproposal.optional_chaining=enable
esproposal.nullish_coalescing=enable
exact_by_default=true
module.file_ext=.js
module.file_ext=.json
module.file_ext=.ios.js
@@ -44,10 +42,6 @@ suppress_type=$FlowFixMe
suppress_type=$FlowFixMeProps
suppress_type=$FlowFixMeState
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\)
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\)?:? #[0-9]+
suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
[lints]
sketchy-null-number=warn
sketchy-null-mixed=warn
@@ -59,7 +53,6 @@ unsafe-getters-setters=warn
inexact-spread=warn
unnecessary-invariant=warn
signature-verification-failure=warn
deprecated-utility=error
[strict]
deprecated-type
@@ -71,4 +64,4 @@ untyped-import
untyped-type-import
[version]
^0.122.0
^0.137.0

7
.gitattributes vendored
View File

@@ -1,4 +1,3 @@
*.pbxproj -text
# specific for windows script files
*.bat text eol=crlf
# Windows files should use crlf line endings
# https://help.github.com/articles/dealing-with-line-endings/
*.bat text eol=crlf

1
.husky/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
_

4
.husky/pre-commit Executable file
View File

@@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
sh ./scripts/pre-commit.sh

View File

@@ -694,37 +694,6 @@ SOFTWARE.
---
## core-js
This product contains 'core-js' by Denis Pushkarev.
Modular standard library for JavaScript.
* HOMEPAGE:
* https://github.com/zloirock/core-js
* LICENSE: Copyright (c) 2014-2019 Denis Pushkarev
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---
## deep-equal
This product contains 'deep-equal' by James Halliday.

View File

@@ -1,6 +1,6 @@
# Mattermost Mobile
- **Minimum Server versions:** Current ESR version (5.25)
- **Minimum Server versions:** Current ESR version (5.31)
- **Supported iOS versions:** 11+
- **Supported Android versions:** 7.0+

View File

@@ -132,8 +132,8 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
missingDimensionStrategy "RNNotifications.reactNativeVersion", "reactNative60"
versionCode 347
versionName "1.41.0"
versionCode 354
versionName "1.42.1"
multiDexEnabled = true
testBuildType System.getProperty('testBuildType', 'debug')
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
@@ -191,6 +191,10 @@ android {
targetCompatibility 1.8
}
packagingOptions {
pickFirst '**/*.so'
}
}
repositories {

View File

@@ -4,5 +4,10 @@
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<application android:usesCleartextTraffic="true" tools:targetApi="28" tools:ignore="GoogleAppIndexingWarning" />
<application
android:usesCleartextTraffic="true"
tools:targetApi="28"
tools:ignore="GoogleAppIndexingWarning">
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
</application>
</manifest>

View File

@@ -50,7 +50,6 @@
<data android:scheme="mmauthbeta" />
</intent-filter>
</activity>
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
<service android:name=".NotificationDismissService"
android:enabled="true"
android:exported="false" />

View File

@@ -16,12 +16,20 @@ import com.mattermost.react_native_interface.KeysReadableArray;
public class MattermostCredentialsHelper {
static final String CURRENT_SERVER_URL = "@currentServerUrl";
static KeychainModule keychainModule;
static AsyncStorageHelper asyncStorage;
public static void getCredentialsForCurrentServer(ReactApplicationContext context, ResolvePromise promise) {
final KeychainModule keychainModule = new KeychainModule(context);
final AsyncStorageHelper asyncStorage = new AsyncStorageHelper(context);
final ArrayList<String> keys = new ArrayList<String>(1);
keys.add(CURRENT_SERVER_URL);
if (keychainModule == null) {
keychainModule = new KeychainModule(context);
}
if (asyncStorage == null) {
asyncStorage = new AsyncStorageHelper(context);
}
KeysReadableArray asyncStorageKeys = new KeysReadableArray() {
@Override
public int size() {

View File

@@ -72,7 +72,11 @@ public class RealPathUtil {
split[1]
};
return getDataColumn(context, contentUri, selection, selectionArgs);
if (contentUri != null) {
return getDataColumn(context, contentUri, selection, selectionArgs);
} else {
return getPathFromSavingTempFile(context, uri);
}
}
}

View File

@@ -18,4 +18,6 @@
<string name="timeout_description">How long in milliseconds the mobile app should wait for the server to respond.</string>
<string name="vendor_title">EMM Vendor or Company Name</string>
<string name="vendor_description">Name of the EMM vendor or company deploying the app. Used in help text when prompting for passcodes so users are aware why the app is being protected.</string>
<string name="inAppSessionAuth_title">In-App Session Auth</string>
<string name="inAppSessionAuth_description">Instead of default flow from the mobile browser, enforce SSO with the WebView.</string>
</resources>

View File

@@ -1,7 +1,7 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
<!-- Customize your theme here. -->
<item name="android:windowBackground">@android:color/transparent</item>

View File

@@ -7,6 +7,12 @@
android:description="@string/inAppPinCode_description"
android:restrictionType="string"
android:defaultValue="false" />
<restriction
android:key="inAppSessionAuth"
android:title="@string/inAppSessionAuth_title"
android:description="@string/inAppSessionAuth_description"
android:restrictionType="string"
android:defaultValue="false" />
<restriction
android:key="blurApplicationScreen"
android:title="@string/blurApplicationScreen_title"

View File

@@ -2,7 +2,7 @@
buildscript {
ext {
buildToolsVersion = "29.0.2"
buildToolsVersion = "29.0.3"
minSdkVersion = 24
compileSdkVersion = 29
targetSdkVersion = 29
@@ -10,6 +10,7 @@ buildscript {
kotlinVersion = "1.3.61"
firebaseVersion = "21.0.0"
RNNKotlinVersion = kotlinVersion
ndkVersion = "21.1.6352462"
}
repositories {
@@ -19,7 +20,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.3'
classpath 'com.android.tools.build:gradle:4.1.0'
classpath 'com.google.gms:google-services:4.2.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"

View File

@@ -30,4 +30,4 @@ android.useAndroidX=true
android.enableJetifier=true
# Version of flipper SDK to use with React Native
FLIPPER_VERSION=0.37.0
FLIPPER_VERSION=0.75.1

Binary file not shown.

View File

@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip

22
android/gradlew.bat vendored
View File

@@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -54,7 +54,7 @@ goto fail
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@@ -64,28 +64,14 @@ echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell

View File

@@ -30,6 +30,7 @@ export function executeCommand(message: string, channelId: string, rootId: strin
};
let msg = message;
msg = filterEmDashForCommand(msg);
let cmdLength = msg.indexOf(' ');
if (cmdLength < 0) {
@@ -43,7 +44,7 @@ export function executeCommand(message: string, channelId: string, rootId: strin
if (appsAreEnabled) {
const parser = new AppCommandParser({dispatch, getState}, intl, args.channel_id, args.root_id);
if (parser.isAppCommand(msg)) {
const {call, errorMessage} = await parser.composeCallFromCommand(message);
const {call, errorMessage} = await parser.composeCallFromCommand(msg);
const createErrorMessage = (errMessage: string) => {
return {error: {message: errMessage}};
};
@@ -64,7 +65,7 @@ export function executeCommand(message: string, channelId: string, rootId: strin
switch (callResp.type) {
case AppCallResponseTypes.OK:
if (callResp.markdown) {
dispatch(sendEphemeralPost(callResp.markdown, args.channel_id, args.parent_id));
dispatch(sendEphemeralPost(callResp.markdown, args.channel_id, args.parent_id, callResp.app_metadata?.bot_user_id));
}
return {data: {}};
case AppCallResponseTypes.FORM:
@@ -90,3 +91,7 @@ export function executeCommand(message: string, channelId: string, rootId: strin
return {data, error};
};
}
const filterEmDashForCommand = (command: string): string => {
return command.replace(/\u2014/g, '--');
};

View File

@@ -28,12 +28,12 @@ import {getChannelSinceValue} from '@utils/channels';
import {getEmojisInPosts} from './emoji';
export function sendEphemeralPost(message, channelId = '', parentId = '') {
export function sendEphemeralPost(message, channelId = '', parentId = '', userId = '0') {
return async (dispatch, getState) => {
const timestamp = Date.now();
const post = {
id: generateId(),
user_id: '0',
user_id: userId,
channel_id: channelId || getCurrentChannelId(getState()),
message,
type: Posts.POST_TYPES.EPHEMERAL,

View File

@@ -2,16 +2,14 @@
exports[`AtMention should match snapshot, no highlight 1`] = `
<Text
style={Object {}}
style={
Object {
"backgroundColor": "yellow",
}
}
>
<Text
style={
Array [
Object {
"backgroundColor": "yellow",
},
]
}
style={Array []}
>
@John.Smith
</Text>
@@ -22,7 +20,11 @@ exports[`AtMention should match snapshot, with highlight 1`] = `
<Text
onLongPress={[Function]}
onPress={[Function]}
style={Object {}}
style={
Object {
"backgroundColor": "yellow",
}
}
>
<Text
style={
@@ -31,7 +33,8 @@ exports[`AtMention should match snapshot, with highlight 1`] = `
"color": "#ff0000",
},
Object {
"backgroundColor": "yellow",
"backgroundColor": "#ffe577",
"color": "#145dbf",
},
]
}
@@ -45,7 +48,11 @@ exports[`AtMention should match snapshot, without highlight 1`] = `
<Text
onLongPress={[Function]}
onPress={[Function]}
style={Object {}}
style={
Object {
"backgroundColor": "yellow",
}
}
>
<Text
style={

View File

@@ -10,7 +10,6 @@ import {displayUsername} from '@mm-redux/utils/user_utils';
import {showModal} from '@actions/navigation';
import CompassIcon from '@components/compass_icon';
import CustomPropTypes from '@constants/custom_prop_types';
import BottomSheet from '@utils/bottom_sheet';
import mattermostManaged from 'app/mattermost_managed';
@@ -19,9 +18,9 @@ export default class AtMention extends React.PureComponent {
isSearchResult: PropTypes.bool,
mentionKeys: PropTypes.array.isRequired,
mentionName: PropTypes.string.isRequired,
mentionStyle: CustomPropTypes.Style,
mentionStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
onPostPress: PropTypes.func,
textStyle: CustomPropTypes.Style,
textStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
teammateNameDisplay: PropTypes.string,
theme: PropTypes.object.isRequired,
usersByUsername: PropTypes.object.isRequired,
@@ -139,7 +138,7 @@ export default class AtMention extends React.PureComponent {
}
render() {
const {isSearchResult, mentionName, mentionStyle, onPostPress, teammateNameDisplay, textStyle, mentionKeys} = this.props;
const {isSearchResult, mentionName, mentionStyle, onPostPress, teammateNameDisplay, textStyle, mentionKeys, theme} = this.props;
const {user} = this.state;
const mentionTextStyle = [];
@@ -155,9 +154,8 @@ export default class AtMention extends React.PureComponent {
let styleText;
if (textStyle) {
const {backgroundColor: bg, ...otherStyles} = StyleSheet.flatten(textStyle);
backgroundColor = bg;
styleText = otherStyles;
backgroundColor = theme.mentionHighlightBg;
styleText = textStyle;
}
if (user?.username) {
@@ -176,12 +174,12 @@ export default class AtMention extends React.PureComponent {
} else {
const pattern = new RegExp(/\b(all|channel|here)(?:\.\B|_\b|\b)/, 'i');
const mentionMatch = pattern.exec(mentionName);
highlighted = true;
if (mentionMatch) {
mention = mentionMatch.length > 1 ? mentionMatch[1] : mentionMatch[0];
suffix = mentionName.replace(mention, '');
isMention = true;
highlighted = true;
} else {
mention = mentionName;
}
@@ -194,7 +192,7 @@ export default class AtMention extends React.PureComponent {
}
if (suffix) {
const suffixStyle = {...styleText, color: this.props.theme.centerChannelColor};
const suffixStyle = {...StyleSheet.flatten(styleText), color: theme.centerChannelColor};
suffixElement = (
<Text style={suffixStyle}>
{suffix}
@@ -207,7 +205,7 @@ export default class AtMention extends React.PureComponent {
}
if (highlighted) {
mentionTextStyle.push({backgroundColor});
mentionTextStyle.push({backgroundColor, color: theme.mentionColor});
}
return (

View File

@@ -3,6 +3,9 @@
import React from 'react';
import {shallow} from 'enzyme';
import {Preferences} from '@mm-redux/constants';
import AtMention from './at_mention.js';
describe('AtMention', () => {
@@ -13,7 +16,7 @@ describe('AtMention', () => {
mentionName: 'John.Smith',
mentionStyle: {color: '#ff0000'},
textStyle: {backgroundColor: 'yellow'},
theme: {},
theme: Preferences.THEMES.default,
};
test('should match snapshot, no highlight', () => {

View File

@@ -3,17 +3,17 @@
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {SectionList} from 'react-native';
import {Platform, SectionList} from 'react-native';
import {AT_MENTION_REGEX, AT_MENTION_SEARCH_REGEX} from '@constants/autocomplete';
import AtMentionItem from '@components/autocomplete/at_mention_item';
import AutocompleteSectionHeader from '@components/autocomplete/autocomplete_section_header';
import SpecialMentionItem from '@components/autocomplete/special_mention_item';
import GroupMentionItem from '@components/autocomplete/at_mention_group/at_mention_group';
import {RequestStatus} from '@mm-redux/constants';
import {AT_MENTION_REGEX, AT_MENTION_SEARCH_REGEX} from 'app/constants/autocomplete';
import AtMentionItem from 'app/components/autocomplete/at_mention_item';
import AutocompleteSectionHeader from 'app/components/autocomplete/autocomplete_section_header';
import SpecialMentionItem from 'app/components/autocomplete/special_mention_item';
import GroupMentionItem from 'app/components/autocomplete/at_mention_group/at_mention_group';
import {makeStyleSheetFromTheme} from 'app/utils/theme';
import {t} from 'app/utils/i18n';
import {debounce} from '@mm-redux/actions/helpers';
import {makeStyleSheetFromTheme} from '@utils/theme';
import {t} from '@utils/i18n';
export default class AtMention extends PureComponent {
static propTypes = {
@@ -54,9 +54,15 @@ export default class AtMention extends PureComponent {
sections: [],
};
}
runSearch = debounce((currentTeamId, channelId, matchTerm) => {
this.props.actions.autocompleteUsers(matchTerm, currentTeamId, channelId);
}, 200);
updateSections(sections) {
this.setState({sections});
}
componentDidUpdate(prevProps, prevState) {
if (this.props.matchTerm !== prevProps.matchTerm) {
if (this.props.matchTerm === null) {
@@ -68,9 +74,9 @@ export default class AtMention extends PureComponent {
this.props.onResultCountChange(sections.reduce((total, section) => total + section.data.length, 0));
// Update user autocomplete list with results of server request
const {currentTeamId, currentChannelId} = this.props;
const {currentTeamId, currentChannelId, matchTerm} = this.props;
const channelId = this.props.isSearch ? '' : currentChannelId;
this.props.actions.autocompleteUsers(this.props.matchTerm, currentTeamId, channelId);
this.runSearch(currentTeamId, channelId, matchTerm);
}
}
if (this.props.matchTerm !== null && this.props.matchTerm === prevProps.matchTerm) {
@@ -251,15 +257,16 @@ export default class AtMention extends PureComponent {
return (
<SectionList
testID='at_mention_suggestion.list'
keyboardShouldPersistTaps='always'
keyExtractor={this.keyExtractor}
style={[style.listView, {maxHeight: maxListHeight}]}
sections={sections}
renderItem={this.renderItem}
renderSectionHeader={this.renderSectionHeader}
initialNumToRender={10}
nestedScrollEnabled={nestedScrollEnabled}
removeClippedSubviews={Platform.OS === 'android'}
renderItem={this.renderItem}
renderSectionHeader={this.renderSectionHeader}
style={[style.listView, {maxHeight: maxListHeight}]}
sections={sections}
testID='at_mention_suggestion.list'
/>
);
}

View File

@@ -17,7 +17,6 @@ import {t} from 'app/utils/i18n';
export default class ChannelMention extends PureComponent {
static propTypes = {
actions: PropTypes.shape({
searchChannels: PropTypes.func.isRequired,
autocompleteChannelsForSearch: PropTypes.func.isRequired,
}).isRequired,
currentTeamId: PropTypes.string.isRequired,
@@ -225,15 +224,16 @@ export default class ChannelMention extends PureComponent {
return (
<SectionList
testID='channel_mention_suggestion.list'
keyboardShouldPersistTaps='always'
keyExtractor={this.keyExtractor}
style={[style.listView, {maxHeight: maxListHeight}]}
sections={sections}
renderItem={this.renderItem}
renderSectionHeader={this.renderSectionHeader}
initialNumToRender={10}
nestedScrollEnabled={nestedScrollEnabled}
removeClippedSubviews={Platform.OS === 'android'}
renderItem={this.renderItem}
renderSectionHeader={this.renderSectionHeader}
style={[style.listView, {maxHeight: maxListHeight}]}
sections={sections}
testID='channel_mention_suggestion.list'
/>
);
}

View File

@@ -4,7 +4,7 @@
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {searchChannels, autocompleteChannelsForSearch} from '@mm-redux/actions/channels';
import {autocompleteChannelsForSearch} from '@mm-redux/actions/channels';
import {getMyChannelMemberships} from '@mm-redux/selectors/entities/channels';
import {getTheme} from '@mm-redux/selectors/entities/preferences';
import {getCurrentTeamId} from '@mm-redux/selectors/entities/teams';
@@ -56,7 +56,6 @@ function mapStateToProps(state, ownProps) {
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
searchChannels,
autocompleteChannelsForSearch,
}, dispatch),
};

View File

@@ -3036,7 +3036,7 @@ exports[`components/autocomplete/emoji_suggestion should match snapshot 2`] = `
numColumns={1}
onEndReachedThreshold={2}
pageSize={10}
removeClippedSubviews={false}
removeClippedSubviews={true}
renderItem={[Function]}
scrollEventThrottle={50}
style={

View File

@@ -230,6 +230,7 @@ export default class EmojiSuggestion extends PureComponent {
extraData={this.state}
data={this.state.dataSource}
keyExtractor={this.keyExtractor}
removeClippedSubviews={true}
renderItem={this.renderItem}
pageSize={10}
initialListSize={10}

View File

@@ -37,7 +37,7 @@ exports[`components/autocomplete/slash_suggestion should match snapshot 1`] = `
nestedScrollEnabled={false}
numColumns={1}
onEndReachedThreshold={2}
removeClippedSubviews={false}
removeClippedSubviews={true}
renderItem={[Function]}
scrollEventThrottle={50}
style={

View File

@@ -416,7 +416,7 @@ describe('AppCommandParser', () => {
{
title: 'error: unexpected positional',
command: '/jira issue create wrong',
submit: {expectError: 'Command does not accept {positionX} positional arguments.'},
submit: {expectError: 'Unable to identify argument.'},
},
{
title: 'error: multiple equal signs',
@@ -443,11 +443,6 @@ describe('AppCommandParser', () => {
});
describe('getSuggestions', () => {
test('just the app command', async () => {
const suggestions = await parser.getSuggestions('/jira');
expect(suggestions).toEqual([]);
});
test('subcommand 1', async () => {
const suggestions = await parser.getSuggestions('/jira ');
expect(suggestions).toEqual([
@@ -574,7 +569,7 @@ describe('AppCommandParser', () => {
Description: 'The Jira issue key',
Hint: '',
IconData: '',
Suggestion: '',
Suggestion: 'issue: ""',
},
]);
});
@@ -726,7 +721,7 @@ describe('AppCommandParser', () => {
Description: 'The Jira issue summary',
Hint: '',
IconData: 'Create icon',
Suggestion: '',
Suggestion: 'summary: ""',
},
]);
});
@@ -739,7 +734,7 @@ describe('AppCommandParser', () => {
Description: 'The Jira issue summary',
Hint: '',
IconData: 'Create icon',
Suggestion: 'Sum',
Suggestion: 'summary: "Sum"',
},
]);
});
@@ -752,7 +747,7 @@ describe('AppCommandParser', () => {
Description: 'The Jira issue summary',
Hint: '',
IconData: 'Create icon',
Suggestion: 'Sum',
Suggestion: 'summary: "Sum"',
},
]);
});
@@ -765,7 +760,7 @@ describe('AppCommandParser', () => {
Description: 'The Jira issue summary',
Hint: '',
IconData: 'Create icon',
Suggestion: 'Sum',
Suggestion: 'summary: `Sum`',
},
]);
});
@@ -778,7 +773,7 @@ describe('AppCommandParser', () => {
Description: 'The Jira issue summary',
Hint: '',
IconData: 'Create icon',
Suggestion: '',
Suggestion: 'summary: ""',
},
]);
});
@@ -836,9 +831,9 @@ describe('AppCommandParser', () => {
{
Complete: 'jira issue create --project KT --summary "great feature" --epic',
Suggestion: '',
Description: 'No matching options.',
Hint: '',
IconData: '',
Description: '',
Hint: 'No matching options.',
IconData: 'error',
},
]);
});

View File

@@ -37,6 +37,7 @@ import {
getChannelByNameAndTeamName,
getCurrentTeam,
selectChannelByName,
errorMessage as parserErrorMessage,
} from './app_command_parser_dependencies';
export type Store = {
@@ -65,7 +66,7 @@ export const ParseState = keyMirror({
});
interface FormsCache {
getForm: (location: string, binding: AppBinding) => Promise<AppForm | undefined>;
getForm: (location: string, binding: AppBinding) => Promise<{form?: AppForm, error?: string} | undefined>;
}
interface Intl {
@@ -100,17 +101,6 @@ export class ParsedCommand {
return this;
};
errorMessage = (): string => {
return this.intl.formatMessage({
id: 'apps.error.parser',
defaultMessage: 'Parsing error: {error}.\n```\n{command}\n{space}^\n```',
}, {
error: this.error,
command: this.command,
space: ' '.repeat(this.i),
});
}
// matchBinding finds the closest matching command binding.
matchBinding = async (commandBindings: AppBinding[], autocompleteMode = false): Promise<ParsedCommand> => {
if (commandBindings.length === 0) {
@@ -225,7 +215,11 @@ export class ParsedCommand {
this.form = this.binding.form;
if (!this.form) {
this.form = await this.formsCache.getForm(this.location, this.binding);
const fetched = await this.formsCache.getForm(this.location, this.binding);
if (fetched?.error) {
return this.asError(fetched.error);
}
this.form = fetched?.form;
}
return this;
@@ -273,9 +267,7 @@ export class ParsedCommand {
if (!field) {
return this.asError(this.intl.formatMessage({
id: 'apps.error.parser.no_argument_pos_x',
defaultMessage: 'Command does not accept {positionX} positional arguments.',
}, {
positionX: this.position,
defaultMessage: 'Unable to identify argument.',
}));
}
this.field = field;
@@ -568,7 +560,7 @@ export class AppCommandParser {
parsed = await parsed.matchBinding(commandBindings, false);
parsed = parsed.parseForm(false);
if (parsed.state === ParseState.Error) {
return {call: null, errorMessage: parsed.errorMessage()};
return {call: null, errorMessage: parserErrorMessage(this.intl, parsed.error, parsed.command, parsed.i)};
}
const missing = this.getMissingFields(parsed);
@@ -592,8 +584,9 @@ export class AppCommandParser {
const result: AutocompleteSuggestion[] = [];
const bindings = this.getCommandBindings();
for (const binding of bindings) {
let base = binding.app_id;
let base = binding.label;
if (!base) {
continue;
}
@@ -601,10 +594,11 @@ export class AppCommandParser {
if (base[0] !== '/') {
base = '/' + base;
}
if (base.startsWith(command)) {
result.push({
Complete: binding.label,
Suggestion: base,
Complete: base.substring(1),
Description: binding.description || '',
Hint: binding.hint || '',
IconData: binding.icon || '',
@@ -618,6 +612,7 @@ export class AppCommandParser {
// getSuggestions returns suggestions for subcommands and/or form arguments
public getSuggestions = async (pretext: string): Promise<AutocompleteSuggestion[]> => {
let parsed = new ParsedCommand(pretext, this, this.intl);
let suggestions: AutocompleteSuggestion[] = [];
const commandBindings = this.getCommandBindings();
if (!commandBindings) {
@@ -625,13 +620,19 @@ export class AppCommandParser {
}
parsed = await parsed.matchBinding(commandBindings, true);
let suggestions: AutocompleteSuggestion[] = [];
if (parsed.state === ParseState.Error) {
suggestions = this.getErrorSuggestion(parsed);
}
if (parsed.state === ParseState.Command) {
suggestions = this.getCommandSuggestions(parsed);
}
if (parsed.form || parsed.incomplete) {
parsed = parsed.parseForm(true);
if (parsed.state === ParseState.Error) {
suggestions = this.getErrorSuggestion(parsed);
}
const argSuggestions = await this.getParameterSuggestions(parsed);
suggestions = suggestions.concat(argSuggestions);
}
@@ -654,11 +655,38 @@ export class AppCommandParser {
if (execute) {
suggestions = [execute, ...suggestions];
}
} else if (suggestions.length === 0 && (parsed.field?.type !== AppFieldTypes.USER && parsed.field?.type !== AppFieldTypes.CHANNEL)) {
suggestions = this.getNoMatchingSuggestion();
}
return suggestions.map((suggestion) => this.decorateSuggestionComplete(parsed, suggestion));
}
getNoMatchingSuggestion = () => {
return [{
Complete: '',
Suggestion: '',
Hint: this.intl.formatMessage({
id: 'apps.suggestion.no_suggestion',
defaultMessage: 'No matching suggestions.',
}),
IconData: 'error',
Description: '',
}];
}
getErrorSuggestion = (parsed: ParsedCommand) => {
return [{
Complete: '',
Suggestion: '',
Hint: this.intl.formatMessage({
id: 'apps.suggestion.errors.parser_error',
defaultMessage: 'Parsing error',
}),
IconData: 'error',
Description: parsed.error,
}];
}
// composeCallFromParsed creates the form submission call
composeCallFromParsed = async (parsed: ParsedCommand): Promise<{call: AppCallRequest | null, errorMessage?: string}> => {
if (!parsed.binding) {
@@ -789,7 +817,7 @@ export class AppCommandParser {
goBackSpace = 1;
}
let complete = parsed.command.substring(0, parsed.incompleteStart - goBackSpace);
complete += choice.Complete || choice.Suggestion;
complete += choice.Complete === undefined ? choice.Suggestion : choice.Complete;
choice.Hint = choice.Hint || '';
complete = complete.substring(1);
@@ -821,7 +849,7 @@ export class AppCommandParser {
isAppCommand = (pretext: string): boolean => {
const command = pretext.toLowerCase();
for (const binding of this.getCommandBindings()) {
let base = binding.app_id;
let base = binding.label;
if (!base) {
continue;
}
@@ -857,7 +885,7 @@ export class AppCommandParser {
}
// fetchForm unconditionaly retrieves the form for the given binding (subcommand)
fetchForm = async (binding: AppBinding): Promise<AppForm | undefined> => {
fetchForm = async (binding: AppBinding): Promise<{form?: AppForm, error?: string} | undefined> => {
if (!binding.call) {
return undefined;
}
@@ -870,11 +898,10 @@ export class AppCommandParser {
const res = await this.store.dispatch(doAppCall(payload, AppCallTypes.FORM, this.intl));
if (res.error) {
const errorResponse = res.error as AppCallResponse;
this.displayError(errorResponse.error || this.intl.formatMessage({
return {error: errorResponse.error || this.intl.formatMessage({
id: 'apps.error.unknown',
defaultMessage: 'Unknown error.',
}));
return undefined;
})};
}
const callResponse = res.data as AppCallResponse;
@@ -883,35 +910,33 @@ export class AppCommandParser {
break;
case AppCallResponseTypes.NAVIGATE:
case AppCallResponseTypes.OK:
this.displayError(this.intl.formatMessage({
return {error: this.intl.formatMessage({
id: 'apps.error.responses.unexpected_type',
defaultMessage: 'App response type was not expected. Response type: {type}',
}, {
type: callResponse.type,
}));
return undefined;
})};
default:
this.displayError(this.intl.formatMessage({
return {error: this.intl.formatMessage({
id: 'apps.error.responses.unknown_type',
defaultMessage: 'App response type not supported. Response type: {type}.',
}, {
type: callResponse.type,
}));
return undefined;
})};
}
return callResponse.form;
return {form: callResponse.form};
}
getForm = async (location: string, binding: AppBinding): Promise<AppForm | undefined> => {
getForm = async (location: string, binding: AppBinding): Promise<{form?: AppForm, error?: string} | undefined> => {
const form = this.forms[location];
if (form) {
return form;
return {form};
}
const fetched = await this.fetchForm(binding);
if (fetched) {
this.forms[location] = fetched;
if (fetched?.form) {
this.forms[location] = fetched.form;
}
return fetched;
}
@@ -1054,7 +1079,7 @@ export class AppCommandParser {
return [{
Complete: complete,
Suggestion: parsed.incomplete,
Suggestion: `${parsed.field.label || parsed.field.name}: ${delimiter || '"'}${parsed.incomplete}${delimiter || '"'}`,
Description: f.description || '',
Hint: '',
IconData: parsed.binding?.icon || '',
@@ -1069,12 +1094,12 @@ export class AppCommandParser {
return [{
Complete: '',
Suggestion: '',
Hint: '',
Description: this.intl.formatMessage({
Hint: this.intl.formatMessage({
id: 'apps.suggestion.no_static',
defaultMessage: 'No matching options.',
}),
IconData: '',
Description: '',
IconData: 'error',
}];
}
return opts.map((opt) => {
@@ -1099,7 +1124,7 @@ export class AppCommandParser {
const f = parsed.field;
if (!f) {
// Should never happen
return this.makeSuggestionError(this.intl.formatMessage({
return this.makeDynamicSelectSuggestionError(this.intl.formatMessage({
id: 'apps.error.parser.unexpected_error',
defaultMessage: 'Unexpected error.',
}));
@@ -1107,7 +1132,7 @@ export class AppCommandParser {
const {call, errorMessage} = await this.composeCallFromParsed(parsed);
if (!call) {
return this.makeSuggestionError(this.intl.formatMessage({
return this.makeDynamicSelectSuggestionError(this.intl.formatMessage({
id: 'apps.error.lookup.error_preparing_request',
defaultMessage: 'Error preparing lookup request: {errorMessage}',
}, {
@@ -1121,7 +1146,7 @@ export class AppCommandParser {
const res = await this.store.dispatch(doAppCall<ResponseType>(call, AppCallTypes.LOOKUP, this.intl));
if (res.error) {
const errorResponse = res.error as AppCallResponse;
return this.makeSuggestionError(errorResponse.error || this.intl.formatMessage({
return this.makeDynamicSelectSuggestionError(errorResponse.error || this.intl.formatMessage({
id: 'apps.error.unknown',
defaultMessage: 'Unknown error.',
}));
@@ -1133,14 +1158,14 @@ export class AppCommandParser {
break;
case AppCallResponseTypes.NAVIGATE:
case AppCallResponseTypes.FORM:
return this.makeSuggestionError(this.intl.formatMessage({
return this.makeDynamicSelectSuggestionError(this.intl.formatMessage({
id: 'apps.error.responses.unexpected_type',
defaultMessage: 'App response type was not expected. Response type: {type}',
}, {
type: callResponse.type,
}));
default:
return this.makeSuggestionError(this.intl.formatMessage({
return this.makeDynamicSelectSuggestionError(this.intl.formatMessage({
id: 'apps.error.responses.unknown_type',
defaultMessage: 'App response type not supported. Response type: {type}.',
}, {
@@ -1153,7 +1178,10 @@ export class AppCommandParser {
return [{
Complete: '',
Suggestion: '',
Hint: '',
Hint: this.intl.formatMessage({
id: 'apps.suggestion.no_static',
defaultMessage: 'No matching options.',
}),
IconData: '',
Description: this.intl.formatMessage({
id: 'apps.suggestion.no_dynamic',
@@ -1179,7 +1207,7 @@ export class AppCommandParser {
});
}
makeSuggestionError = (message: string): AutocompleteSuggestion[] => {
makeDynamicSelectSuggestionError = (message: string): AutocompleteSuggestion[] => {
const errMsg = this.intl.formatMessage({
id: 'apps.error',
defaultMessage: 'Error: {error}',
@@ -1188,9 +1216,12 @@ export class AppCommandParser {
});
return [{
Complete: '',
Suggestion: '',
Suggestion: this.intl.formatMessage({
id: 'apps.suggestion.dynamic.error',
defaultMessage: 'Dynamic select error',
}),
Hint: '',
IconData: '',
IconData: 'error',
Description: errMsg,
}];
}

View File

@@ -76,3 +76,12 @@ export const displayError = (intl: typeof intlShape, body: string) => {
});
Alert.alert(title, body);
};
export const errorMessage = (intl: typeof intlShape, error: string, _command: string, _position: number): string => { // eslint-disable-line @typescript-eslint/no-unused-vars
return intl.formatMessage({
id: 'apps.error.parser',
defaultMessage: 'Parsing error: {error}',
}, {
error,
});
};

View File

@@ -8,7 +8,7 @@ import {
Platform,
} from 'react-native';
import {analytics} from '@init/analytics.ts';
import {analytics} from '@init/analytics';
import {Client4} from '@mm-redux/client';
import {isMinimumServerVersion} from '@mm-redux/utils/helpers';
import {Command, AutocompleteSuggestion, CommandArgs} from '@mm-redux/types/integrations';
@@ -243,6 +243,7 @@ export default class SlashSuggestion extends PureComponent<Props, State> {
theme={this.props.theme}
suggestion={item.Suggestion}
complete={item.Complete}
icon={item.IconData}
/>
)
@@ -265,6 +266,7 @@ export default class SlashSuggestion extends PureComponent<Props, State> {
extraData={this.state}
data={this.state.dataSource}
keyExtractor={this.keyExtractor}
removeClippedSubviews={true}
renderItem={this.renderItem}
nestedScrollEnabled={nestedScrollEnabled}
/>

View File

@@ -9,7 +9,9 @@ import {Theme} from '@mm-redux/types/preferences';
import TouchableWithFeedback from '@components/touchable_with_feedback';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import FastImage from 'react-native-fast-image';
const slashIcon = require('@assets/images/autocomplete/slash_command.png');
const bangIcon = require('@assets/images/autocomplete/slash_command_error.png');
const getStyleFromTheme = makeStyleSheetFromTheme((theme: Theme) => {
return {
@@ -55,6 +57,7 @@ type Props = {
hint: string;
onPress: (complete: string) => void;
suggestion: string;
icon: string;
theme: Theme;
}
@@ -88,6 +91,32 @@ const SlashSuggestionItem = (props: Props) => {
}
}
let image = (
<Image
style={style.iconColor}
width={10}
height={16}
source={slashIcon}
/>
);
if (props.icon === 'error') {
image = (
<Image
style={style.iconColor}
width={10}
height={16}
source={bangIcon}
/>
);
} else if (props.icon && props.icon.startsWith('http')) {
image = (
<FastImage
source={{uri: props.icon}}
style={{width: 16, height: 16}}
/>
);
}
return (
<TouchableWithFeedback
onPress={completeSuggestion}
@@ -97,12 +126,7 @@ const SlashSuggestionItem = (props: Props) => {
>
<View style={style.container}>
<View style={style.icon}>
<Image
style={style.iconColor}
width={10}
height={16}
source={slashIcon}
/>
{image}
</View>
<View style={style.suggestionContainer}>
<Text style={style.suggestionName}>{`${suggestionText}`}</Text>

View File

@@ -29,6 +29,35 @@ exports[`Avatars should match snapshot for overflow 1`] = `
showStatus={false}
size={24}
testID="avatars.profile_picture"
theme={
Object {
"awayIndicator": "#ffbc42",
"buttonBg": "#166de0",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3d3c40",
"codeTheme": "github",
"dndIndicator": "#f74343",
"errorTextColor": "#fd5960",
"linkColor": "#2389d7",
"mentionBg": "#ffffff",
"mentionBj": "#ffffff",
"mentionColor": "#145dbf",
"mentionHighlightBg": "#ffe577",
"mentionHighlightLink": "#166de0",
"newMessageSeparator": "#ff8800",
"onlineIndicator": "#06d6a0",
"sidebarBg": "#145dbf",
"sidebarHeaderBg": "#1153ab",
"sidebarHeaderTextColor": "#ffffff",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#579eff",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#4578bf",
"sidebarUnreadText": "#ffffff",
"type": "Mattermost",
}
}
userId="user1"
/>
</View>
@@ -51,6 +80,35 @@ exports[`Avatars should match snapshot for overflow 1`] = `
showStatus={false}
size={24}
testID="avatars.profile_picture"
theme={
Object {
"awayIndicator": "#ffbc42",
"buttonBg": "#166de0",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3d3c40",
"codeTheme": "github",
"dndIndicator": "#f74343",
"errorTextColor": "#fd5960",
"linkColor": "#2389d7",
"mentionBg": "#ffffff",
"mentionBj": "#ffffff",
"mentionColor": "#145dbf",
"mentionHighlightBg": "#ffe577",
"mentionHighlightLink": "#166de0",
"newMessageSeparator": "#ff8800",
"onlineIndicator": "#06d6a0",
"sidebarBg": "#145dbf",
"sidebarHeaderBg": "#1153ab",
"sidebarHeaderTextColor": "#ffffff",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#579eff",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#4578bf",
"sidebarUnreadText": "#ffffff",
"type": "Mattermost",
}
}
userId="user2"
/>
</View>
@@ -73,6 +131,35 @@ exports[`Avatars should match snapshot for overflow 1`] = `
showStatus={false}
size={24}
testID="avatars.profile_picture"
theme={
Object {
"awayIndicator": "#ffbc42",
"buttonBg": "#166de0",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3d3c40",
"codeTheme": "github",
"dndIndicator": "#f74343",
"errorTextColor": "#fd5960",
"linkColor": "#2389d7",
"mentionBg": "#ffffff",
"mentionBj": "#ffffff",
"mentionColor": "#145dbf",
"mentionHighlightBg": "#ffe577",
"mentionHighlightLink": "#166de0",
"newMessageSeparator": "#ff8800",
"onlineIndicator": "#06d6a0",
"sidebarBg": "#145dbf",
"sidebarHeaderBg": "#1153ab",
"sidebarHeaderTextColor": "#ffffff",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#579eff",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#4578bf",
"sidebarUnreadText": "#ffffff",
"type": "Mattermost",
}
}
userId="user3"
/>
</View>
@@ -151,6 +238,35 @@ exports[`Avatars should match snapshot for single avatar 1`] = `
showStatus={false}
size={24}
testID="avatars.profile_picture"
theme={
Object {
"awayIndicator": "#ffbc42",
"buttonBg": "#166de0",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3d3c40",
"codeTheme": "github",
"dndIndicator": "#f74343",
"errorTextColor": "#fd5960",
"linkColor": "#2389d7",
"mentionBg": "#ffffff",
"mentionBj": "#ffffff",
"mentionColor": "#145dbf",
"mentionHighlightBg": "#ffe577",
"mentionHighlightLink": "#166de0",
"newMessageSeparator": "#ff8800",
"onlineIndicator": "#06d6a0",
"sidebarBg": "#145dbf",
"sidebarHeaderBg": "#1153ab",
"sidebarHeaderTextColor": "#ffffff",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#579eff",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#4578bf",
"sidebarUnreadText": "#ffffff",
"type": "Mattermost",
}
}
userId="user1"
/>
</View>

View File

@@ -109,6 +109,7 @@ export default class Avatars extends PureComponent<AvatarsProps> {
size={ViewTypes.AVATAR_LIST_PICTURE_SIZE}
showStatus={false}
testID='avatars.profile_picture'
theme={theme}
/>
</View>
))}

View File

@@ -6,10 +6,9 @@ import PropTypes from 'prop-types';
import {Text} from 'react-native';
import {intlShape} from 'react-intl';
import CustomPropTypes from 'app/constants/custom_prop_types';
import {t} from 'app/utils/i18n';
import {alertErrorWithFallback} from 'app/utils/general';
import {popToRoot, dismissAllModals} from 'app/actions/navigation';
import {popToRoot, dismissAllModals} from '@actions/navigation';
import {t} from '@utils/i18n';
import {alertErrorWithFallback} from '@utils/general';
import {getChannelFromChannelName} from './channel_link_utils';
@@ -19,9 +18,9 @@ export default class ChannelLink extends React.PureComponent {
channelMentions: PropTypes.object,
currentTeamId: PropTypes.string.isRequired,
currentUserId: PropTypes.string.isRequired,
linkStyle: CustomPropTypes.Style,
linkStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
onChannelLinkPress: PropTypes.func,
textStyle: CustomPropTypes.Style,
textStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
channelsByName: PropTypes.object.isRequired,
actions: PropTypes.shape({
handleSelectChannel: PropTypes.func.isRequired,

View File

@@ -14,7 +14,6 @@ import * as RNPlaceholder from 'rn-placeholder';
import {SafeAreaView} from 'react-native-safe-area-context';
import FormattedText from '@components/formatted_text';
import CustomPropTypes from '@constants/custom_prop_types';
import {INDICATOR_BAR_HEIGHT} from '@constants/view';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
@@ -39,7 +38,7 @@ export default class ChannelLoader extends PureComponent {
static propTypes = {
backgroundColor: PropTypes.string,
channelIsLoading: PropTypes.bool.isRequired,
style: CustomPropTypes.Style,
style: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
theme: PropTypes.object.isRequired,
height: PropTypes.number,
retryLoad: PropTypes.func,

View File

@@ -5,12 +5,10 @@ import PropTypes from 'prop-types';
import React from 'react';
import {TouchableOpacity} from 'react-native';
import CustomPropTypes from 'app/constants/custom_prop_types';
export default class ConditionalTouchable extends React.PureComponent {
static propTypes = {
touchable: PropTypes.bool,
children: CustomPropTypes.Children.isRequired,
children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf([PropTypes.node])]),
};
render() {

View File

@@ -10,7 +10,6 @@ import {
import CompassIcon from '@components/compass_icon';
import ConditionalTouchable from '@components/conditional_touchable';
import CustomPropTypes from '@constants/custom_prop_types';
export default class CustomListRow extends React.PureComponent {
static propTypes = {
@@ -18,7 +17,7 @@ export default class CustomListRow extends React.PureComponent {
enabled: PropTypes.bool,
selectable: PropTypes.bool,
selected: PropTypes.bool,
children: CustomPropTypes.Children,
children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf([PropTypes.node])]),
testID: PropTypes.string,
};

View File

@@ -16,12 +16,13 @@ import {Post} from '@mm-redux/types/posts';
import {AppExpandLevels, AppBindingLocations, AppCallTypes, AppCallResponseTypes} from '@mm-redux/constants/apps';
import {createCallContext, createCallRequest} from '@utils/apps';
import {Channel} from '@mm-redux/types/channels';
import {SendEphemeralPost} from 'types/actions/posts';
type Props = {
actions: {
doAppCall: (call: AppCallRequest, type: AppCallType, intl: any) => Promise<{data?: AppCallResponse, error?: AppCallResponse}>;
getChannel: (channelId: string) => Promise<ActionResult>;
sendEphemeralPost: (message: any, channelId?: string, parentId?: string) => Promise<ActionResult>;
sendEphemeralPost: SendEphemeralPost;
};
post: Post;
binding: AppBinding;
@@ -32,9 +33,10 @@ export default class ButtonBinding extends PureComponent<Props> {
static contextTypes = {
intl: intlShape.isRequired,
};
handleActionPress = preventDoubleTap(async () => {
const ephemeral = (message: string) => this.props.actions.sendEphemeralPost(message, this.props.post.channel_id, this.props.post.root_id);
private mounted = false;
handleActionPress = preventDoubleTap(async () => {
const {
binding,
post,
@@ -66,8 +68,11 @@ export default class ButtonBinding extends PureComponent<Props> {
);
this.setState({executing: true});
const res = await this.props.actions.doAppCall(call, AppCallTypes.SUBMIT, this.context.intl);
this.setState({executing: false});
if (this.mounted) {
this.setState({executing: false});
}
const ephemeral = (message: string) => this.props.actions.sendEphemeralPost(message, this.props.post.channel_id, this.props.post.root_id, res.data?.app_metadata?.bot_user_id);
if (res.error) {
const errorResponse = res.error;
const errorMessage = errorResponse.error || intl.formatMessage(
@@ -104,6 +109,14 @@ export default class ButtonBinding extends PureComponent<Props> {
}
}, 4000);
componentDidMount() {
this.mounted = true;
}
componentWillUnmount() {
this.mounted = false;
}
render() {
const {theme, binding} = this.props;
const style = getStyleSheet(theme);

View File

@@ -3,12 +3,13 @@
import React from 'react';
import PropTypes from 'prop-types';
import {Text, View, StyleSheet, StyleProp, TextStyle} from 'react-native';
import Emoji from 'app/components/emoji';
import {getEmoticonName} from 'app/utils/emoji_utils';
import {reEmoji, reEmoticon, reMain} from 'app/constants/emoji';
import {Text, View, StyleSheet, StyleProp} from 'react-native';
export default function ButtonBindingText({message, style}: {message: string; style: StyleProp<TextStyle>}) {
import Emoji from '@components/emoji';
import {reEmoji, reEmoticon, reMain} from '@constants/emoji';
import {getEmoticonName} from '@utils/emoji_utils';
export default function ButtonBindingText({message, style}: {message: string; style: StyleProp<any>}) {
const components = [] as JSX.Element[];
let text = message;

View File

@@ -16,6 +16,7 @@ import ButtonBinding from './button_binding';
import {getChannel} from '@mm-redux/actions/channels';
import {sendEphemeralPost} from '@actions/views/post';
import {getCurrentTeamId} from '@mm-redux/selectors/entities/teams';
import {SendEphemeralPost} from 'types/actions/posts';
type OwnProps = {
postId: string;
@@ -32,7 +33,7 @@ function mapStateToProps(state: GlobalState, ownProps: OwnProps) {
type Actions = {
doAppCall: (call: AppCallRequest, type: AppCallType, intl: any) => Promise<{data?: AppCallResponse, error?: AppCallResponse}>;
getChannel: (channelId: string) => Promise<ActionResult>;
sendEphemeralPost: (message: any, channelId?: string, parentId?: string) => Promise<ActionResult>;
sendEphemeralPost: SendEphemeralPost;
}
function mapDispatchToProps(dispatch: Dispatch<GenericAction>) {

View File

@@ -2,20 +2,20 @@
// See LICENSE.txt for license information.
import React, {PureComponent} from 'react';
import {LayoutChangeEvent, ScrollView, StyleProp, TextStyle, View, ViewStyle} from 'react-native';
import {LayoutChangeEvent, ScrollView, StyleProp, View} from 'react-native';
import Markdown from 'app/components/markdown';
import ShowMoreButton from 'app/components/show_more_button';
import Markdown from '@components/markdown';
import ShowMoreButton from '@components/show_more_button';
import {Theme} from '@mm-redux/types/preferences';
const SHOW_MORE_HEIGHT = 60;
type Props = {
baseTextStyle: StyleProp<TextStyle>,
blockStyles?: StyleProp<ViewStyle>[],
baseTextStyle: StyleProp<any>,
blockStyles?: StyleProp<any>[],
deviceHeight: number,
onPermalinkPress?: () => void,
textStyles?: StyleProp<TextStyle>[],
textStyles?: StyleProp<any>[],
theme?: Theme,
value?: string,
}

View File

@@ -8,6 +8,7 @@ import {GlobalState} from '@mm-redux/types/store';
import {doAppCall} from '@actions/apps';
import {ActionFunc, ActionResult, GenericAction} from '@mm-redux/types/actions';
import {AppCallRequest, AppCallResponse, AppCallType} from '@mm-redux/types/apps';
import {SendEphemeralPost} from 'types/actions/posts';
import {getPost} from '@mm-redux/selectors/entities/posts';
import MenuBinding from './menu_binding';
@@ -29,7 +30,7 @@ function mapStateToProps(state: GlobalState, ownProps: OwnProps) {
type Actions = {
doAppCall: (call: AppCallRequest, type: AppCallType, intl: any) => Promise<{data?: AppCallResponse, error?: AppCallResponse}>;
getChannel: (channelId: string) => Promise<ActionResult>;
sendEphemeralPost: (message: any, channelId?: string, parentId?: string) => Promise<ActionResult>;
sendEphemeralPost: SendEphemeralPost;
}
function mapDispatchToProps(dispatch: Dispatch<GenericAction>) {

View File

@@ -12,12 +12,13 @@ import {ActionResult} from '@mm-redux/types/actions';
import {AppExpandLevels, AppBindingLocations, AppCallTypes, AppCallResponseTypes} from '@mm-redux/constants/apps';
import {Channel} from '@mm-redux/types/channels';
import {createCallContext, createCallRequest} from '@utils/apps';
import {SendEphemeralPost} from 'types/actions/posts';
type Props = {
actions: {
doAppCall: (call: AppCallRequest, type: AppCallType, intl: any) => Promise<{data?: AppCallResponse, error?: AppCallResponse}>;
getChannel: (channelId: string) => Promise<ActionResult>;
sendEphemeralPost: (message: any, channelId?: string, parentId?: string) => Promise<ActionResult>;
sendEphemeralPost: SendEphemeralPost;
};
binding?: AppBinding;
post: Post;
@@ -42,7 +43,6 @@ export default class MenuBinding extends PureComponent<Props, State> {
if (!selected) {
return;
}
const ephemeral = (message: string) => this.props.actions.sendEphemeralPost(message, this.props.post.channel_id, this.props.post.root_id);
this.setState({selected});
const binding = this.props.binding?.bindings?.find((b) => b.location === selected.value);
@@ -83,6 +83,8 @@ export default class MenuBinding extends PureComponent<Props, State> {
);
const res = await actions.doAppCall(call, AppCallTypes.SUBMIT, this.context.intl);
const ephemeral = (message: string) => this.props.actions.sendEphemeralPost(message, this.props.post.channel_id, this.props.post.root_id, res.data?.app_metadata?.bot_user_id);
if (res.error) {
const errorResponse = res.error;
const errorMessage = errorResponse.error || intl.formatMessage({

View File

@@ -10,8 +10,6 @@ import {
} from 'react-native';
import FastImage from 'react-native-fast-image';
import CustomPropTypes from 'app/constants/custom_prop_types';
export default class Emoji extends React.PureComponent {
static propTypes = {
@@ -36,9 +34,9 @@ export default class Emoji extends React.PureComponent {
displayTextOnly: PropTypes.bool,
literal: PropTypes.string,
size: PropTypes.number,
textStyle: CustomPropTypes.Style,
textStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
unicode: PropTypes.string,
customEmojiStyle: CustomPropTypes.Style,
customEmojiStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
testID: PropTypes.string,
};

View File

@@ -103,7 +103,7 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
onScroll={[Function]}
onScrollToIndexFailed={[Function]}
pageSize={50}
removeClippedSubviews={false}
removeClippedSubviews={true}
renderItem={[Function]}
renderSectionHeader={[Function]}
scrollEventThrottle={50}

View File

@@ -264,15 +264,16 @@ export default class EmojiPicker extends PureComponent {
listComponent = (
<FlatList
contentContainerStyle={contentContainerStyle}
data={filteredEmojis}
initialListSize={10}
keyboardShouldPersistTaps='always'
keyExtractor={this.flatListKeyExtractor}
initialListSize={10}
ListEmptyComponent={this.renderEmptyList}
nativeID={SCROLLVIEW_NATIVE_ID}
pageSize={10}
renderItem={this.flatListRenderItem}
ListEmptyComponent={this.renderEmptyList}
contentContainerStyle={contentContainerStyle}
removeClippedSubviews={true}
style={styles.flatList}
/>
);
@@ -292,7 +293,7 @@ export default class EmojiPicker extends PureComponent {
onScroll={this.onScroll}
onScrollToIndexFailed={this.handleScrollToSectionFailed}
pageSize={50}
removeClippedSubviews={false}
removeClippedSubviews={true}
renderItem={this.renderItem}
renderSectionHeader={this.renderSectionHeader}
sections={emojis}

View File

@@ -5,7 +5,6 @@ import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {Text} from 'react-native';
import CustomPropTypes from 'app/constants/custom_prop_types';
import FormattedText from 'app/components/formatted_text';
import {GlobalStyles} from 'app/styles';
import {makeStyleSheetFromTheme} from 'app/utils/theme';
@@ -14,7 +13,7 @@ export default class ErrorText extends PureComponent {
static propTypes = {
testID: PropTypes.string,
error: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
textStyle: CustomPropTypes.Style,
textStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
theme: PropTypes.object.isRequired,
};

View File

@@ -10,7 +10,6 @@ import {injectIntl, intlShape} from 'react-intl';
import {concatStyles, changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
import {getMarkdownTextStyles} from 'app/utils/markdown';
import CustomPropTypes from 'app/constants/custom_prop_types';
import AtMention from 'app/components/at_mention';
import MarkdownLink from 'app/components/markdown/markdown_link';
@@ -32,11 +31,11 @@ const TARGET_BLANK_URL_PREFIX = '!';
*/
class FormattedMarkdownText extends React.PureComponent {
static propTypes = {
baseTextStyle: CustomPropTypes.Style,
baseTextStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
defaultMessage: PropTypes.string.isRequired,
id: PropTypes.string.isRequired,
onPostPress: PropTypes.func,
style: CustomPropTypes.Style,
style: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
textStyles: PropTypes.object,
theme: PropTypes.object.isRequired,
values: PropTypes.object,

View File

@@ -6,15 +6,13 @@ import PropTypes from 'prop-types';
import {Text} from 'react-native';
import moment from 'moment-timezone';
import CustomPropTypes from 'app/constants/custom_prop_types';
export default class FormattedTime extends React.PureComponent {
static propTypes = {
value: PropTypes.any.isRequired,
timeZone: PropTypes.string,
children: PropTypes.func,
hour12: PropTypes.bool,
style: CustomPropTypes.Style,
style: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
testID: PropTypes.string,
};

View File

@@ -10,12 +10,10 @@ import {
View,
} from 'react-native';
import * as CustomPropTypes from 'app/constants/custom_prop_types';
export default class KeyboardLayout extends PureComponent {
static propTypes = {
children: PropTypes.node,
style: CustomPropTypes.Style,
style: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
testID: PropTypes.string,
};

View File

@@ -5,13 +5,12 @@ import PropTypes from 'prop-types';
import React from 'react';
import {Text} from 'react-native';
import CustomPropTypes from 'app/constants/custom_prop_types';
import {popToRoot, showSearchModal, dismissAllModals} from 'app/actions/navigation';
import {popToRoot, showSearchModal, dismissAllModals} from '@actions/navigation';
export default class Hashtag extends React.PureComponent {
static propTypes = {
hashtag: PropTypes.string.isRequired,
linkStyle: CustomPropTypes.Style.isRequired,
linkStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
onHashtagPress: PropTypes.func,
};

View File

@@ -16,7 +16,6 @@ import ChannelLink from 'app/components/channel_link';
import Emoji from 'app/components/emoji';
import FormattedText from 'app/components/formatted_text';
import Hashtag from 'app/components/markdown/hashtag';
import CustomPropTypes from 'app/constants/custom_prop_types';
import {blendColors, concatStyles, makeStyleSheetFromTheme} from 'app/utils/theme';
import {getScheme} from 'app/utils/url';
@@ -39,23 +38,23 @@ import {
export default class Markdown extends PureComponent {
static propTypes = {
autolinkedUrlSchemes: PropTypes.array.isRequired,
baseTextStyle: CustomPropTypes.Style,
autolinkedUrlSchemes: PropTypes.array,
baseTextStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
blockStyles: PropTypes.object,
channelMentions: PropTypes.object,
imagesMetadata: PropTypes.object,
isEdited: PropTypes.bool,
isReplyPost: PropTypes.bool,
isSearchResult: PropTypes.bool,
mentionKeys: PropTypes.array.isRequired,
minimumHashtagLength: PropTypes.number.isRequired,
mentionKeys: PropTypes.array,
minimumHashtagLength: PropTypes.number,
onChannelLinkPress: PropTypes.func,
onHashtagPress: PropTypes.func,
onPermalinkPress: PropTypes.func,
onPostPress: PropTypes.func,
postId: PropTypes.string,
textStyles: PropTypes.object,
theme: PropTypes.object.isRequired,
theme: PropTypes.object,
value: PropTypes.string.isRequired,
disableHashtags: PropTypes.bool,
disableAtMentions: PropTypes.bool,
@@ -155,7 +154,8 @@ export default class Markdown extends PureComponent {
};
computeTextStyle = (baseStyle, context) => {
return concatStyles(baseStyle, context.map((type) => this.props.textStyles[type]));
const contextStyles = context.map((type) => this.props.textStyles[type]).filter((f) => f !== undefined);
return contextStyles.length ? concatStyles(baseStyle, contextStyles) : baseStyle;
};
renderText = ({context, literal}) => {

View File

@@ -9,13 +9,12 @@ import {
} from 'react-native';
import CompassIcon from '@components/compass_icon';
import CustomPropTypes from '@constants/custom_prop_types';
export default class MarkdownBlockQuote extends PureComponent {
static propTypes = {
continue: PropTypes.bool,
iconStyle: CustomPropTypes.Style,
children: CustomPropTypes.Children.isRequired,
iconStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf([PropTypes.node])]),
};
render() {

View File

@@ -12,7 +12,6 @@ import {
} from 'react-native';
import Clipboard from '@react-native-community/clipboard';
import CustomPropTypes from 'app/constants/custom_prop_types';
import FormattedText from 'app/components/formatted_text';
import TouchableWithFeedback from 'app/components/touchable_with_feedback';
import BottomSheet from 'app/utils/bottom_sheet';
@@ -29,7 +28,7 @@ export default class MarkdownCodeBlock extends React.PureComponent {
theme: PropTypes.object.isRequired,
language: PropTypes.string,
content: PropTypes.string.isRequired,
textStyle: CustomPropTypes.Style,
textStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
};
static defaultProps = {

View File

@@ -9,12 +9,11 @@ import PropTypes from 'prop-types';
import Emoji from 'app/components/emoji';
import FormattedText from 'app/components/formatted_text';
import CustomPropTypes from 'app/constants/custom_prop_types';
import {blendColors, concatStyles, makeStyleSheetFromTheme} from 'app/utils/theme';
export default class MarkdownEmoji extends PureComponent {
static propTypes = {
baseTextStyle: CustomPropTypes.Style,
baseTextStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
isEdited: PropTypes.bool,
shouldRenderJumboEmoji: PropTypes.bool.isRequired,
theme: PropTypes.object.isRequired,

View File

@@ -19,7 +19,6 @@ import ImageViewPort from '@components/image_viewport';
import ProgressiveImage from '@components/progressive_image';
import FormattedText from '@components/formatted_text';
import TouchableWithFeedback from '@components/touchable_with_feedback';
import {CustomPropTypes} from '@constants';
import EphemeralStore from '@store/ephemeral_store';
import BottomSheet from '@utils/bottom_sheet';
import {generateId} from '@utils/file';
@@ -35,7 +34,7 @@ export default class MarkdownImage extends ImageViewPort {
static propTypes = {
children: PropTypes.node,
disable: PropTypes.bool,
errorTextStyle: CustomPropTypes.Style,
errorTextStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
imagesMetadata: PropTypes.object,
isReplyPost: PropTypes.bool,
linkDestination: PropTypes.string,

View File

@@ -10,7 +10,6 @@ import urlParse from 'url-parse';
import Config from '@assets/config';
import {DeepLinkTypes} from '@constants';
import CustomPropTypes from '@constants/custom_prop_types';
import {getCurrentServerUrl} from '@init/credentials';
import BottomSheet from '@utils/bottom_sheet';
import {errorBadChannel} from '@utils/draft';
@@ -24,7 +23,7 @@ export default class MarkdownLink extends PureComponent {
actions: PropTypes.shape({
handleSelectChannelByName: PropTypes.func.isRequired,
}).isRequired,
children: CustomPropTypes.Children.isRequired,
children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf([PropTypes.node])]),
href: PropTypes.string.isRequired,
onPermalinkPress: PropTypes.func,
serverURL: PropTypes.string,

View File

@@ -9,16 +9,14 @@ import {
View,
} from 'react-native';
import CustomPropTypes from 'app/constants/custom_prop_types';
export default class MarkdownListItem extends PureComponent {
static propTypes = {
children: CustomPropTypes.Children.isRequired,
children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf([PropTypes.node])]),
ordered: PropTypes.bool.isRequired,
continue: PropTypes.bool,
index: PropTypes.number.isRequired,
bulletWidth: PropTypes.number,
bulletStyle: CustomPropTypes.Style,
bulletStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
level: PropTypes.number,
};

View File

@@ -5,13 +5,12 @@ import React, {PureComponent} from 'react';
import {Text, View} from 'react-native';
import PropTypes from 'prop-types';
import Markdown from 'app/components/markdown';
import CustomPropTypes from 'app/constants/custom_prop_types';
import {makeStyleSheetFromTheme} from 'app/utils/theme';
import Markdown from '@components/markdown';
import {makeStyleSheetFromTheme} from '@utils/theme';
export default class AttachmentFields extends PureComponent {
static propTypes = {
baseTextStyle: CustomPropTypes.Style.isRequired,
baseTextStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
blockStyles: PropTypes.object.isRequired,
fields: PropTypes.array,
metadata: PropTypes.object,

View File

@@ -5,12 +5,11 @@ import React, {PureComponent} from 'react';
import {StyleSheet, View} from 'react-native';
import PropTypes from 'prop-types';
import Markdown from 'app/components/markdown';
import CustomPropTypes from 'app/constants/custom_prop_types';
import Markdown from '@components/markdown';
export default class AttachmentPreText extends PureComponent {
static propTypes = {
baseTextStyle: CustomPropTypes.Style.isRequired,
baseTextStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
blockStyles: PropTypes.object.isRequired,
metadata: PropTypes.object,
onPermalinkPress: PropTypes.func,

View File

@@ -5,15 +5,14 @@ import React, {PureComponent} from 'react';
import {ScrollView, StyleSheet, View} from 'react-native';
import PropTypes from 'prop-types';
import Markdown from 'app/components/markdown';
import ShowMoreButton from 'app/components/show_more_button';
import CustomPropTypes from 'app/constants/custom_prop_types';
import Markdown from '@components/markdown';
import ShowMoreButton from '@components/show_more_button';
const SHOW_MORE_HEIGHT = 60;
export default class AttachmentText extends PureComponent {
static propTypes = {
baseTextStyle: CustomPropTypes.Style.isRequired,
baseTextStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
blockStyles: PropTypes.object.isRequired,
deviceHeight: PropTypes.number.isRequired,
hasThumbnail: PropTypes.bool,

View File

@@ -5,14 +5,12 @@ import React, {PureComponent} from 'react';
import {View} from 'react-native';
import PropTypes from 'prop-types';
import CustomPropTypes from 'app/constants/custom_prop_types';
import MessageAttachment from './message_attachment';
export default class MessageAttachments extends PureComponent {
static propTypes = {
attachments: PropTypes.array.isRequired,
baseTextStyle: CustomPropTypes.Style,
baseTextStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
blockStyles: PropTypes.object,
deviceHeight: PropTypes.number.isRequired,
deviceWidth: PropTypes.number.isRequired,

View File

@@ -5,9 +5,8 @@ import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {View} from 'react-native';
import CustomPropTypes from 'app/constants/custom_prop_types';
import {getStatusColors} from 'app/utils/message_attachment_colors';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
import {getStatusColors} from '@utils/message_attachment_colors';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import AttachmentActions from './attachment_actions';
import AttachmentAuthor from './attachment_author';
@@ -22,7 +21,7 @@ import AttachmentFooter from './attachment_footer';
export default class MessageAttachment extends PureComponent {
static propTypes = {
attachment: PropTypes.object.isRequired,
baseTextStyle: CustomPropTypes.Style,
baseTextStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
blockStyles: PropTypes.object,
deviceHeight: PropTypes.number.isRequired,
deviceWidth: PropTypes.number.isRequired,

View File

@@ -24,7 +24,6 @@ import {INDICATOR_BAR_HEIGHT} from '@constants/view';
import networkConnectionListener, {checkConnection} from '@utils/network';
import {t} from '@utils/i18n';
import mattermostBucket from 'app/mattermost_bucket';
import PushNotifications from '@init/push_notifications';
const MAX_WEBSOCKET_RETRIES = 3;
@@ -288,13 +287,8 @@ export default class NetworkIndicator extends PureComponent {
initializeWebSocket = async () => {
const {actions} = this.props;
const {closeWebSocket, initWebSocket} = actions;
const platform = Platform.OS;
let certificate = null;
if (platform === 'ios') {
certificate = await mattermostBucket.getPreference('cert');
}
initWebSocket({certificate, forceConnection: true}).catch(() => {
initWebSocket({forceConnection: true}).catch(() => {
// we should dispatch a failure and show the app as disconnected
closeWebSocket(true);
});

View File

@@ -1,12 +1,12 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`PasteableTextInput should render pasteable text input 1`] = `
<Component
<TextInput
allowFontScaling={true}
onPaste={[Function]}
rejectResponderTermination={true}
underlineColorAndroid="transparent"
>
My Text
</Component>
</TextInput>
`;

View File

@@ -7,14 +7,11 @@ import {intlShape} from 'react-intl';
import {Text} from 'react-native';
import AtMention from '@components/at_mention';
import FormattedText from '@components/formatted_text';
import {General} from '@mm-redux/constants';
import {concatStyles} from 'app/utils/theme';
import {t} from 'app/utils/i18n';
import AtMention from 'app/components/at_mention';
import FormattedText from 'app/components/formatted_text';
import CustomPropTypes from 'app/constants/custom_prop_types';
import {t} from '@utils/i18n';
import {concatStyles} from '@utils/theme';
export default class PostAddChannelMember extends React.PureComponent {
static propTypes = {
@@ -23,7 +20,7 @@ export default class PostAddChannelMember extends React.PureComponent {
removePost: PropTypes.func.isRequired,
sendAddToChannelEphemeralPost: PropTypes.func.isRequired,
}).isRequired,
baseTextStyle: CustomPropTypes.Style,
baseTextStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
currentUser: PropTypes.object.isRequired,
channelType: PropTypes.string,
post: PropTypes.object.isRequired,

View File

@@ -298,3 +298,77 @@ exports[`PostAttachmentOpenGraph should match state and snapshot, on renderImage
</TouchableWithFeedbackIOS>
</View>
`;
exports[`PostAttachmentOpenGraph should match state and snapshot, on renderImage 3`] = `
<View
style={
Object {
"borderColor": "rgba(61,60,64,0.2)",
"borderRadius": 3,
"borderWidth": 1,
"flex": 1,
"marginTop": 10,
"padding": 10,
}
}
>
<View
style={
Object {
"flex": 1,
}
}
>
<Text
ellipsizeMode="tail"
numberOfLines={1}
style={
Object {
"color": "rgba(61,60,64,0.5)",
"fontSize": 12,
"marginBottom": 10,
}
}
>
Mattermost
</Text>
</View>
<View
style={
Object {
"flex": 1,
"flexDirection": "row",
}
}
>
<TouchableWithFeedbackIOS
onPress={[Function]}
style={
Object {
"flex": 1,
}
}
type="opacity"
>
<Text
ellipsizeMode="tail"
numberOfLines={3}
style={
Array [
Object {
"color": "#2389d7",
"fontSize": 14,
"marginBottom": 10,
},
Object {
"marginRight": 0,
},
]
}
>
Title
</Text>
</TouchableWithFeedbackIOS>
</View>
</View>
`;

View File

@@ -15,7 +15,7 @@ import {generateId} from '@utils/file';
import {openGalleryAtIndex, calculateDimensions} from '@utils/images';
import {getNearestPoint} from '@utils/opengraph';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import {tryOpenURL} from '@utils/url';
import {tryOpenURL, isValidUrl} from '@utils/url';
const MAX_IMAGE_HEIGHT = 150;
const VIEWPORT_IMAGE_OFFSET = 93;
@@ -93,7 +93,7 @@ export default class PostAttachmentOpenGraph extends PureComponent {
}
return {
hasImage: true,
hasImage: Boolean(isValidUrl(imageUrl) && (ogImage.width && ogImage.height)),
...dimensions,
openGraphImageUrl: imageUrl,
};

View File

@@ -104,6 +104,22 @@ describe('PostAttachmentOpenGraph', () => {
expect(wrapper.find(TouchableWithFeedback).exists()).toEqual(true);
});
test('should match state and snapshot, on renderImage', () => {
const images = [{height: 440, width: 1200, url: '%REACT_APP_WEBSITE_BANNER%'}];
const openGraphDataWithImage = {...openGraphData, images};
const wrapper = shallow(
<PostAttachmentOpenGraph
{...baseProps}
openGraphData={openGraphDataWithImage}
/>,
);
expect(wrapper.getElement()).toMatchSnapshot();
expect(wrapper.state('hasImage')).toEqual(false);
expect(wrapper.find(FastImage).exists()).toEqual(false);
expect(wrapper.find(TouchableWithFeedback).exists()).toEqual(true);
});
test('should match state and snapshot, on renderDescription', () => {
const wrapper = shallow(
<PostAttachmentOpenGraph

View File

@@ -14,14 +14,7 @@ exports[`renderSystemMessage uses renderer for Channel Display Name update 1`] =
>
<Text>
<Text
style={
Array [
Object {},
Array [
undefined,
],
]
}
style={Object {}}
testID="markdown_text"
>
{username} updated the channel display name from: {oldDisplayName} to: {newDisplayName}
@@ -44,14 +37,7 @@ exports[`renderSystemMessage uses renderer for Channel Header update 1`] = `
>
<Text>
<Text
style={
Array [
Object {},
Array [
undefined,
],
]
}
style={Object {}}
testID="markdown_text"
>
{username} updated the channel header from: {oldHeader} to: {newHeader}
@@ -82,14 +68,7 @@ exports[`renderSystemMessage uses renderer for OLD archived channel without a us
>
<Text>
<Text
style={
Array [
Object {},
Array [
undefined,
],
]
}
style={Object {}}
testID="markdown_text"
>
{username} archived the channel
@@ -123,14 +102,7 @@ exports[`renderSystemMessage uses renderer for archived channel 2`] = `
>
<Text>
<Text
style={
Array [
Object {},
Array [
undefined,
],
]
}
style={Object {}}
testID="markdown_text"
>
{username} archived the channel
@@ -153,14 +125,7 @@ exports[`renderSystemMessage uses renderer for unarchived channel 1`] = `
>
<Text>
<Text
style={
Array [
Object {},
Array [
undefined,
],
]
}
style={Object {}}
testID="markdown_text"
>
{username} unarchived the channel

View File

@@ -18,7 +18,6 @@ import ImageViewPort from '@components/image_viewport';
import PostAttachmentImage from '@components/post_attachment_image';
import ProgressiveImage from '@components/progressive_image';
import TouchableWithFeedback from '@components/touchable_with_feedback';
import CustomPropTypes from '@constants/custom_prop_types';
import EventEmitter from '@mm-redux/utils/event_emitter';
import {generateId} from '@utils/file';
import {calculateDimensions, getViewPortWidth, openGalleryAtIndex} from '@utils/images';
@@ -36,7 +35,7 @@ export default class PostBodyAdditionalContent extends ImageViewPort {
actions: PropTypes.shape({
getRedirectLocation: PropTypes.func.isRequired,
}).isRequired,
baseTextStyle: CustomPropTypes.Style,
baseTextStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
blockStyles: PropTypes.object,
deviceHeight: PropTypes.number.isRequired,
deviceWidth: PropTypes.number.isRequired,
@@ -396,7 +395,7 @@ export default class PostBodyAdditionalContent extends ImageViewPort {
if (app_bindings && app_bindings.length) {
return (
<EmbeddedBindings
embed={app_bindings}
embeds={app_bindings}
baseTextStyle={baseTextStyle}
blockStyles={blockStyles}
deviceHeight={deviceHeight}
@@ -532,13 +531,13 @@ export default class PostBodyAdditionalContent extends ImageViewPort {
render() {
let {link} = this.props;
const {openGraphData, postProps, expandedLink} = this.props;
const {openGraphData, postProps, expandedLink, appsEnabled} = this.props;
const {linkLoadError} = this.state;
if (expandedLink) {
link = expandedLink;
}
const {attachments, app_bindings, appsEnabled} = postProps;
const {attachments, app_bindings} = postProps;
if (!link && !attachments && !(appsEnabled && app_bindings)) {
return null;

View File

@@ -434,8 +434,7 @@ exports[`PostHeader should match snapshot when post isBot and shouldRenderReplyB
}
}
>
<TouchableWithFeedbackIOS
onPress={[Function]}
<View
style={
Array [
Object {
@@ -448,7 +447,6 @@ exports[`PostHeader should match snapshot when post isBot and shouldRenderReplyB
},
]
}
type="opacity"
>
<Text
ellipsizeMode="tail"
@@ -466,7 +464,7 @@ exports[`PostHeader should match snapshot when post isBot and shouldRenderReplyB
>
John Smith
</Text>
</TouchableWithFeedbackIOS>
</View>
<BotTag
style={
Object {
@@ -589,8 +587,7 @@ exports[`PostHeader should match snapshot when post isBot and shouldRenderReplyB
}
}
>
<TouchableWithFeedbackIOS
onPress={[Function]}
<View
style={
Array [
Object {
@@ -603,7 +600,6 @@ exports[`PostHeader should match snapshot when post isBot and shouldRenderReplyB
},
]
}
type="opacity"
>
<Text
ellipsizeMode="tail"
@@ -621,7 +617,7 @@ exports[`PostHeader should match snapshot when post isBot and shouldRenderReplyB
>
John Smith
</Text>
</TouchableWithFeedbackIOS>
</View>
<BotTag
style={
Object {

View File

@@ -142,6 +142,7 @@ export default class PostHeader extends PureComponent {
displayName,
enablePostUsernameOverride,
fromWebHook,
isBot,
isSystemMessage,
fromAutoResponder,
overrideUsername,
@@ -153,7 +154,7 @@ export default class PostHeader extends PureComponent {
const displayNameWidth = this.calcNameWidth();
const displayNameStyle = [style.displayNameContainer, displayNameWidth];
if (fromAutoResponder || fromWebHook) {
if (fromAutoResponder || fromWebHook || isBot) {
let name = displayName;
if (overrideUsername && enablePostUsernameOverride) {
name = overrideUsername;

View File

@@ -57,7 +57,7 @@ exports[`PostList setting channel deep link 1`] = `
tintColor="#3d3c40"
/>
}
removeClippedSubviews={false}
removeClippedSubviews={true}
renderItem={[Function]}
scrollEventThrottle={60}
style={
@@ -134,7 +134,7 @@ exports[`PostList setting permalink deep link 1`] = `
tintColor="#3d3c40"
/>
}
removeClippedSubviews={false}
removeClippedSubviews={true}
renderItem={[Function]}
scrollEventThrottle={60}
style={
@@ -211,7 +211,7 @@ exports[`PostList should match snapshot 1`] = `
tintColor="#3d3c40"
/>
}
removeClippedSubviews={false}
removeClippedSubviews={true}
renderItem={[Function]}
scrollEventThrottle={60}
style={

View File

@@ -516,7 +516,7 @@ export default class PostList extends PureComponent {
onScrollToIndexFailed={this.handleScrollToIndexFailed}
ref={this.flatListRef}
refreshControl={refreshControl}
removeClippedSubviews={false}
removeClippedSubviews={true}
renderItem={this.renderItem}
scrollEventThrottle={60}
style={styles.flex}

View File

@@ -19,18 +19,19 @@ const STATUS_BUFFER = Platform.select({
export default class ProfilePicture extends PureComponent {
static propTypes = {
isCurrentUser: PropTypes.bool.isRequired,
isCurrentUser: PropTypes.bool,
size: PropTypes.number,
statusSize: PropTypes.number,
iconSize: PropTypes.number,
user: PropTypes.object,
userId: PropTypes.string,
showStatus: PropTypes.bool,
status: PropTypes.string,
edit: PropTypes.bool,
imageUri: PropTypes.string,
profileImageUri: PropTypes.string,
profileImageRemove: PropTypes.bool,
theme: PropTypes.object.isRequired,
theme: PropTypes.object,
testID: PropTypes.string,
actions: PropTypes.shape({
getStatusForId: PropTypes.func.isRequired,
@@ -181,7 +182,7 @@ export default class ProfilePicture extends PureComponent {
return (
<View
style={[style.container, containerStyle]}
testID={`${testID}.${user.id}`}
testID={`${testID}.${user?.id}`}
>
{image}
{(showStatus || edit) && (user && !user.is_bot) &&

View File

@@ -6,7 +6,6 @@ import PropTypes from 'prop-types';
import {Animated, ImageBackground, Image, Platform, View, StyleSheet} from 'react-native';
import thumb from '@assets/images/thumb.png';
import CustomPropTypes from '@constants/custom_prop_types';
import RetriableFastImage from '@components/retriable_fast_image';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
@@ -17,15 +16,15 @@ export default class ProgressiveImage extends PureComponent {
static propTypes = {
id: PropTypes.string,
isBackgroundImage: PropTypes.bool,
children: CustomPropTypes.Children,
children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf([PropTypes.node])]),
defaultSource: PropTypes.oneOfType([PropTypes.string, PropTypes.object, PropTypes.number]), // this should be provided by the component
imageUri: PropTypes.string,
imageStyle: CustomPropTypes.Style,
imageStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
inViewPort: PropTypes.bool,
onError: PropTypes.func,
resizeMethod: PropTypes.string,
resizeMode: PropTypes.string,
style: CustomPropTypes.Style,
style: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
theme: PropTypes.object.isRequired,
thumbnailUri: PropTypes.string,
tintDefaultSource: PropTypes.bool,

View File

@@ -19,7 +19,6 @@ import {SearchBar} from 'react-native-elements';
import {memoizeResult} from '@mm-redux/utils/helpers';
import CompassIcon from '@components/compass_icon';
import CustomPropTypes from '@constants/custom_prop_types';
const LEFT_COMPONENT_INITIAL_POSITION = Platform.OS === 'ios' ? 7 : 0;
@@ -38,9 +37,9 @@ export default class Search extends PureComponent {
tintColorSearch: PropTypes.string,
tintColorDelete: PropTypes.string,
selectionColor: PropTypes.string,
inputStyle: CustomPropTypes.Style,
containerStyle: CustomPropTypes.Style,
cancelButtonStyle: CustomPropTypes.Style,
inputStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
containerStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
cancelButtonStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
autoFocus: PropTypes.bool,
placeholder: PropTypes.string,
cancelTitle: PropTypes.oneOfType([

View File

@@ -383,6 +383,7 @@ class FilteredList extends Component {
<View style={styles.container}>
<SectionList
sections={dataSource}
removeClippedSubviews={true}
renderItem={this.renderItem}
renderSectionHeader={this.renderSectionHeader}
keyExtractor={this.keyExtractor}

View File

@@ -21,6 +21,7 @@ exports[`ChannelsList List should match snapshot 1`] = `
onEndReachedThreshold={2}
onScrollBeginDrag={[Function]}
onViewableItemsChanged={[Function]}
removeClippedSubviews={true}
renderItem={[Function]}
renderSectionHeader={[Function]}
scrollEventThrottle={50}

View File

@@ -411,6 +411,7 @@ export default class List extends PureComponent {
ref={this.setListRef}
sections={sections}
contentContainerStyle={{paddingBottom}}
removeClippedSubviews={true}
renderItem={this.renderItem}
renderSectionHeader={this.renderSectionHeader}
keyboardShouldPersistTaps={'always'}

View File

@@ -179,6 +179,7 @@ export default class TeamsList extends PureComponent {
extraData={this.state.serverUrl}
contentContainerStyle={this.listContentPadding()}
data={teamIds}
removeClippedSubviews={true}
renderItem={this.renderItem}
keyExtractor={this.keyExtractor}
viewabilityConfig={VIEWABILITY_CONFIG}

View File

@@ -7,12 +7,10 @@ import React, {PureComponent} from 'react';
import {TouchableNativeFeedback, TouchableOpacity, TouchableWithoutFeedback, View} from 'react-native';
import PropTypes from 'prop-types';
import CustomPropTypes from 'app/constants/custom_prop_types';
export default class TouchableWithFeedbackAndroid extends PureComponent {
static propTypes = {
testID: PropTypes.string,
children: CustomPropTypes.Children,
children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf([PropTypes.node])]),
underlayColor: PropTypes.string,
type: PropTypes.oneOf(['native', 'opacity', 'none']),
};

View File

@@ -5,12 +5,10 @@ import React, {PureComponent} from 'react';
import {PanResponder, TouchableHighlight, TouchableOpacity, TouchableWithoutFeedback, View} from 'react-native';
import PropTypes from 'prop-types';
import CustomPropTypes from 'app/constants/custom_prop_types';
export default class TouchableWithFeedbackIOS extends PureComponent {
static propTypes = {
testID: PropTypes.string,
children: CustomPropTypes.Children,
children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf([PropTypes.node])]),
cancelTouchOnPanning: PropTypes.bool,
type: PropTypes.oneOf(['native', 'opacity', 'none']),
};

View File

@@ -1,17 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import PropTypes from 'prop-types';
export const Children = PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf([PropTypes.node])]);
export const Style = PropTypes.oneOfType([
PropTypes.object, // inline style
PropTypes.number, // style sheet entry
PropTypes.array,
]);
export default {
Children,
Style,
};

View File

@@ -0,0 +1,12 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {ReactNode, ReactNodeArray} from 'react';
import {StyleProp, TextStyle, ViewStyle} from 'react-native';
interface CustomPropTypes {
Children: ReactNode | ReactNodeArray;
Style: object | number | StyleProp<TextStyle> | StyleProp<TextStyle>[] | StyleProp<ViewStyle> | StyleProp<ViewStyle>[];
}
export default CustomPropTypes;

View File

@@ -112,10 +112,10 @@ const ViewTypes = keyMirror({
});
const RequiredServer = {
FULL_VERSION: 5.25,
FULL_VERSION: '5.31.3',
MAJOR_VERSION: 5,
MIN_VERSION: 25,
PATCH_VERSION: 0,
MIN_VERSION: 31,
PATCH_VERSION: 3,
};
export default {

View File

@@ -28,6 +28,7 @@ class EMMProvider {
this.allowOtherServers = true;
this.emmServerUrl = null;
this.inAppSessionAuth = false;
}
checkIfDeviceIsTrusted = () => {
@@ -106,6 +107,8 @@ class EMMProvider {
this.jailbreakProtection = managedConfig.jailbreakProtection === 'true';
this.vendor = managedConfig.vendor || 'Mattermost';
this.inAppSessionAuth = managedConfig.inAppSessionAuth === 'true';
const credentials = await getAppCredentials();
if (!credentials) {
this.emmServerUrl = managedConfig.serverUrl;

View File

@@ -25,7 +25,7 @@ import {logError} from './errors';
import {bindClientFunc, forceLogoutIfNecessary} from './helpers';
import {getMissingProfilesByIds} from './users';
import {loadRolesIfNeeded} from './roles';
import {analytics} from '@init/analytics.ts';
import {analytics} from '@init/analytics';
export function selectChannel(channelId: string) {
return {

View File

@@ -4,7 +4,7 @@ import {GifTypes} from '@mm-redux/action_types';
import gfycatSdk from '@mm-redux/utils/gfycat_sdk';
import {DispatchFunc, GetStateFunc} from '@mm-redux/types/actions';
import {GlobalState} from '@mm-redux/types/store';
import {analytics} from '@init/analytics.ts';
import {analytics} from '@init/analytics';
// APP PROPS

View File

@@ -9,7 +9,7 @@ import {Client4} from '@mm-redux/client';
import {getCurrentUserId} from '@mm-redux/selectors/entities/users';
import {getCurrentChannelId} from '@mm-redux/selectors/entities/channels';
import {getCurrentTeamId} from '@mm-redux/selectors/entities/teams';
import {analytics} from '@init/analytics.ts';
import {analytics} from '@init/analytics';
import {batchActions, DispatchFunc, GetStateFunc, ActionFunc} from '@mm-redux/types/actions';

View File

@@ -22,7 +22,7 @@ import {getMyChannelMember, markChannelAsUnread, markChannelAsRead, markChannelA
import {getCustomEmojiByName, getCustomEmojisByName} from './emojis';
import {logError} from './errors';
import {forceLogoutIfNecessary} from './helpers';
import {analytics} from '@init/analytics.ts';
import {analytics} from '@init/analytics';
import {
deletePreferences,

View File

@@ -13,7 +13,7 @@ import {ActionResult, batchActions, DispatchFunc, GetStateFunc, ActionFunc} from
import {RelationOneToOne} from '@mm-redux/types/utilities';
import {Post} from '@mm-redux/types/posts';
import {SearchParameter} from '@mm-redux/types/search';
import {analytics} from '@init/analytics.ts';
import {analytics} from '@init/analytics';
const WEBAPP_SEARCH_PER_PAGE = 20;
export function getMissingChannelsFromPosts(posts: RelationOneToOne<Post, Post>): ActionFunc {

View File

@@ -22,7 +22,7 @@ import {logError} from './errors';
import {bindClientFunc, forceLogoutIfNecessary, debounce} from './helpers';
import {getMyPreferences, makeDirectChannelVisibleIfNecessary, makeGroupMessageVisibleIfNecessary} from './preferences';
import {Dictionary} from '@mm-redux/types/utilities';
import {analytics} from '@init/analytics.ts';
import {analytics} from '@init/analytics';
export function checkMfa(loginId: string): ActionFunc {
return async (dispatch: DispatchFunc) => {

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