forked from Ivasoft/mattermost-mobile
Compare commits
55 Commits
release-1.
...
v1.32.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
41848b2634 | ||
|
|
8d81d946c5 | ||
|
|
10d27ee5ba | ||
|
|
647def15be | ||
|
|
da440e50fb | ||
|
|
793ac98d74 | ||
|
|
fab353b494 | ||
|
|
8853b5dd45 | ||
|
|
464d93df8d | ||
|
|
47f126b71f | ||
|
|
4b2a0c7aea | ||
|
|
f6bedbb7d6 | ||
|
|
b5ee7c8908 | ||
|
|
aab0814b7f | ||
|
|
bf840785fb | ||
|
|
514e9cfd08 | ||
|
|
b5c3e95a4b | ||
|
|
e6547d7dc1 | ||
|
|
2b8bba7c24 | ||
|
|
9b9373e27b | ||
|
|
bc25a29c42 | ||
|
|
4c4dd8297d | ||
|
|
c3fc53a071 | ||
|
|
34af598a6d | ||
|
|
ff89f3530e | ||
|
|
8a95000bd0 | ||
|
|
21e1466068 | ||
|
|
4ebcba6069 | ||
|
|
b34ce42016 | ||
|
|
97d393a2ba | ||
|
|
fb03a88304 | ||
|
|
6e239d5566 | ||
|
|
72b95fa265 | ||
|
|
d19fc71ad4 | ||
|
|
526290bbdf | ||
|
|
962b38d024 | ||
|
|
51109c74d3 | ||
|
|
098230e79e | ||
|
|
8fa67bd5b4 | ||
|
|
6d7749a098 | ||
|
|
37479587cc | ||
|
|
3708b86b30 | ||
|
|
cabce2a808 | ||
|
|
4abb483f2c | ||
|
|
7a0bf1dc77 | ||
|
|
6cf1140a0f | ||
|
|
9506875683 | ||
|
|
679a897848 | ||
|
|
b5fd0284e8 | ||
|
|
67eea1750d | ||
|
|
d4e405485b | ||
|
|
1389e4f7f7 | ||
|
|
7cf4084fe5 | ||
|
|
6b7cffd6af | ||
|
|
dba3278c9f |
503
NOTICE.txt
503
NOTICE.txt
@@ -113,6 +113,52 @@ SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
## @react-native-community/cookies
|
||||
|
||||
This product contains '@react-native-community/cookies' by React Native Community.
|
||||
|
||||
Cookie Manager for React Native
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/react-native-community/cookies
|
||||
|
||||
* LICENSE: MIT License
|
||||
|
||||
Copyright (c) 2020 React Native Community
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
## @react-native-community/masked-view
|
||||
|
||||
This product contains '@react-native-community/masked-view' by React Native Community.
|
||||
|
||||
React Native Masked View Library
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/react-native-community/react-native-masked-view
|
||||
|
||||
* LICENSE: MIT
|
||||
|
||||
---
|
||||
|
||||
## @react-native-community/netinfo
|
||||
|
||||
This product contains 'netinfo' by Matt Oakes.
|
||||
@@ -148,38 +194,66 @@ SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
## @sentry/react-native
|
||||
## @react-navigation/native
|
||||
|
||||
This product contains 'react-native-sentry' by Sentry.
|
||||
This product contains 'react-navigation' by Adam Miskiewicz.
|
||||
|
||||
Official Sentry SDK for react-native
|
||||
Routing and navigation for your React Native apps
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/getsentry/react-native-sentry
|
||||
* https://github.com/react-navigation/react-navigation#readme
|
||||
|
||||
* LICENSE: BSD-2-Clause
|
||||
|
||||
BSD License
|
||||
|
||||
For React Navigation software
|
||||
|
||||
Copyright (c) 2016-present, React Navigation Contributors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
---
|
||||
|
||||
## @react-navigation/stack
|
||||
|
||||
This product contains 'react-navigation-stack' by react-navigation.
|
||||
|
||||
Stack navigator for React Navigation
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/react-navigation/stack
|
||||
|
||||
* LICENSE: MIT
|
||||
|
||||
The MIT License (MIT)
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Sentry
|
||||
Copyright (c) 2017 React Native Community
|
||||
|
||||
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:
|
||||
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 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.
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
@@ -398,6 +472,62 @@ limitations under the License.
|
||||
|
||||
---
|
||||
|
||||
## @sentry/react-native
|
||||
|
||||
This product contains 'react-native-sentry' by Sentry.
|
||||
|
||||
Official Sentry SDK for react-native
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/getsentry/react-native-sentry
|
||||
|
||||
* LICENSE: MIT
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017 Sentry
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
## analytics-react-native
|
||||
|
||||
This product contains a modified version of 'analytics-react-native' by Segment.
|
||||
|
||||
The hassle-free way to add analytics to your React-Native app.
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/segmentio/analytics-react-native
|
||||
|
||||
* LICENSE: The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018 Segment.io, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
## commonmark
|
||||
|
||||
This product contains a modified version of 'commonmark' by John MacFarlane.
|
||||
@@ -1430,41 +1560,6 @@ THE SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
## @react-native-community/cookies
|
||||
|
||||
This product contains '@react-native-community/cookies' by @joeferraro.
|
||||
|
||||
Cookie manager for react native
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/react-native-community/cookies
|
||||
|
||||
* LICENSE: MIT
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 React Native Community
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
## react-native-device-info
|
||||
|
||||
This product contains a modified version of 'react-native-device-info' by Rebecca Hughes.
|
||||
@@ -1570,6 +1665,39 @@ SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
## react-native-elements
|
||||
|
||||
This product contains 'react-native-elements' by React Native Elements.
|
||||
|
||||
Cross Platform React Native UI Toolkit
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/react-native-elements/react-native-elements
|
||||
|
||||
* LICENSE: The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Nader Dabit
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
## react-native-fast-image
|
||||
|
||||
This product contains 'react-native-fast-image' by Dylan Vann.
|
||||
@@ -1605,6 +1733,39 @@ SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
## react-native-file-viewer
|
||||
|
||||
This product contains 'react-native-file-viewer' by Vincenzo Scamporlino.
|
||||
|
||||
Native file viewer for React Native. Preview any type of file supported by the mobile device.
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/vinzscam/react-native-file-viewer
|
||||
|
||||
* LICENSE: MIT License
|
||||
|
||||
Copyright (c) 2017 Vincenzo Scamporlino
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
## react-native-gesture-handler
|
||||
|
||||
This product contains 'react-native-gesture-handler' by Krzysztof Magiera.
|
||||
@@ -1916,6 +2077,39 @@ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH RE
|
||||
|
||||
---
|
||||
|
||||
## react-native-localize
|
||||
|
||||
This product contains 'react-native-localize' by React Native Community.
|
||||
|
||||
A toolbox for your React Native app localization (formerly react-native-languages)
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/react-native-community/react-native-localize
|
||||
|
||||
* LICENSE: MIT License
|
||||
|
||||
Copyright (c) 2017-present, Mathieu Acthernoene
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
## react-native-mmkv-storage
|
||||
|
||||
This product contains 'react-native-mmkv-storage' by Ammar Ahmed.
|
||||
@@ -2090,6 +2284,39 @@ SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
## react-native-reanimated
|
||||
|
||||
This product contains 'react-native-reanimated' by Software Mansion.
|
||||
|
||||
React Native's Animated library reimplemented
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/software-mansion/react-native-reanimated
|
||||
|
||||
* LICENSE: The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Krzysztof Magiera
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
## react-native-safe-area
|
||||
|
||||
This product contains 'react-native-safe-area' by Masayuki Iwai.
|
||||
@@ -2125,6 +2352,72 @@ SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
## react-native-safe-area-context
|
||||
|
||||
This product contains 'react-native-safe-area-context' by Th3rdwave.
|
||||
|
||||
A flexible way to handle safe area insets in JS. Also works on Android and Web
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/th3rdwave/react-native-safe-area-context
|
||||
|
||||
* LICENSE: MIT License
|
||||
|
||||
Copyright (c) 2019 Th3rd Wave
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
## react-native-screens
|
||||
|
||||
This product contains 'react-native-screens' by Software Mansion.
|
||||
|
||||
Native navigation primitives for your React Native app.
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/software-mansion/react-native-screens
|
||||
|
||||
* LICENSE: The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018 Krzysztof Magiera
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
## react-native-section-list-get-item-layout
|
||||
|
||||
This product contains 'react-native-section-list-get-item-layout' by Jan Soendermann.
|
||||
@@ -2420,69 +2713,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
## react-navigation
|
||||
|
||||
This product contains 'react-navigation' by Adam Miskiewicz.
|
||||
|
||||
Routing and navigation for your React Native apps
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/react-navigation/react-navigation#readme
|
||||
|
||||
* LICENSE: BSD-2-Clause
|
||||
|
||||
BSD License
|
||||
|
||||
For React Navigation software
|
||||
|
||||
Copyright (c) 2016-present, React Navigation Contributors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
---
|
||||
|
||||
## react-navigation-stack
|
||||
|
||||
This product contains 'react-navigation-stack' by react-navigation.
|
||||
|
||||
Stack navigator for React Navigation
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/react-navigation/stack
|
||||
|
||||
* LICENSE: MIT
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 React Native Community
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
----
|
||||
|
||||
## react-redux
|
||||
@@ -2695,41 +2925,6 @@ SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
## redux-reset
|
||||
|
||||
This product contains 'redux-reset' by Wang Zixiao.
|
||||
|
||||
Gives redux the ability to reset the state
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/wwayne/redux-reset
|
||||
|
||||
* LICENSE: MIT
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Wang Zixiao
|
||||
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
## redux-thunk
|
||||
|
||||
This product contains 'redux-thunk' by Dan Abramov.
|
||||
|
||||
@@ -133,8 +133,8 @@ android {
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
missingDimensionStrategy "RNNotifications.reactNativeVersion", "reactNative60"
|
||||
versionCode 293
|
||||
versionName "1.31.0"
|
||||
versionCode 307
|
||||
versionName "1.32.2"
|
||||
multiDexEnabled = true
|
||||
ndk {
|
||||
abiFilters 'armeabi-v7a','arm64-v8a','x86','x86_64'
|
||||
|
||||
@@ -36,12 +36,7 @@ public class MattermostCredentialsHelper {
|
||||
|
||||
HashMap<String, String> asyncStorageResults = asyncStorage.multiGet(asyncStorageKeys);
|
||||
String serverUrl = asyncStorageResults.get(CURRENT_SERVER_URL);
|
||||
final WritableMap options = Arguments.createMap();
|
||||
final WritableMap authPrompt = Arguments.createMap();
|
||||
authPrompt.putString("title", "Authenticate to retrieve secret");
|
||||
authPrompt.putString("cancel", "Cancel");
|
||||
options.putMap("authenticationPrompt", authPrompt);
|
||||
|
||||
keychainModule.getInternetCredentialsForServer(serverUrl, options, promise);
|
||||
keychainModule.getGenericPasswordForOptions(serverUrl, promise);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ public class ReceiptDelivery {
|
||||
try {
|
||||
Response response = client.newCall(request).execute();
|
||||
String responseBody = response.body().string();
|
||||
if (response.code() != 200 || !isIdLoaded) {
|
||||
if (response.code() != 200) {
|
||||
throw new Exception(responseBody);
|
||||
}
|
||||
JSONObject jsonResponse = new JSONObject(responseBody);
|
||||
@@ -120,14 +120,16 @@ public class ReceiptDelivery {
|
||||
promise.resolve(bundle);
|
||||
} catch (Exception e) {
|
||||
Log.e("ReactNative", "Receipt delivery failed to send");
|
||||
try {
|
||||
reRequestCount++;
|
||||
if (reRequestCount < FIBONACCI_BACKOFFS.length) {
|
||||
Log.i("ReactNative", "Retry attempt " + reRequestCount + " with backoff delay: " + FIBONACCI_BACKOFFS[reRequestCount] + " seconds");
|
||||
Thread.sleep(FIBONACCI_BACKOFFS[reRequestCount] * 1000);
|
||||
makeServerRequest(client, request, isIdLoaded, reRequestCount, promise);
|
||||
}
|
||||
} catch(InterruptedException ie) {}
|
||||
if (isIdLoaded) {
|
||||
try {
|
||||
reRequestCount++;
|
||||
if (reRequestCount < FIBONACCI_BACKOFFS.length) {
|
||||
Log.i("ReactNative", "Retry attempt " + reRequestCount + " with backoff delay: " + FIBONACCI_BACKOFFS[reRequestCount] + " seconds");
|
||||
Thread.sleep(FIBONACCI_BACKOFFS[reRequestCount] * 1000);
|
||||
makeServerRequest(client, request, isIdLoaded, reRequestCount, promise);
|
||||
}
|
||||
} catch(InterruptedException ie) {}
|
||||
}
|
||||
|
||||
promise.reject("Receipt delivery failure", e.toString());
|
||||
}
|
||||
|
||||
186
app/actions/helpers/channels.test.js
Normal file
186
app/actions/helpers/channels.test.js
Normal file
@@ -0,0 +1,186 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
/* eslint-disable no-import-assign */
|
||||
|
||||
import {Client4} from '@mm-redux/client';
|
||||
|
||||
import {Preferences} from '@mm-redux/constants';
|
||||
import {PreferenceTypes} from '@mm-redux/action_types';
|
||||
|
||||
import * as CommonSelectors from '@mm-redux/selectors/entities/common';
|
||||
import * as PreferenceSelectors from '@mm-redux/selectors/entities/preferences';
|
||||
import * as PreferenceUtils from '@mm-redux/utils/preference_utils';
|
||||
|
||||
import {
|
||||
makeDirectChannelVisibleIfNecessary,
|
||||
makeGroupMessageVisibleIfNecessary,
|
||||
} from './channels';
|
||||
|
||||
describe('Actions.Helpers.Channels', () => {
|
||||
describe('makeDirectChannelVisibleIfNecessary', () => {
|
||||
const state = {};
|
||||
const currentUserId = 'current-user-id';
|
||||
const otherUserId = 'other-user-id';
|
||||
|
||||
CommonSelectors.getCurrentUserId = jest.fn().mockReturnValue(currentUserId);
|
||||
PreferenceSelectors.getMyPreferences = jest.fn();
|
||||
PreferenceUtils.getPreferenceKey = jest.fn();
|
||||
Client4.savePreferences = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
PreferenceSelectors.getMyPreferences.mockClear();
|
||||
PreferenceUtils.getPreferenceKey.mockClear();
|
||||
Client4.savePreferences.mockClear();
|
||||
});
|
||||
|
||||
it('makes direct channel visible when visibility preference does not exist', () => {
|
||||
PreferenceSelectors.getMyPreferences.mockReturnValueOnce({});
|
||||
|
||||
const expectedResult = {
|
||||
type: PreferenceTypes.RECEIVED_PREFERENCES,
|
||||
data: [{
|
||||
user_id: currentUserId,
|
||||
category: Preferences.CATEGORY_DIRECT_CHANNEL_SHOW,
|
||||
name: otherUserId,
|
||||
value: 'true',
|
||||
}],
|
||||
};
|
||||
|
||||
const result = makeDirectChannelVisibleIfNecessary(state, otherUserId);
|
||||
expect(result).toStrictEqual(expectedResult);
|
||||
|
||||
expect(PreferenceUtils.getPreferenceKey).toHaveBeenCalledTimes(1);
|
||||
expect(PreferenceUtils.getPreferenceKey).toHaveBeenCalledWith(Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, otherUserId);
|
||||
expect(Client4.savePreferences).toHaveBeenCalledTimes(1);
|
||||
expect(Client4.savePreferences).toHaveBeenCalledWith(currentUserId, expectedResult.data);
|
||||
});
|
||||
|
||||
it('makes direct channel visible when visibilty preference is false', () => {
|
||||
const preference = {value: 'false'};
|
||||
const preferenceKey = 'preference-key';
|
||||
PreferenceSelectors.getMyPreferences.mockReturnValueOnce({
|
||||
[preferenceKey]: preference,
|
||||
});
|
||||
PreferenceUtils.getPreferenceKey.mockReturnValueOnce(preferenceKey);
|
||||
|
||||
const expectedResult = {
|
||||
type: PreferenceTypes.RECEIVED_PREFERENCES,
|
||||
data: [{
|
||||
user_id: currentUserId,
|
||||
category: Preferences.CATEGORY_DIRECT_CHANNEL_SHOW,
|
||||
name: otherUserId,
|
||||
value: 'true',
|
||||
}],
|
||||
};
|
||||
|
||||
const result = makeDirectChannelVisibleIfNecessary(state, otherUserId);
|
||||
expect(result).toStrictEqual(expectedResult);
|
||||
|
||||
expect(PreferenceUtils.getPreferenceKey).toHaveBeenCalledTimes(1);
|
||||
expect(PreferenceUtils.getPreferenceKey).toHaveBeenCalledWith(Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, otherUserId);
|
||||
expect(Client4.savePreferences).toHaveBeenCalledTimes(1);
|
||||
expect(Client4.savePreferences).toHaveBeenCalledWith(currentUserId, expectedResult.data);
|
||||
});
|
||||
|
||||
it('does nothing if direct channel visibility preference is true', () => {
|
||||
const preference = {value: 'true'};
|
||||
const preferenceKey = 'preference-key';
|
||||
PreferenceSelectors.getMyPreferences.mockReturnValueOnce({
|
||||
[preferenceKey]: preference,
|
||||
});
|
||||
PreferenceUtils.getPreferenceKey.mockReturnValueOnce(preferenceKey);
|
||||
|
||||
const result = makeDirectChannelVisibleIfNecessary(state, otherUserId);
|
||||
expect(result).toEqual(null);
|
||||
|
||||
expect(PreferenceUtils.getPreferenceKey).toHaveBeenCalledTimes(1);
|
||||
expect(PreferenceUtils.getPreferenceKey).toHaveBeenCalledWith(Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, otherUserId);
|
||||
expect(Client4.savePreferences).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('makeGroupMessageVisibleIfNecessary', () => {
|
||||
const state = {};
|
||||
const currentUserId = 'current-user-id';
|
||||
const channelId = 'channel-id';
|
||||
|
||||
CommonSelectors.getCurrentUserId = jest.fn().mockReturnValue(currentUserId);
|
||||
PreferenceSelectors.getMyPreferences = jest.fn();
|
||||
PreferenceUtils.getPreferenceKey = jest.fn();
|
||||
Client4.savePreferences = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
PreferenceSelectors.getMyPreferences.mockClear();
|
||||
PreferenceUtils.getPreferenceKey.mockClear();
|
||||
Client4.savePreferences.mockClear();
|
||||
});
|
||||
|
||||
it('makes group channel visible when visibility preference does not exist', async () => {
|
||||
PreferenceSelectors.getMyPreferences.mockReturnValueOnce({});
|
||||
|
||||
const expectedPreferenceResult = {
|
||||
type: PreferenceTypes.RECEIVED_PREFERENCES,
|
||||
data: [{
|
||||
user_id: currentUserId,
|
||||
category: Preferences.CATEGORY_GROUP_CHANNEL_SHOW,
|
||||
name: channelId,
|
||||
value: 'true',
|
||||
}],
|
||||
};
|
||||
|
||||
const result = await makeGroupMessageVisibleIfNecessary(state, channelId);
|
||||
expect(result.length).toEqual(2);
|
||||
expect(result[1]).toStrictEqual(expectedPreferenceResult);
|
||||
|
||||
expect(PreferenceUtils.getPreferenceKey).toHaveBeenCalledTimes(1);
|
||||
expect(PreferenceUtils.getPreferenceKey).toHaveBeenCalledWith(Preferences.CATEGORY_GROUP_CHANNEL_SHOW, channelId);
|
||||
expect(Client4.savePreferences).toHaveBeenCalledTimes(1);
|
||||
expect(Client4.savePreferences).toHaveBeenCalledWith(currentUserId, expectedPreferenceResult.data);
|
||||
});
|
||||
|
||||
it('makes group channel visible when visibilty preference is false', async () => {
|
||||
const preference = {value: 'false'};
|
||||
const preferenceKey = 'preference-key';
|
||||
PreferenceSelectors.getMyPreferences.mockReturnValueOnce({
|
||||
[preferenceKey]: preference,
|
||||
});
|
||||
PreferenceUtils.getPreferenceKey.mockReturnValueOnce(preferenceKey);
|
||||
|
||||
const expectedPreferenceResult = {
|
||||
type: PreferenceTypes.RECEIVED_PREFERENCES,
|
||||
data: [{
|
||||
user_id: currentUserId,
|
||||
category: Preferences.CATEGORY_GROUP_CHANNEL_SHOW,
|
||||
name: channelId,
|
||||
value: 'true',
|
||||
}],
|
||||
};
|
||||
|
||||
const result = await makeGroupMessageVisibleIfNecessary(state, channelId);
|
||||
expect(result.length).toEqual(2);
|
||||
expect(result[1]).toStrictEqual(expectedPreferenceResult);
|
||||
|
||||
expect(PreferenceUtils.getPreferenceKey).toHaveBeenCalledTimes(1);
|
||||
expect(PreferenceUtils.getPreferenceKey).toHaveBeenCalledWith(Preferences.CATEGORY_GROUP_CHANNEL_SHOW, channelId);
|
||||
expect(Client4.savePreferences).toHaveBeenCalledTimes(1);
|
||||
expect(Client4.savePreferences).toHaveBeenCalledWith(currentUserId, expectedPreferenceResult.data);
|
||||
});
|
||||
|
||||
it('does nothing if group channel visibility preference is true', async () => {
|
||||
const preference = {value: 'true'};
|
||||
const preferenceKey = 'preference-key';
|
||||
PreferenceSelectors.getMyPreferences.mockReturnValueOnce({
|
||||
[preferenceKey]: preference,
|
||||
});
|
||||
PreferenceUtils.getPreferenceKey.mockReturnValueOnce(preferenceKey);
|
||||
|
||||
const result = await makeGroupMessageVisibleIfNecessary(state, channelId);
|
||||
expect(result).toEqual(null);
|
||||
|
||||
expect(PreferenceUtils.getPreferenceKey).toHaveBeenCalledTimes(1);
|
||||
expect(PreferenceUtils.getPreferenceKey).toHaveBeenCalledWith(Preferences.CATEGORY_GROUP_CHANNEL_SHOW, channelId);
|
||||
expect(Client4.savePreferences).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -169,6 +169,7 @@ export function makeDirectChannelVisibleIfNecessary(state: GlobalState, otherUse
|
||||
value: 'true',
|
||||
};
|
||||
|
||||
Client4.savePreferences(currentUserId, [preference]);
|
||||
return {
|
||||
type: PreferenceTypes.RECEIVED_PREFERENCES,
|
||||
data: [preference],
|
||||
@@ -193,6 +194,8 @@ export async function makeGroupMessageVisibleIfNecessary(state: GlobalState, cha
|
||||
value: 'true',
|
||||
};
|
||||
|
||||
Client4.savePreferences(currentUserId, [preference]);
|
||||
|
||||
const profilesInChannel = await fetchUsersInChannel(state, channelId);
|
||||
|
||||
return [{
|
||||
@@ -368,11 +371,15 @@ async function getProfilesFromPromises(promises: Array<Promise<ActionResult>>):
|
||||
return null;
|
||||
}
|
||||
|
||||
const result = await Promise.all(promises);
|
||||
const data = result.filter((p: any) => !p.error);
|
||||
try {
|
||||
const result = await Promise.all(promises);
|
||||
const data = result.filter((p: any) => !p.error);
|
||||
|
||||
return {
|
||||
type: UserTypes.RECEIVED_BATCHED_PROFILES_IN_CHANNEL,
|
||||
data,
|
||||
};
|
||||
return {
|
||||
type: UserTypes.RECEIVED_BATCHED_PROFILES_IN_CHANNEL,
|
||||
data,
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import {Keyboard, Platform} from 'react-native';
|
||||
import {Navigation} from 'react-native-navigation';
|
||||
import merge from 'deepmerge';
|
||||
|
||||
import {Preferences} from '@mm-redux/constants';
|
||||
import {getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
|
||||
import EphemeralStore from '@store/ephemeral_store';
|
||||
@@ -83,7 +84,7 @@ export function resetToChannel(passProps = {}) {
|
||||
}
|
||||
|
||||
export function resetToSelectServer(allowOtherServers) {
|
||||
const theme = getThemeFromState();
|
||||
const theme = Preferences.THEMES.default;
|
||||
|
||||
Navigation.setRoot({
|
||||
root: {
|
||||
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
import {getCurrentUserId} from '@mm-redux/selectors/entities/users';
|
||||
import {getTeamByName} from '@mm-redux/selectors/entities/teams';
|
||||
|
||||
import {getChannelByName as selectChannelByName} from '@mm-redux/utils/channel_utils';
|
||||
import {getChannelByName as selectChannelByName, getChannelsIdForTeam} from '@mm-redux/utils/channel_utils';
|
||||
import EventEmitter from '@mm-redux/utils/event_emitter';
|
||||
|
||||
import {loadSidebarDirectMessagesProfiles} from '@actions/helpers/channels';
|
||||
@@ -224,10 +224,15 @@ export function handleSelectChannel(channelId) {
|
||||
const channel = channels[channelId];
|
||||
const member = myMembers[channelId];
|
||||
|
||||
dispatch(loadPostsIfNecessaryWithRetry(channelId));
|
||||
if (channel) {
|
||||
dispatch(loadPostsIfNecessaryWithRetry(channelId));
|
||||
|
||||
if (channel && currentChannelId !== channelId) {
|
||||
const actions = markAsViewedAndReadBatch(state, channelId, currentChannelId);
|
||||
let previousChannelId = null;
|
||||
if (currentChannelId !== channelId) {
|
||||
previousChannelId = currentChannelId;
|
||||
}
|
||||
|
||||
const actions = markAsViewedAndReadBatch(state, channelId, previousChannelId);
|
||||
actions.push({
|
||||
type: ChannelTypes.SELECT_CHANNEL,
|
||||
data: channelId,
|
||||
@@ -237,10 +242,11 @@ export function handleSelectChannel(channelId) {
|
||||
teamId: channel.team_id || currentTeamId,
|
||||
},
|
||||
});
|
||||
dispatch(batchActions(actions, 'BATCH_SWITCH_CHANNEL'));
|
||||
}
|
||||
|
||||
console.log('channel switch to', channel?.display_name, channelId, (Date.now() - dt), 'ms'); //eslint-disable-line
|
||||
dispatch(batchActions(actions, 'BATCH_SWITCH_CHANNEL'));
|
||||
|
||||
console.log('channel switch to', channel?.display_name, channelId, (Date.now() - dt), 'ms'); //eslint-disable-line
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -321,7 +327,9 @@ export function markAsViewedAndReadBatch(state, channelId, prevChannelId = '', m
|
||||
const prevChannel = (!prevChanManuallyUnread && prevChannelId) ? channels[prevChannelId] : null; // May be null since prevChannelId is optional
|
||||
|
||||
if (markOnServer) {
|
||||
Client4.viewMyChannel(channelId, prevChanManuallyUnread ? '' : prevChannelId);
|
||||
Client4.viewMyChannel(channelId, prevChanManuallyUnread ? '' : prevChannelId).catch(() => {
|
||||
// do nothing just adding the handler to avoid the warning
|
||||
});
|
||||
}
|
||||
|
||||
if (member) {
|
||||
@@ -600,7 +608,12 @@ export function loadChannelsForTeam(teamId, skipDispatch = false) {
|
||||
return async (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const currentUserId = getCurrentUserId(state);
|
||||
const data = {sync: true, teamId};
|
||||
const data = {
|
||||
sync: true,
|
||||
teamId,
|
||||
teamChannels: getChannelsIdForTeam(state, teamId),
|
||||
};
|
||||
|
||||
const actions = [];
|
||||
|
||||
if (currentUserId) {
|
||||
@@ -639,12 +652,17 @@ export function loadChannelsForTeam(teamId, skipDispatch = false) {
|
||||
}
|
||||
|
||||
if (rolesToLoad.size > 0) {
|
||||
data.roles = await Client4.getRolesByNames(Array.from(rolesToLoad));
|
||||
if (data.roles.length) {
|
||||
actions.push({
|
||||
type: RoleTypes.RECEIVED_ROLES,
|
||||
data: data.roles,
|
||||
});
|
||||
try {
|
||||
data.roles = await Client4.getRolesByNames(Array.from(rolesToLoad));
|
||||
if (data.roles.length) {
|
||||
actions.push({
|
||||
type: RoleTypes.RECEIVED_ROLES,
|
||||
data: data.roles,
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
//eslint-disable-next-line no-console
|
||||
console.log('Could not retrieve channel members roles for the user');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -320,7 +320,7 @@ describe('Actions.Views.Channel', () => {
|
||||
teamId: currentTeamId,
|
||||
},
|
||||
};
|
||||
if (channelId.includes('not') || channelId === currentChannelId) {
|
||||
if (channelId.includes('not')) {
|
||||
expect(selectChannelWithMember).toBe(undefined);
|
||||
} else {
|
||||
expect(selectChannelWithMember).toStrictEqual(expectedSelectChannelWithMember);
|
||||
|
||||
@@ -55,7 +55,7 @@ export function getEmojisInPosts(posts) {
|
||||
const emojisToLoad = getNeededCustomEmojis(state, posts);
|
||||
|
||||
if (emojisToLoad?.size > 0) {
|
||||
const promises = emojisToLoad.map((name) => getCustomEmojiByName(name));
|
||||
const promises = Array.from(emojisToLoad).map((name) => getCustomEmojiByName(name));
|
||||
const result = await Promise.all(promises);
|
||||
const actions = [];
|
||||
const data = [];
|
||||
|
||||
@@ -87,7 +87,10 @@ export function getPosts(channelId, page = 0, perPage = Posts.POST_CHUNK_SIZE) {
|
||||
const postForChannel = postsInChannel[channelId];
|
||||
const data = await Client4.getPosts(channelId, page, perPage);
|
||||
const posts = Object.values(data.posts);
|
||||
const actions = [];
|
||||
const actions = [{
|
||||
type: ViewTypes.SET_CHANNEL_RETRY_FAILED,
|
||||
failed: false,
|
||||
}];
|
||||
|
||||
if (posts?.length) {
|
||||
actions.push(receivedPosts(data));
|
||||
|
||||
@@ -145,7 +145,7 @@ export function purgeOfflineStore() {
|
||||
|
||||
dispatch({
|
||||
type: General.OFFLINE_STORE_PURGE,
|
||||
state: getStateForReset(initialState, currentState),
|
||||
data: getStateForReset(initialState, currentState),
|
||||
});
|
||||
|
||||
EventEmitter.emit(NavigationTypes.RESTART_APP);
|
||||
|
||||
@@ -32,10 +32,16 @@ export function selectDefaultTeam() {
|
||||
const state = getState();
|
||||
|
||||
const {ExperimentalPrimaryTeam} = getConfig(state);
|
||||
const {teams: allTeams, myMembers} = state.entities.teams;
|
||||
const teams = Object.keys(myMembers).map((key) => allTeams[key]);
|
||||
const {teams, myMembers} = state.entities.teams;
|
||||
const myTeams = Object.keys(teams).reduce((result, id) => {
|
||||
if (myMembers[id]) {
|
||||
result.push(teams[id]);
|
||||
}
|
||||
|
||||
let defaultTeam = selectFirstAvailableTeam(teams, ExperimentalPrimaryTeam);
|
||||
return result;
|
||||
}, []);
|
||||
|
||||
let defaultTeam = selectFirstAvailableTeam(myTeams, ExperimentalPrimaryTeam);
|
||||
|
||||
if (defaultTeam) {
|
||||
dispatch(handleTeamChange(defaultTeam.id));
|
||||
|
||||
@@ -183,13 +183,17 @@ export function login(loginId, password, mfaToken, ldapOnly = false) {
|
||||
}
|
||||
|
||||
export function ssoLogin(token) {
|
||||
return async (dispatch) => {
|
||||
return async (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const deviceToken = state.entities?.general?.deviceToken;
|
||||
|
||||
Client4.setToken(token);
|
||||
await setCSRFFromCookie(Client4.getUrl());
|
||||
|
||||
const result = await dispatch(loadMe());
|
||||
|
||||
if (!result.error) {
|
||||
dispatch(completeLogin(result.data.user));
|
||||
dispatch(completeLogin(result.data.user, deviceToken));
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -9,7 +9,6 @@ import {
|
||||
Text,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import Fuse from 'fuse.js';
|
||||
|
||||
import AutocompleteDivider from '@components/autocomplete/autocomplete_divider';
|
||||
import Emoji from '@components/emoji';
|
||||
@@ -21,15 +20,6 @@ import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
const EMOJI_REGEX = /(^|\s|^\+|^-)(:([^:\s]*))$/i;
|
||||
const EMOJI_REGEX_WITHOUT_PREFIX = /\B(:([^:\s]*))$/i;
|
||||
|
||||
const options = {
|
||||
shouldSort: true,
|
||||
threshold: 0.3,
|
||||
location: 0,
|
||||
distance: 100,
|
||||
minMatchCharLength: 2,
|
||||
maxPatternLength: 32,
|
||||
};
|
||||
|
||||
export default class EmojiSuggestion extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
@@ -39,6 +29,7 @@ export default class EmojiSuggestion extends PureComponent {
|
||||
cursorPosition: PropTypes.number,
|
||||
customEmojisEnabled: PropTypes.bool,
|
||||
emojis: PropTypes.array.isRequired,
|
||||
fuse: PropTypes.object.isRequired,
|
||||
isSearch: PropTypes.bool,
|
||||
maxListHeight: PropTypes.number,
|
||||
theme: PropTypes.object.isRequired,
|
||||
@@ -63,23 +54,16 @@ export default class EmojiSuggestion extends PureComponent {
|
||||
super(props);
|
||||
|
||||
this.matchTerm = '';
|
||||
const list = props.emojis || [];
|
||||
this.fuse = new Fuse(list, options);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
componentDidUpdate() {
|
||||
if (this.props.isSearch) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {cursorPosition, emojis, value} = this.props;
|
||||
const {cursorPosition, value} = this.props;
|
||||
const match = value.substring(0, cursorPosition).match(EMOJI_REGEX);
|
||||
|
||||
if (prevProps.emojis !== emojis) {
|
||||
const list = emojis || [];
|
||||
this.fuse = new Fuse(list, options);
|
||||
}
|
||||
|
||||
if (!match || this.state.emojiComplete) {
|
||||
this.resetAutocomplete();
|
||||
return;
|
||||
@@ -91,7 +75,7 @@ export default class EmojiSuggestion extends PureComponent {
|
||||
if (this.props.customEmojisEnabled) {
|
||||
this.props.actions.autocompleteCustomEmojis(this.matchTerm);
|
||||
}
|
||||
this.searchEmoji(this.matchTerm);
|
||||
this.searchEmojis(this.matchTerm);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,17 +129,6 @@ export default class EmojiSuggestion extends PureComponent {
|
||||
|
||||
getItemLayout = ({index}) => ({length: 40, offset: 40 * index, index})
|
||||
|
||||
handleFuzzySearch = (matchTerm) => {
|
||||
const {emojis} = this.props;
|
||||
|
||||
clearTimeout(this.searchTermTimeout);
|
||||
this.searchTermTimeout = setTimeout(() => {
|
||||
const results = this.fuse.search(matchTerm.toLowerCase()).map((r) => r.refIndex);
|
||||
const data = results.map((index) => emojis[index]);
|
||||
this.setEmojiData(data, matchTerm);
|
||||
}, 100);
|
||||
};
|
||||
|
||||
keyExtractor = (item) => item;
|
||||
|
||||
renderItem = ({item}) => {
|
||||
@@ -188,26 +161,38 @@ export default class EmojiSuggestion extends PureComponent {
|
||||
this.props.onResultCountChange(0);
|
||||
}
|
||||
|
||||
searchEmoji = (matchTerm) => {
|
||||
if (matchTerm.length) {
|
||||
this.handleFuzzySearch(matchTerm);
|
||||
} else {
|
||||
this.setEmojiData(this.props.emojis);
|
||||
}
|
||||
}
|
||||
searchEmojis = (searchTerm) => {
|
||||
const {emojis, fuse} = this.props;
|
||||
|
||||
setEmojiData = (data, matchTerm = null) => {
|
||||
let sorter = compareEmojis;
|
||||
if (matchTerm) {
|
||||
sorter = (a, b) => compareEmojis(a, b, matchTerm);
|
||||
if (searchTerm.trim().length) {
|
||||
const searchTermLowerCase = searchTerm.toLowerCase();
|
||||
|
||||
sorter = (a, b) => compareEmojis(a, b, searchTermLowerCase);
|
||||
clearTimeout(this.searchTermTimeout);
|
||||
|
||||
this.searchTermTimeout = setTimeout(() => {
|
||||
const fuzz = fuse.search(searchTerm);
|
||||
const results = fuzz.reduce((values, r) => {
|
||||
const v = r.matches[0]?.value;
|
||||
if (v) {
|
||||
values.push(v);
|
||||
}
|
||||
|
||||
return values;
|
||||
}, []);
|
||||
const data = results.sort(sorter);
|
||||
this.setState({
|
||||
active: data.length > 0,
|
||||
dataSource: data,
|
||||
});
|
||||
}, 100);
|
||||
} else {
|
||||
this.setState({
|
||||
active: emojis.length > 0,
|
||||
dataSource: emojis.sort(sorter),
|
||||
});
|
||||
}
|
||||
|
||||
this.setState({
|
||||
active: data.length > 0,
|
||||
dataSource: data.sort(sorter),
|
||||
});
|
||||
|
||||
this.props.onResultCountChange(data.length);
|
||||
};
|
||||
|
||||
render() {
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import Fuse from 'fuse.js';
|
||||
|
||||
import Preferences from '@mm-redux/constants/preferences';
|
||||
import {selectEmojisByName} from '@selectors/emojis';
|
||||
import initialState from '@store/initial_state';
|
||||
import {shallowWithIntl} from 'test/intl-test-helper';
|
||||
|
||||
import EmojiSuggestion from './emoji_suggestion';
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
describe('components/autocomplete/emoji_suggestion', () => {
|
||||
const state = {
|
||||
...initialState,
|
||||
views: {
|
||||
recentEmojis: [],
|
||||
},
|
||||
};
|
||||
const emojis = selectEmojisByName(state);
|
||||
const options = {
|
||||
shouldSort: false,
|
||||
threshold: 0.3,
|
||||
location: 0,
|
||||
distance: 10,
|
||||
includeMatches: true,
|
||||
findAllMatches: true,
|
||||
};
|
||||
const fuse = new Fuse(emojis, options);
|
||||
const baseProps = {
|
||||
actions: {
|
||||
addReactionToLatestPost: jest.fn(),
|
||||
autocompleteCustomEmojis: jest.fn(),
|
||||
},
|
||||
cursorPosition: 0,
|
||||
customEmojisEnabled: false,
|
||||
emojis,
|
||||
fuse,
|
||||
isSearch: false,
|
||||
theme: Preferences.THEMES.default,
|
||||
onChangeText: jest.fn(),
|
||||
onResultCountChange: jest.fn(),
|
||||
rootId: '',
|
||||
value: '',
|
||||
nestedScrollEnabled: false,
|
||||
};
|
||||
|
||||
test('should match snapshot', () => {
|
||||
const wrapper = shallowWithIntl(<EmojiSuggestion {...baseProps}/>);
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
|
||||
wrapper.setProps({cursorPosition: 1, value: ':1'});
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('searchEmojis should return the right values on fuse', () => {
|
||||
const output1 = ['100', '1234', '1st_place_medal', '+1', '-1', 'u7121'];
|
||||
const output2 = ['+1'];
|
||||
const output3 = ['-1'];
|
||||
|
||||
const wrapper = shallowWithIntl(<EmojiSuggestion {...baseProps}/>);
|
||||
wrapper.instance().searchEmojis('');
|
||||
expect(wrapper.state('dataSource')).toEqual(baseProps.emojis);
|
||||
|
||||
wrapper.instance().searchEmojis('1');
|
||||
jest.runAllTimers();
|
||||
setTimeout(() => {
|
||||
expect(wrapper.state('dataSource')).toEqual(output1);
|
||||
}, 100);
|
||||
|
||||
wrapper.instance().searchEmojis('+');
|
||||
jest.runAllTimers();
|
||||
setTimeout(() => {
|
||||
expect(wrapper.state('dataSource')).toEqual(output2);
|
||||
}, 100);
|
||||
|
||||
wrapper.instance().searchEmojis('-');
|
||||
jest.runAllTimers();
|
||||
setTimeout(() => {
|
||||
expect(wrapper.state('dataSource')).toEqual(output3);
|
||||
}, 100);
|
||||
});
|
||||
});
|
||||
@@ -3,34 +3,32 @@
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import Fuse from 'fuse.js';
|
||||
|
||||
import {getCustomEmojisByName} from '@mm-redux/selectors/entities/emojis';
|
||||
import {getConfig} from '@mm-redux/selectors/entities/general';
|
||||
import {addReactionToLatestPost} from '@actions/views/emoji';
|
||||
import {autocompleteCustomEmojis} from '@mm-redux/actions/emojis';
|
||||
import {createIdsSelector} from '@mm-redux/utils/helpers';
|
||||
|
||||
import {addReactionToLatestPost} from 'app/actions/views/emoji';
|
||||
import {getConfig} from '@mm-redux/selectors/entities/general';
|
||||
import {getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
import {EmojiIndicesByAlias} from 'app/utils/emojis';
|
||||
import {selectEmojisByName} from '@selectors/emojis';
|
||||
|
||||
import EmojiSuggestion from './emoji_suggestion';
|
||||
|
||||
const getEmojisByName = createIdsSelector(
|
||||
getCustomEmojisByName,
|
||||
(customEmojis) => {
|
||||
const emoticons = new Set();
|
||||
for (const [key] of [...EmojiIndicesByAlias.entries(), ...customEmojis.entries()]) {
|
||||
emoticons.add(key);
|
||||
}
|
||||
|
||||
return Array.from(emoticons);
|
||||
},
|
||||
);
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const emojis = getEmojisByName(state);
|
||||
const emojis = selectEmojisByName(state);
|
||||
const options = {
|
||||
shouldSort: false,
|
||||
threshold: 0.3,
|
||||
location: 0,
|
||||
distance: 10,
|
||||
includeMatches: true,
|
||||
findAllMatches: true,
|
||||
};
|
||||
|
||||
const list = emojis.length ? emojis : [];
|
||||
const fuse = new Fuse(list, options);
|
||||
|
||||
return {
|
||||
fuse,
|
||||
emojis,
|
||||
customEmojisEnabled: getConfig(state).EnableCustomEmoji === 'true',
|
||||
theme: getTheme(state),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -47,25 +47,27 @@ export default class EmojiPicker extends EmojiPickerBase {
|
||||
keyboardVerticalOffset={keyboardOffset}
|
||||
style={styles.flex}
|
||||
>
|
||||
<View style={[styles.searchBar, padding(isLandscape)]}>
|
||||
<SearchBar
|
||||
ref={this.setSearchBarRef}
|
||||
placeholder={formatMessage({id: 'search_bar.search', defaultMessage: 'Search'})}
|
||||
cancelTitle={formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'})}
|
||||
backgroundColor='transparent'
|
||||
inputHeight={33}
|
||||
inputStyle={searchBarInput}
|
||||
placeholderTextColor={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
tintColorSearch={changeOpacity(theme.centerChannelColor, 0.8)}
|
||||
tintColorDelete={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
titleCancelColor={theme.centerChannelColor}
|
||||
onChangeText={this.changeSearchTerm}
|
||||
onCancelButtonPress={this.cancelSearch}
|
||||
autoCapitalize='none'
|
||||
value={searchTerm}
|
||||
keyboardAppearance={getKeyboardAppearanceFromTheme(theme)}
|
||||
onAnimationComplete={this.setRebuiltEmojis}
|
||||
/>
|
||||
<View style={styles.searchBar}>
|
||||
<View style={padding(isLandscape)}>
|
||||
<SearchBar
|
||||
ref={this.setSearchBarRef}
|
||||
placeholder={formatMessage({id: 'search_bar.search', defaultMessage: 'Search'})}
|
||||
cancelTitle={formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'})}
|
||||
backgroundColor='transparent'
|
||||
inputHeight={33}
|
||||
inputStyle={searchBarInput}
|
||||
placeholderTextColor={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
tintColorSearch={changeOpacity(theme.centerChannelColor, 0.8)}
|
||||
tintColorDelete={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
titleCancelColor={theme.centerChannelColor}
|
||||
onChangeText={this.changeSearchTerm}
|
||||
onCancelButtonPress={this.cancelSearch}
|
||||
autoCapitalize='none'
|
||||
value={searchTerm}
|
||||
keyboardAppearance={getKeyboardAppearanceFromTheme(theme)}
|
||||
onAnimationComplete={this.setRebuiltEmojis}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<View style={[styles.container]}>
|
||||
{this.renderListComponent(shorten)}
|
||||
|
||||
@@ -2,14 +2,35 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import Fuse from 'fuse.js';
|
||||
|
||||
import Preferences from '@mm-redux/constants/preferences';
|
||||
|
||||
import {selectEmojisByName, selectEmojisBySection} from '@selectors/emojis';
|
||||
import initialState from '@store/initial_state';
|
||||
import {shallowWithIntl} from 'test/intl-test-helper';
|
||||
|
||||
import {filterEmojiSearchInput} from './emoji_picker_base';
|
||||
import EmojiPicker from './emoji_picker.ios';
|
||||
|
||||
describe('components/emoji_picker/EmojiPicker', () => {
|
||||
describe('components/emoji_picker/emoji_picker.ios', () => {
|
||||
const state = {
|
||||
...initialState,
|
||||
views: {
|
||||
recentEmojis: [],
|
||||
},
|
||||
};
|
||||
const emojis = selectEmojisByName(state);
|
||||
const emojisBySection = selectEmojisBySection(state);
|
||||
const options = {
|
||||
shouldSort: false,
|
||||
threshold: 0.3,
|
||||
location: 0,
|
||||
distance: 10,
|
||||
includeMatches: true,
|
||||
findAllMatches: true,
|
||||
};
|
||||
const fuse = new Fuse(emojis, options);
|
||||
|
||||
const baseProps = {
|
||||
actions: {
|
||||
getCustomEmojis: jest.fn(),
|
||||
@@ -19,9 +40,9 @@ describe('components/emoji_picker/EmojiPicker', () => {
|
||||
customEmojisEnabled: false,
|
||||
customEmojiPage: 200,
|
||||
deviceWidth: 400,
|
||||
emojis: [],
|
||||
emojisBySection: [],
|
||||
fuse: {},
|
||||
emojis,
|
||||
emojisBySection,
|
||||
fuse,
|
||||
isLandscape: false,
|
||||
theme: Preferences.THEMES.default,
|
||||
};
|
||||
@@ -48,6 +69,15 @@ describe('components/emoji_picker/EmojiPicker', () => {
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('searchEmojis should return the right values on fuse', () => {
|
||||
const input = '1';
|
||||
const output = ['100', '1234', '1st_place_medal', '+1', '-1', 'u7121'];
|
||||
|
||||
const wrapper = shallowWithIntl(<EmojiPicker {...baseProps}/>);
|
||||
const result = wrapper.instance().searchEmojis(input);
|
||||
expect(result).toEqual(output);
|
||||
});
|
||||
|
||||
test('should set rebuildEmojis to true when deviceWidth changes', () => {
|
||||
const wrapper = shallowWithIntl(<EmojiPicker {...baseProps}/>);
|
||||
const instance = wrapper.instance();
|
||||
@@ -84,7 +114,7 @@ describe('components/emoji_picker/EmojiPicker', () => {
|
||||
const setRebuiltEmojis = jest.spyOn(instance, 'setRebuiltEmojis');
|
||||
setRebuiltEmojis(searchBarAnimationComplete);
|
||||
|
||||
expect(instance.setState).toHaveBeenCalledWith({emojis: []});
|
||||
expect(instance.setState).toHaveBeenCalledTimes(1);
|
||||
expect(instance.rebuildEmojis).toBe(false);
|
||||
});
|
||||
|
||||
|
||||
@@ -203,16 +203,24 @@ export default class EmojiPicker extends PureComponent {
|
||||
};
|
||||
|
||||
searchEmojis = (searchTerm) => {
|
||||
const {emojis, fuse} = this.props;
|
||||
const {fuse} = this.props;
|
||||
const searchTermLowerCase = searchTerm.toLowerCase();
|
||||
|
||||
if (!searchTerm) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const results = fuse.search(searchTermLowerCase).map((r) => r.refIndex);
|
||||
const sorter = (a, b) => compareEmojis(a, b, searchTerm);
|
||||
const data = results.map((index) => emojis[index]).sort(sorter);
|
||||
const sorter = (a, b) => compareEmojis(a, b, searchTermLowerCase);
|
||||
const fuzz = fuse.search(searchTermLowerCase);
|
||||
const results = fuzz.reduce((values, r) => {
|
||||
const v = r.matches[0]?.value;
|
||||
if (v) {
|
||||
values.push(v);
|
||||
}
|
||||
|
||||
return values;
|
||||
}, []);
|
||||
const data = results.sort(sorter);
|
||||
|
||||
return data;
|
||||
};
|
||||
@@ -295,6 +303,7 @@ export default class EmojiPicker extends PureComponent {
|
||||
<View style={[style.flatListEmoji, padding(this.props.isLandscape)]}>
|
||||
<Emoji
|
||||
emojiName={item}
|
||||
textStyle={style.emojiText}
|
||||
size={20}
|
||||
/>
|
||||
</View>
|
||||
@@ -476,6 +485,10 @@ export const getStyleSheetFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
flex: 1,
|
||||
},
|
||||
emojiText: {
|
||||
color: '#000',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
flatList: {
|
||||
flex: 1,
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
|
||||
@@ -3,154 +3,28 @@
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {createSelector} from 'reselect';
|
||||
import Fuse from 'fuse.js';
|
||||
|
||||
import {incrementEmojiPickerPage} from '@actions/views/emoji';
|
||||
import {getCustomEmojis} from '@mm-redux/actions/emojis';
|
||||
import {getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
import {getCustomEmojisByName} from '@mm-redux/selectors/entities/emojis';
|
||||
import {getConfig} from '@mm-redux/selectors/entities/general';
|
||||
import {createIdsSelector} from '@mm-redux/utils/helpers';
|
||||
import {getDimensions, isLandscape} from '@selectors/device';
|
||||
import {BuiltInEmojis, CategoryNames, Emojis, EmojiIndicesByAlias, EmojiIndicesByCategory} from '@utils/emojis';
|
||||
import {t} from '@utils/i18n';
|
||||
import {selectEmojisByName, selectEmojisBySection} from '@selectors/emojis';
|
||||
|
||||
import EmojiPicker from './emoji_picker';
|
||||
|
||||
const categoryToI18n = {
|
||||
activity: {
|
||||
id: t('mobile.emoji_picker.activity'),
|
||||
defaultMessage: 'ACTIVITY',
|
||||
icon: 'futbol-o',
|
||||
},
|
||||
custom: {
|
||||
id: t('mobile.emoji_picker.custom'),
|
||||
defaultMessage: 'CUSTOM',
|
||||
icon: 'at',
|
||||
},
|
||||
flags: {
|
||||
id: t('mobile.emoji_picker.flags'),
|
||||
defaultMessage: 'FLAGS',
|
||||
icon: 'flag-o',
|
||||
},
|
||||
foods: {
|
||||
id: t('mobile.emoji_picker.foods'),
|
||||
defaultMessage: 'FOODS',
|
||||
icon: 'cutlery',
|
||||
},
|
||||
nature: {
|
||||
id: t('mobile.emoji_picker.nature'),
|
||||
defaultMessage: 'NATURE',
|
||||
icon: 'leaf',
|
||||
},
|
||||
objects: {
|
||||
id: t('mobile.emoji_picker.objects'),
|
||||
defaultMessage: 'OBJECTS',
|
||||
icon: 'lightbulb-o',
|
||||
},
|
||||
people: {
|
||||
id: t('mobile.emoji_picker.people'),
|
||||
defaultMessage: 'PEOPLE',
|
||||
icon: 'smile-o',
|
||||
},
|
||||
places: {
|
||||
id: t('mobile.emoji_picker.places'),
|
||||
defaultMessage: 'PLACES',
|
||||
icon: 'plane',
|
||||
},
|
||||
recent: {
|
||||
id: t('mobile.emoji_picker.recent'),
|
||||
defaultMessage: 'RECENTLY USED',
|
||||
icon: 'clock-o',
|
||||
},
|
||||
symbols: {
|
||||
id: t('mobile.emoji_picker.symbols'),
|
||||
defaultMessage: 'SYMBOLS',
|
||||
icon: 'heart-o',
|
||||
},
|
||||
};
|
||||
|
||||
function fillEmoji(indice) {
|
||||
const emoji = Emojis[indice];
|
||||
return {
|
||||
name: emoji.aliases[0],
|
||||
aliases: emoji.aliases,
|
||||
};
|
||||
}
|
||||
|
||||
const getEmojisBySection = createSelector(
|
||||
getCustomEmojisByName,
|
||||
(state) => state.views.recentEmojis,
|
||||
(customEmojis, recentEmojis) => {
|
||||
const emoticons = CategoryNames.filter((name) => name !== 'custom').map((category) => {
|
||||
const items = EmojiIndicesByCategory.get(category).map(fillEmoji);
|
||||
|
||||
const section = {
|
||||
...categoryToI18n[category],
|
||||
key: category,
|
||||
data: items,
|
||||
};
|
||||
|
||||
return section;
|
||||
});
|
||||
|
||||
const customEmojiItems = [];
|
||||
BuiltInEmojis.forEach((emoji) => {
|
||||
customEmojiItems.push({
|
||||
name: emoji,
|
||||
});
|
||||
});
|
||||
|
||||
for (const [key] of customEmojis) {
|
||||
customEmojiItems.push({
|
||||
name: key,
|
||||
});
|
||||
}
|
||||
|
||||
emoticons.push({
|
||||
...categoryToI18n.custom,
|
||||
key: 'custom',
|
||||
data: customEmojiItems,
|
||||
});
|
||||
|
||||
if (recentEmojis.length) {
|
||||
const items = recentEmojis.map((emoji) => ({name: emoji}));
|
||||
|
||||
emoticons.unshift({
|
||||
...categoryToI18n.recent,
|
||||
key: 'recent',
|
||||
data: items,
|
||||
});
|
||||
}
|
||||
|
||||
return emoticons;
|
||||
},
|
||||
);
|
||||
|
||||
const getEmojisByName = createIdsSelector(
|
||||
getCustomEmojisByName,
|
||||
(customEmojis) => {
|
||||
const emoticons = new Set();
|
||||
for (const [key] of [...EmojiIndicesByAlias.entries(), ...customEmojis.entries()]) {
|
||||
emoticons.add(key);
|
||||
}
|
||||
|
||||
return Array.from(emoticons);
|
||||
},
|
||||
);
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const emojisBySection = getEmojisBySection(state);
|
||||
const emojis = getEmojisByName(state);
|
||||
const emojisBySection = selectEmojisBySection(state);
|
||||
const emojis = selectEmojisByName(state);
|
||||
const {deviceWidth} = getDimensions(state);
|
||||
const options = {
|
||||
shouldSort: true,
|
||||
shouldSort: false,
|
||||
threshold: 0.3,
|
||||
location: 0,
|
||||
distance: 100,
|
||||
minMatchCharLength: 2,
|
||||
maxPatternLength: 32,
|
||||
distance: 10,
|
||||
includeMatches: true,
|
||||
findAllMatches: true,
|
||||
};
|
||||
|
||||
const list = emojis.length ? emojis : [];
|
||||
|
||||
@@ -3,39 +3,48 @@
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {View} from 'react-native';
|
||||
import {Text, View} from 'react-native';
|
||||
import {intlShape} from 'react-intl';
|
||||
import Button from 'react-native-button';
|
||||
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
import {t} from 'app/utils/i18n';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
import Cloud from './cloud';
|
||||
|
||||
export default class FailedNetworkAction extends PureComponent {
|
||||
static propTypes = {
|
||||
onRetry: PropTypes.func.isRequired,
|
||||
actionId: PropTypes.string,
|
||||
actionDefaultMessage: PropTypes.string,
|
||||
errorId: PropTypes.string,
|
||||
errorDefaultMessage: PropTypes.string,
|
||||
actionText: PropTypes.string,
|
||||
errorMessage: PropTypes.string,
|
||||
errorTitle: PropTypes.string,
|
||||
theme: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
actionId: t('mobile.failed_network_action.retry'),
|
||||
actionDefaultMessage: 'try again',
|
||||
errorId: t('mobile.failed_network_action.shortDescription'),
|
||||
errorDefaultMessage: 'Messages will load when you have an internet connection or {tryAgainAction}.',
|
||||
showAction: true,
|
||||
};
|
||||
|
||||
render() {
|
||||
const {actionId, actionDefaultMessage, errorId, errorDefaultMessage, onRetry, theme} = this.props;
|
||||
const {formatMessage} = this.context.intl;
|
||||
const {onRetry, theme} = this.props;
|
||||
const style = getStyleFromTheme(theme);
|
||||
|
||||
const errorTitle = {
|
||||
id: t('mobile.failed_network_action.title'),
|
||||
const actionText = this.props.actionText || formatMessage({
|
||||
id: 'mobile.failed_network_action.retry',
|
||||
defaultMessage: 'Try again',
|
||||
});
|
||||
const errorTitle = this.props.errorTitle || formatMessage({
|
||||
id: 'mobile.failed_network_action.title',
|
||||
defaultMessage: 'No internet connection',
|
||||
};
|
||||
});
|
||||
const errorMessage = this.props.errorMessage || formatMessage({
|
||||
id: 'mobile.failed_network_action.shortDescription',
|
||||
defaultMessage: 'Messages will load when you have an internet connection.',
|
||||
});
|
||||
|
||||
return (
|
||||
<View style={style.container}>
|
||||
@@ -44,26 +53,14 @@ export default class FailedNetworkAction extends PureComponent {
|
||||
height={76}
|
||||
width={76}
|
||||
/>
|
||||
<FormattedText
|
||||
id={errorTitle.id}
|
||||
defaultMessage={errorTitle.defaultMessage}
|
||||
style={style.title}
|
||||
/>
|
||||
<FormattedText
|
||||
id={errorId}
|
||||
defaultMessage={errorDefaultMessage}
|
||||
style={style.description}
|
||||
values={{
|
||||
tryAgainAction: (
|
||||
<FormattedText
|
||||
id={actionId}
|
||||
defaultMessage={actionDefaultMessage}
|
||||
style={style.link}
|
||||
onPress={onRetry}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Text style={style.title}>{errorTitle}</Text>
|
||||
<Text style={style.description}>{errorMessage}</Text>
|
||||
<Button
|
||||
onPress={onRetry}
|
||||
containerStyle={style.buttonContainer}
|
||||
>
|
||||
<Text style={style.link}>{actionText}</Text>
|
||||
</Button>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -92,7 +89,16 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
textAlign: 'center',
|
||||
},
|
||||
link: {
|
||||
color: theme.linkColor,
|
||||
color: theme.buttonColor,
|
||||
fontSize: 15,
|
||||
},
|
||||
buttonContainer: {
|
||||
backgroundColor: theme.buttonBg,
|
||||
borderRadius: 5,
|
||||
height: 42,
|
||||
justifyContent: 'center',
|
||||
marginTop: 20,
|
||||
paddingHorizontal: 12,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
View,
|
||||
StyleSheet,
|
||||
} from 'react-native';
|
||||
import FastImage from 'react-native-fast-image';
|
||||
|
||||
import brokenImageIcon from '@assets/images/icons/brokenimage.png';
|
||||
import ProgressiveImage from '@components/progressive_image';
|
||||
@@ -49,29 +48,9 @@ export default class FileAttachmentImage extends PureComponent {
|
||||
resizeMethod: 'resize',
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const {file} = props;
|
||||
if (file && file.id && !file.localPath) {
|
||||
const headers = Client4.getOptions({}).headers;
|
||||
|
||||
const preloadImages = [
|
||||
{uri: Client4.getFileThumbnailUrl(file.id), headers},
|
||||
{uri: Client4.getFileUrl(file.id), headers},
|
||||
];
|
||||
|
||||
if (isGif(file)) {
|
||||
preloadImages.push({uri: Client4.getFilePreviewUrl(file.id), headers});
|
||||
}
|
||||
|
||||
FastImage.preload(preloadImages);
|
||||
}
|
||||
|
||||
this.state = {
|
||||
failed: false,
|
||||
};
|
||||
}
|
||||
state = {
|
||||
failed: false,
|
||||
};
|
||||
|
||||
boxPlaceholder = () => {
|
||||
if (this.props.isSingleImage) {
|
||||
|
||||
@@ -77,7 +77,6 @@ export default class MarkdownImage extends ImageViewPort {
|
||||
uri = EphemeralStore.currentServerUrl + uri;
|
||||
}
|
||||
|
||||
FastImage.preload([{uri}]);
|
||||
return uri;
|
||||
};
|
||||
|
||||
|
||||
@@ -16,14 +16,6 @@ export default class AttachmentAuthor extends PureComponent {
|
||||
theme: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
if (props.icon) {
|
||||
FastImage.preload([{uri: props.icon}]);
|
||||
}
|
||||
}
|
||||
|
||||
openLink = () => {
|
||||
const {link} = this.props;
|
||||
if (link && Linking.canOpenURL(link)) {
|
||||
|
||||
@@ -17,14 +17,6 @@ export default class AttachmentFooter extends PureComponent {
|
||||
theme: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
if (props.icon) {
|
||||
FastImage.preload([{uri: props.icon}]);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
text,
|
||||
|
||||
@@ -312,7 +312,7 @@ export default class Post extends PureComponent {
|
||||
<TouchableWithFeedback
|
||||
onPress={this.handlePress}
|
||||
onLongPress={this.showPostOptions}
|
||||
delayLongPress={100}
|
||||
delayLongPress={200}
|
||||
underlayColor={changeOpacity(theme.centerChannelColor, 0.1)}
|
||||
cancelTouchOnPanning={true}
|
||||
>
|
||||
|
||||
@@ -110,8 +110,6 @@ export default class PostAttachmentOpenGraph extends PureComponent {
|
||||
dimensions = calculateDimensions(ogImage.height, ogImage.width, this.getViewPostWidth());
|
||||
}
|
||||
|
||||
FastImage.preload([{uri: imageUrl}]);
|
||||
|
||||
return {
|
||||
hasImage: true,
|
||||
...dimensions,
|
||||
|
||||
@@ -141,7 +141,7 @@ export default class PostDraft extends PureComponent {
|
||||
channel_id: channelId,
|
||||
root_id: rootId,
|
||||
parent_id: rootId,
|
||||
message: value.trim(),
|
||||
message: value,
|
||||
};
|
||||
|
||||
createPost(post, postFiles);
|
||||
|
||||
@@ -151,7 +151,7 @@ export default class UploadItem extends PureComponent {
|
||||
|
||||
const certificate = await mattermostBucket.getPreference('cert');
|
||||
const options = {
|
||||
timeout: 10000,
|
||||
timeout: 60000,
|
||||
certificate,
|
||||
};
|
||||
this.uploadPromise = RNFetchBlob.config(options).fetch('POST', Client4.getFilesRoute(), headers, data);
|
||||
|
||||
@@ -61,7 +61,6 @@ export default class ProfilePicture extends PureComponent {
|
||||
this.setImageURL(imageUri);
|
||||
} else if (user) {
|
||||
const uri = Client4.getProfilePictureUrl(user.id, user.last_picture_update);
|
||||
FastImage.preload([{uri, headers: Client4.getOptions({}).headers}]);
|
||||
|
||||
this.setImageURL(uri);
|
||||
this.clearProfileImageUri();
|
||||
@@ -102,7 +101,6 @@ export default class ProfilePicture extends PureComponent {
|
||||
|
||||
if (nextUrl && url !== nextUrl) {
|
||||
// empty function is so that promise unhandled is not triggered in dev mode
|
||||
FastImage.preload([{uri: nextUrl, headers: Client4.getOptions({}).headers}]);
|
||||
this.setImageURL(nextUrl);
|
||||
this.clearProfileImageUri();
|
||||
}
|
||||
|
||||
@@ -8,10 +8,6 @@ import Preferences from '@mm-redux/constants/preferences';
|
||||
|
||||
import ProgressiveImage from './progressive_image';
|
||||
|
||||
jest.mock('react-native-fast-image', () => ({
|
||||
preload: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
describe('ProgressiveImage', () => {
|
||||
|
||||
@@ -68,7 +68,7 @@ export default class Root extends PureComponent {
|
||||
const {currentUrl, theme} = this.props;
|
||||
const {intl} = this.providerRef.getChildContext();
|
||||
|
||||
let passProps = null;
|
||||
let passProps = {theme};
|
||||
const options = {topBar: {}};
|
||||
if (Platform.OS === 'android') {
|
||||
options.topBar.rightButtons = [{
|
||||
@@ -87,6 +87,7 @@ export default class Root extends PureComponent {
|
||||
|
||||
if (screen === 'SelectTeam') {
|
||||
passProps = {
|
||||
...passProps,
|
||||
currentUrl,
|
||||
userWithoutTeams: true,
|
||||
};
|
||||
|
||||
@@ -38,6 +38,7 @@ export default class Search extends PureComponent {
|
||||
tintColorDelete: PropTypes.string,
|
||||
selectionColor: PropTypes.string,
|
||||
inputStyle: CustomPropTypes.Style,
|
||||
containerStyle: CustomPropTypes.Style,
|
||||
cancelButtonStyle: CustomPropTypes.Style,
|
||||
autoFocus: PropTypes.bool,
|
||||
placeholder: PropTypes.string,
|
||||
@@ -285,7 +286,7 @@ export default class Search extends PureComponent {
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={searchBarStyle.container}>
|
||||
<View style={[searchBarStyle.container, this.props.containerStyle]}>
|
||||
{((this.props.leftComponent) ?
|
||||
<Animated.View
|
||||
style={{
|
||||
@@ -385,7 +386,7 @@ const getSearchBarStyle = memoizeResult((
|
||||
justifyContent: 'flex-start',
|
||||
alignItems: 'center',
|
||||
height: containerHeight,
|
||||
flex: 1,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
clearIconColorIos: tintColorDelete || styles.defaultColor.color,
|
||||
clearIconColorAndroid: titleCancelColor || placeholderTextColor,
|
||||
|
||||
@@ -135,6 +135,7 @@ export default class ChannelsList extends PureComponent {
|
||||
backgroundColor='transparent'
|
||||
inputHeight={33}
|
||||
inputStyle={searchBarInput}
|
||||
containerStyle={styles.searchBar}
|
||||
placeholderTextColor={changeOpacity(theme.sidebarHeaderTextColor, 0.5)}
|
||||
tintColorSearch={changeOpacity(theme.sidebarHeaderTextColor, 0.5)}
|
||||
tintColorDelete={changeOpacity(theme.sidebarHeaderTextColor, 0.5)}
|
||||
@@ -225,6 +226,10 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
right: 10,
|
||||
top: 10,
|
||||
},
|
||||
searchBar: {
|
||||
flex: 1,
|
||||
overflow: 'visible',
|
||||
},
|
||||
searchContainer: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
|
||||
@@ -113,11 +113,15 @@ export default class MainSidebarIOS extends MainSidebarBase {
|
||||
};
|
||||
|
||||
render() {
|
||||
const {children} = this.props;
|
||||
const {children, currentUserId} = this.props;
|
||||
const {deviceWidth, openDrawerOffset} = this.state;
|
||||
const isTablet = DeviceTypes.IS_TABLET && !this.state.isSplitView && this.state.permanentSidebar;
|
||||
const drawerWidth = DeviceTypes.IS_TABLET ? TABLET_WIDTH : (deviceWidth - openDrawerOffset);
|
||||
|
||||
if (!currentUserId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<DrawerLayout
|
||||
ref={this.drawerRef}
|
||||
|
||||
@@ -71,7 +71,7 @@ export default class MainSidebarBase extends Component {
|
||||
|
||||
const condition = nextProps.currentTeamId !== currentTeamId ||
|
||||
nextProps.teamsCount !== teamsCount ||
|
||||
nextProps.theme !== theme;
|
||||
nextProps.theme !== theme || this.props.children !== nextProps.children;
|
||||
|
||||
if (Platform.OS === 'ios') {
|
||||
return condition ||
|
||||
@@ -263,6 +263,10 @@ export default class MainSidebarBase extends Component {
|
||||
selectChannel = (channel, currentChannelId, closeDrawer = true) => {
|
||||
const {logChannelSwitch, handleSelectChannel} = this.props.actions;
|
||||
|
||||
if (channel.id === currentChannelId) {
|
||||
return;
|
||||
}
|
||||
|
||||
logChannelSwitch(channel.id, currentChannelId);
|
||||
|
||||
tracker.channelSwitch = Date.now();
|
||||
|
||||
@@ -221,7 +221,7 @@ export default class SlideUpPanel extends PureComponent {
|
||||
|
||||
scrollToTop = () => {
|
||||
if (this.scrollViewRef?.current) {
|
||||
this.scrollViewRef.current._component.scrollTo({ //eslint-disable-line no-underscore-dangle
|
||||
this.scrollViewRef.current.scrollTo({
|
||||
x: 0,
|
||||
y: 0,
|
||||
animated: false,
|
||||
|
||||
@@ -10,10 +10,10 @@ import TeamIcon from './team_icon';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
const team = getTeam(state, ownProps.teamId);
|
||||
const lastIconUpdate = team.last_team_icon_update;
|
||||
const lastIconUpdate = team?.last_team_icon_update;
|
||||
|
||||
return {
|
||||
displayName: team.display_name,
|
||||
displayName: team?.display_name,
|
||||
lastIconUpdate,
|
||||
theme: getTheme(state),
|
||||
};
|
||||
|
||||
@@ -54,7 +54,6 @@ export default class TeamIcon extends React.PureComponent {
|
||||
|
||||
preloadTeamIcon = (teamId, lastIconUpdate) => {
|
||||
const uri = Client4.getTeamIconUrl(teamId, lastIconUpdate);
|
||||
FastImage.preload([{uri, headers: Client4.getOptions({}).headers}]);
|
||||
this.setImageURL(uri);
|
||||
};
|
||||
|
||||
@@ -87,7 +86,7 @@ export default class TeamIcon extends React.PureComponent {
|
||||
} else {
|
||||
teamIconContent = (
|
||||
<Text style={[styles.text, styleText]}>
|
||||
{displayName.substr(0, 2).toUpperCase()}
|
||||
{displayName?.substr(0, 2).toUpperCase()}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -106,17 +106,15 @@ export default class VideoControls extends PureComponent {
|
||||
});
|
||||
};
|
||||
|
||||
getPlayerStateIcon = (playerState) => {
|
||||
getControlIconAndAspectRatio = (playerState) => {
|
||||
switch (playerState) {
|
||||
case PLAYER_STATE.PAUSED:
|
||||
return playImage;
|
||||
case PLAYER_STATE.PLAYING:
|
||||
return pauseImage;
|
||||
return {icon: pauseImage, aspectRatio: 0.83};
|
||||
case PLAYER_STATE.ENDED:
|
||||
return replayImage;
|
||||
return {icon: replayImage, aspectRatio: 1.17};
|
||||
}
|
||||
|
||||
return playImage;
|
||||
return {icon: playImage, aspectRatio: 0.83};
|
||||
};
|
||||
|
||||
handleAppStateChange = (nextAppState) => {
|
||||
@@ -216,16 +214,16 @@ export default class VideoControls extends PureComponent {
|
||||
};
|
||||
|
||||
setPlayerControls = (playerState) => {
|
||||
const icon = this.getPlayerStateIcon(playerState);
|
||||
const {icon, aspectRatio} = this.getControlIconAndAspectRatio(playerState);
|
||||
const pressAction = playerState === PLAYER_STATE.ENDED ? this.onReplay : this.onPause;
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={[styles.playButton, {backgroundColor: this.props.mainColor}]}
|
||||
style={[styles.controlButton, {backgroundColor: this.props.mainColor}]}
|
||||
onPress={pressAction}
|
||||
>
|
||||
<FastImage
|
||||
source={icon}
|
||||
style={styles.playIcon}
|
||||
style={[styles.controlIcon, {aspectRatio}]}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
@@ -288,7 +286,7 @@ const styles = StyleSheet.create({
|
||||
timeRow: {
|
||||
alignSelf: 'stretch',
|
||||
},
|
||||
playButton: {
|
||||
controlButton: {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
width: 50,
|
||||
@@ -297,15 +295,8 @@ const styles = StyleSheet.create({
|
||||
borderWidth: 1.5,
|
||||
borderColor: 'rgba(255,255,255,0.5)',
|
||||
},
|
||||
playIcon: {
|
||||
width: 22,
|
||||
height: 22,
|
||||
resizeMode: 'contain',
|
||||
},
|
||||
replayIcon: {
|
||||
width: 25,
|
||||
controlIcon: {
|
||||
height: 20,
|
||||
resizeMode: 'stretch',
|
||||
},
|
||||
progressContainer: {
|
||||
position: 'absolute',
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import {Alert, AppState, Dimensions, Linking, NativeModules, Platform} from 'react-native';
|
||||
import AsyncStorage from '@react-native-community/async-storage';
|
||||
import CookieManager from '@react-native-community/cookies';
|
||||
import CookieManager from 'react-native-cookies';
|
||||
import DeviceInfo from 'react-native-device-info';
|
||||
import {getLocales} from 'react-native-localize';
|
||||
import RNFetchBlob from 'rn-fetch-blob';
|
||||
@@ -156,30 +156,35 @@ class GlobalEventHandler {
|
||||
emmProvider.handleManagedConfig(true);
|
||||
};
|
||||
|
||||
onLogout = async () => {
|
||||
Store.redux.dispatch(closeWebSocket(false));
|
||||
Store.redux.dispatch(setServerVersion(''));
|
||||
|
||||
if (analytics) {
|
||||
await analytics.reset();
|
||||
clearCookiesAndWebData = async () => {
|
||||
try {
|
||||
await CookieManager.clearAll(Platform.OS === 'ios');
|
||||
} catch (error) {
|
||||
// Nothing to clear
|
||||
}
|
||||
|
||||
removeAppCredentials();
|
||||
deleteFileCache();
|
||||
await this.resetState();
|
||||
resetMomentLocale();
|
||||
switch (Platform.OS) {
|
||||
case 'ios': {
|
||||
const mainPath = RNFetchBlob.fs.dirs.DocumentDir.split('/').slice(0, -1).join('/');
|
||||
const libraryDir = `${mainPath}/Library`;
|
||||
const cookiesDir = `${libraryDir}/Cookies`;
|
||||
const cookies = await RNFetchBlob.fs.exists(cookiesDir);
|
||||
const webkitDir = `${libraryDir}/WebKit`;
|
||||
const webkit = await RNFetchBlob.fs.exists(webkitDir);
|
||||
|
||||
// TODO: Handle when multi-server support is added
|
||||
CookieManager.clearAll(Platform.OS === 'ios');
|
||||
PushNotifications.clearNotifications();
|
||||
const cacheDir = RNFetchBlob.fs.dirs.CacheDir;
|
||||
const mainPath = cacheDir.split('/').slice(0, -1).join('/');
|
||||
if (cookies) {
|
||||
RNFetchBlob.fs.unlink(cookiesDir);
|
||||
}
|
||||
|
||||
mattermostBucket.removePreference('cert');
|
||||
mattermostBucket.removePreference('emm');
|
||||
if (Platform.OS === 'ios') {
|
||||
mattermostBucket.removeFile('entities');
|
||||
} else {
|
||||
if (webkit) {
|
||||
RNFetchBlob.fs.unlink(webkitDir);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'android': {
|
||||
const cacheDir = RNFetchBlob.fs.dirs.CacheDir;
|
||||
const mainPath = cacheDir.split('/').slice(0, -1).join('/');
|
||||
const cookies = await RNFetchBlob.fs.exists(`${mainPath}/app_webview/Cookies`);
|
||||
const cookiesJ = await RNFetchBlob.fs.exists(`${mainPath}/app_webview/Cookies-journal`);
|
||||
if (cookies) {
|
||||
@@ -189,7 +194,31 @@ class GlobalEventHandler {
|
||||
if (cookiesJ) {
|
||||
RNFetchBlob.fs.unlink(`${mainPath}/app_webview/Cookies-journal`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onLogout = async () => {
|
||||
Store.redux.dispatch(closeWebSocket(false));
|
||||
Store.redux.dispatch(setServerVersion(''));
|
||||
|
||||
if (analytics) {
|
||||
await analytics.reset();
|
||||
}
|
||||
|
||||
mattermostBucket.removePreference('cert');
|
||||
mattermostBucket.removePreference('emm');
|
||||
if (Platform.OS === 'ios') {
|
||||
mattermostBucket.removeFile('entities');
|
||||
}
|
||||
|
||||
removeAppCredentials();
|
||||
deleteFileCache();
|
||||
resetMomentLocale();
|
||||
|
||||
await this.clearCookiesAndWebData();
|
||||
PushNotifications.clearNotifications();
|
||||
|
||||
if (this.launchApp) {
|
||||
this.launchApp();
|
||||
@@ -325,7 +354,7 @@ class GlobalEventHandler {
|
||||
|
||||
return Store.redux.dispatch({
|
||||
type: General.OFFLINE_STORE_PURGE,
|
||||
state: newState,
|
||||
data: newState,
|
||||
});
|
||||
} catch (e) {
|
||||
// clear error
|
||||
|
||||
@@ -365,17 +365,20 @@ function myMembers(state: RelationOneToOne<Channel, ChannelMembership> = {}, act
|
||||
case ChannelTypes.RECEIVED_MY_CHANNELS_WITH_MEMBERS: { // Used by the mobile app
|
||||
const nextState: any = {...state};
|
||||
const current = Object.values(nextState);
|
||||
const {sync, channelMembers} = action.data;
|
||||
const {sync, teamChannels, channelMembers} = action.data;
|
||||
let hasNewValues = channelMembers && channelMembers.length > 0;
|
||||
|
||||
// Remove existing channel memberships when the user is no longer a member
|
||||
if (sync) {
|
||||
current.forEach((member: ChannelMembership) => {
|
||||
const id = member.channel_id;
|
||||
if (channelMembers.find((cm: ChannelMembership) => cm.channel_id !== id)) {
|
||||
delete nextState[id];
|
||||
hasNewValues = true;
|
||||
}
|
||||
const memberIds = channelMembers.map((cm: ChannelMembership) => cm.channel_id);
|
||||
const removedMembers = current.filter((cm: ChannelMembership) => {
|
||||
const id = cm.channel_id;
|
||||
return !memberIds.includes(id) && teamChannels.includes(id);
|
||||
});
|
||||
|
||||
removedMembers.forEach((member: ChannelMembership) => {
|
||||
delete nextState[member.channel_id];
|
||||
hasNewValues = true;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ export default ((state: Array<{error: any;displayable?: boolean;date: string}> =
|
||||
return nextState;
|
||||
}
|
||||
case ErrorTypes.LOG_ERROR: {
|
||||
const nextState = [...state];
|
||||
const nextState = state.length ? [...state] : [];
|
||||
const {displayable, error} = action;
|
||||
nextState.push({
|
||||
displayable,
|
||||
|
||||
@@ -885,7 +885,7 @@ export const getDefaultChannelForTeams: (a: GlobalState) => RelationOneToOne<Tea
|
||||
});
|
||||
|
||||
export const getMyFirstChannelForTeams: (a: GlobalState) => RelationOneToOne<Team, Channel> = createSelector(getAllChannels, getMyChannelMemberships, getMyTeams, getCurrentUser, (allChannels: IDMappedObjects<Channel>, myChannelMemberships: RelationOneToOne<Channel, ChannelMembership>, myTeams: Array<Team>, currentUser: UserProfile): RelationOneToOne<Team, Channel> => {
|
||||
const locale = currentUser.locale || General.DEFAULT_LOCALE;
|
||||
const locale = currentUser?.locale || General.DEFAULT_LOCALE;
|
||||
const result: RelationOneToOne<Team, Channel> = {};
|
||||
|
||||
for (const team of myTeams) {
|
||||
|
||||
3
app/mm-redux/types/module.d.ts
vendored
3
app/mm-redux/types/module.d.ts
vendored
@@ -2,5 +2,4 @@
|
||||
// See LICENSE.txt for license information.
|
||||
declare module 'gfycat-sdk';
|
||||
declare module 'remote-redux-devtools';
|
||||
declare module 'redux-action-buffer';
|
||||
declare module 'redux-reset';
|
||||
declare module 'redux-action-buffer';
|
||||
@@ -31,18 +31,10 @@ export default class ChannelAndroid extends ChannelBase {
|
||||
|
||||
render() {
|
||||
const {theme} = this.props;
|
||||
const channelLoadingOrFailed = this.renderLoadingOrFailedChannel();
|
||||
if (channelLoadingOrFailed) {
|
||||
return channelLoadingOrFailed;
|
||||
}
|
||||
let component = this.renderLoadingOrFailedChannel();
|
||||
|
||||
const drawerContent = (
|
||||
<>
|
||||
<ChannelNavBar
|
||||
openMainSidebar={this.openMainSidebar}
|
||||
openSettingsSidebar={this.openSettingsSidebar}
|
||||
onPress={this.goToChannelInfo}
|
||||
/>
|
||||
if (!component) {
|
||||
component = (
|
||||
<KeyboardLayout>
|
||||
<View style={style.flex}>
|
||||
<ChannelPostList/>
|
||||
@@ -52,6 +44,17 @@ export default class ChannelAndroid extends ChannelBase {
|
||||
screenId={this.props.componentId}
|
||||
/>
|
||||
</KeyboardLayout>
|
||||
);
|
||||
}
|
||||
|
||||
const drawerContent = (
|
||||
<>
|
||||
<ChannelNavBar
|
||||
openMainSidebar={this.openMainSidebar}
|
||||
openSettingsSidebar={this.openSettingsSidebar}
|
||||
onPress={this.goToChannelInfo}
|
||||
/>
|
||||
{component}
|
||||
<NetworkIndicator/>
|
||||
{LocalConfig.EnableMobileClientUpgrade && <ClientUpgradeListener/>}
|
||||
</>
|
||||
|
||||
@@ -54,10 +54,27 @@ export default class ChannelIOS extends ChannelBase {
|
||||
|
||||
render() {
|
||||
const {currentChannelId, theme} = this.props;
|
||||
let component = this.renderLoadingOrFailedChannel();
|
||||
let renderDraftArea = false;
|
||||
|
||||
const channelLoadingOrFailed = this.renderLoadingOrFailedChannel();
|
||||
if (channelLoadingOrFailed) {
|
||||
return channelLoadingOrFailed;
|
||||
if (!component) {
|
||||
renderDraftArea = true;
|
||||
component = (
|
||||
<>
|
||||
<ChannelPostList
|
||||
updateNativeScrollView={this.updateNativeScrollView}
|
||||
/>
|
||||
<View nativeID={ACCESSORIES_CONTAINER_NATIVE_ID}>
|
||||
<Autocomplete
|
||||
maxHeight={AUTOCOMPLETE_MAX_HEIGHT}
|
||||
onChangeText={this.handleAutoComplete}
|
||||
cursorPositionEvent={CHANNEL_POST_TEXTBOX_CURSOR_CHANGE}
|
||||
valueEvent={CHANNEL_POST_TEXTBOX_VALUE_CHANGE}
|
||||
/>
|
||||
</View>
|
||||
{LocalConfig.EnableMobileClientUpgrade && <ClientUpgradeListener/>}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const style = getStyle(theme);
|
||||
@@ -71,19 +88,9 @@ export default class ChannelIOS extends ChannelBase {
|
||||
openSettingsSidebar={this.openSettingsSidebar}
|
||||
onPress={this.goToChannelInfo}
|
||||
/>
|
||||
<ChannelPostList
|
||||
updateNativeScrollView={this.updateNativeScrollView}
|
||||
/>
|
||||
<View nativeID={ACCESSORIES_CONTAINER_NATIVE_ID}>
|
||||
<Autocomplete
|
||||
maxHeight={AUTOCOMPLETE_MAX_HEIGHT}
|
||||
onChangeText={this.handleAutoComplete}
|
||||
cursorPositionEvent={CHANNEL_POST_TEXTBOX_CURSOR_CHANGE}
|
||||
valueEvent={CHANNEL_POST_TEXTBOX_VALUE_CHANGE}
|
||||
/>
|
||||
</View>
|
||||
{LocalConfig.EnableMobileClientUpgrade && <ClientUpgradeListener/>}
|
||||
{component}
|
||||
</SafeAreaView>
|
||||
{renderDraftArea &&
|
||||
<KeyboardTrackingView
|
||||
ref={this.keyboardTracker}
|
||||
scrollViewNativeID={currentChannelId}
|
||||
@@ -96,6 +103,7 @@ export default class ChannelIOS extends ChannelBase {
|
||||
screenId={this.props.componentId}
|
||||
/>
|
||||
</KeyboardTrackingView>
|
||||
}
|
||||
</>
|
||||
);
|
||||
|
||||
|
||||
@@ -7,14 +7,11 @@ import {intlShape} from 'react-intl';
|
||||
import {
|
||||
Keyboard,
|
||||
StyleSheet,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
|
||||
|
||||
import {showModal, showModalOverCurrentContext} from '@actions/navigation';
|
||||
import LocalConfig from '@assets/config';
|
||||
import SafeAreaView from '@components/safe_area_view';
|
||||
import EmptyToolbar from '@components/start/empty_toolbar';
|
||||
import {NavigationTypes} from '@constants';
|
||||
import EventEmitter from '@mm-redux/utils/event_emitter';
|
||||
import EphemeralStore from '@store/ephemeral_store';
|
||||
@@ -30,19 +27,19 @@ export let ClientUpgradeListener;
|
||||
export default class ChannelBase extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
getChannelStats: PropTypes.func.isRequired,
|
||||
loadChannelsForTeam: PropTypes.func.isRequired,
|
||||
selectDefaultTeam: PropTypes.func.isRequired,
|
||||
selectInitialChannel: PropTypes.func.isRequired,
|
||||
recordLoadTime: PropTypes.func.isRequired,
|
||||
getChannelStats: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
componentId: PropTypes.string.isRequired,
|
||||
currentChannelId: PropTypes.string,
|
||||
currentTeamId: PropTypes.string,
|
||||
isLandscape: PropTypes.bool,
|
||||
disableTermsModal: PropTypes.bool,
|
||||
teamName: PropTypes.string,
|
||||
theme: PropTypes.object.isRequired,
|
||||
showTermsOfService: PropTypes.bool,
|
||||
disableTermsModal: PropTypes.bool,
|
||||
skipMetrics: PropTypes.bool,
|
||||
};
|
||||
|
||||
@@ -213,9 +210,10 @@ export default class ChannelBase extends PureComponent {
|
||||
};
|
||||
|
||||
renderLoadingOrFailedChannel() {
|
||||
const {formatMessage} = this.context.intl;
|
||||
const {
|
||||
currentChannelId,
|
||||
isLandscape,
|
||||
teamName,
|
||||
theme,
|
||||
} = this.props;
|
||||
|
||||
@@ -223,37 +221,28 @@ export default class ChannelBase extends PureComponent {
|
||||
if (!currentChannelId) {
|
||||
if (channelsRequestFailed) {
|
||||
const FailedNetworkAction = require('app/components/failed_network_action').default;
|
||||
const title = formatMessage({id: 'mobile.failed_network_action.teams_title', defaultMessage: 'Something went wrong'});
|
||||
const message = formatMessage({
|
||||
id: 'mobile.failed_network_action.teams_channel_description',
|
||||
defaultMessage: 'Channels could not be loaded for {teamName}.',
|
||||
}, {teamName});
|
||||
|
||||
return (
|
||||
<SafeAreaView>
|
||||
<View style={style.flex}>
|
||||
<EmptyToolbar
|
||||
theme={theme}
|
||||
isLandscape={isLandscape}
|
||||
/>
|
||||
<FailedNetworkAction
|
||||
onRetry={this.retryLoadChannels}
|
||||
theme={theme}
|
||||
/>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
<FailedNetworkAction
|
||||
errorMessage={message}
|
||||
errorTitle={title}
|
||||
onRetry={this.retryLoadChannels}
|
||||
theme={theme}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const Loading = require('app/components/channel_loader').default;
|
||||
return (
|
||||
<SafeAreaView>
|
||||
<View style={style.flex}>
|
||||
<EmptyToolbar
|
||||
theme={theme}
|
||||
isLandscape={isLandscape}
|
||||
/>
|
||||
<Loading
|
||||
channelIsLoading={true}
|
||||
color={theme.centerChannelColor}
|
||||
/>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
<Loading
|
||||
channelIsLoading={true}
|
||||
color={theme.centerChannelColor}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,30 +4,24 @@
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {startPeriodicStatusUpdates, stopPeriodicStatusUpdates} from '@mm-redux/actions/users';
|
||||
import {loadChannelsForTeam, selectInitialChannel} from '@actions/views/channel';
|
||||
import {recordLoadTime} from '@actions/views/root';
|
||||
import {selectDefaultTeam} from '@actions/views/select_team';
|
||||
import {getCurrentChannelId} from '@mm-redux/selectors/entities/channels';
|
||||
import {getCurrentTeamId} from '@mm-redux/selectors/entities/teams';
|
||||
import {getCurrentTeam} from '@mm-redux/selectors/entities/teams';
|
||||
import {getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
import {shouldShowTermsOfService} from '@mm-redux/selectors/entities/users';
|
||||
import {getChannelStats} from '@mm-redux/actions/channels';
|
||||
|
||||
import {
|
||||
loadChannelsForTeam,
|
||||
selectInitialChannel,
|
||||
} from 'app/actions/views/channel';
|
||||
import {connection} from 'app/actions/device';
|
||||
import {recordLoadTime} from 'app/actions/views/root';
|
||||
import {logout} from 'app/actions/views/user';
|
||||
import {selectDefaultTeam} from 'app/actions/views/select_team';
|
||||
import {isLandscape} from 'app/selectors/device';
|
||||
|
||||
import Channel from './channel';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const currentTeam = getCurrentTeam(state);
|
||||
|
||||
return {
|
||||
currentTeamId: getCurrentTeamId(state),
|
||||
currentTeamId: currentTeam?.id,
|
||||
currentChannelId: getCurrentChannelId(state),
|
||||
isLandscape: isLandscape(state),
|
||||
teamName: currentTeam?.display_name,
|
||||
theme: getTheme(state),
|
||||
showTermsOfService: shouldShowTermsOfService(state),
|
||||
};
|
||||
@@ -37,14 +31,10 @@ function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
getChannelStats,
|
||||
connection,
|
||||
loadChannelsForTeam,
|
||||
logout,
|
||||
selectDefaultTeam,
|
||||
selectInitialChannel,
|
||||
recordLoadTime,
|
||||
startPeriodicStatusUpdates,
|
||||
stopPeriodicStatusUpdates,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -334,25 +334,27 @@ export default class ChannelAddMembers extends PureComponent {
|
||||
return (
|
||||
<KeyboardLayout>
|
||||
<StatusBar/>
|
||||
<View style={[style.searchBar, padding(isLandscape)]}>
|
||||
<SearchBar
|
||||
ref={this.setSearchBarRef}
|
||||
placeholder={formatMessage({id: 'search_bar.search', defaultMessage: 'Search'})}
|
||||
cancelTitle={formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'})}
|
||||
backgroundColor='transparent'
|
||||
inputHeight={33}
|
||||
inputStyle={searchBarInput}
|
||||
placeholderTextColor={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
tintColorSearch={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
tintColorDelete={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
titleCancelColor={theme.centerChannelColor}
|
||||
onChangeText={this.onSearch}
|
||||
onSearchButtonPress={this.onSearch}
|
||||
onCancelButtonPress={this.clearSearch}
|
||||
autoCapitalize='none'
|
||||
keyboardAppearance={getKeyboardAppearanceFromTheme(theme)}
|
||||
value={term}
|
||||
/>
|
||||
<View style={style.searchBar}>
|
||||
<View style={padding(isLandscape)}>
|
||||
<SearchBar
|
||||
ref={this.setSearchBarRef}
|
||||
placeholder={formatMessage({id: 'search_bar.search', defaultMessage: 'Search'})}
|
||||
cancelTitle={formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'})}
|
||||
backgroundColor='transparent'
|
||||
inputHeight={33}
|
||||
inputStyle={searchBarInput}
|
||||
placeholderTextColor={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
tintColorSearch={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
tintColorDelete={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
titleCancelColor={theme.centerChannelColor}
|
||||
onChangeText={this.onSearch}
|
||||
onSearchButtonPress={this.onSearch}
|
||||
onCancelButtonPress={this.clearSearch}
|
||||
autoCapitalize='none'
|
||||
keyboardAppearance={getKeyboardAppearanceFromTheme(theme)}
|
||||
value={term}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<CustomList
|
||||
data={data}
|
||||
|
||||
@@ -5,53 +5,54 @@ exports[`ChannelMembers should match snapshot 1`] = `
|
||||
<Connect(StatusBar) />
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"height": 38,
|
||||
"marginVertical": 5,
|
||||
"paddingLeft": 8,
|
||||
},
|
||||
null,
|
||||
]
|
||||
Object {
|
||||
"height": 38,
|
||||
"marginVertical": 5,
|
||||
"paddingLeft": 8,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Search
|
||||
autoCapitalize="none"
|
||||
backArrowSize={24}
|
||||
backgroundColor="transparent"
|
||||
blurOnSubmit={false}
|
||||
cancelTitle="Cancel"
|
||||
containerHeight={40}
|
||||
deleteIconSize={20}
|
||||
editable={true}
|
||||
inputHeight={33}
|
||||
inputStyle={
|
||||
Object {
|
||||
"backgroundColor": "rgba(61,60,64,0.2)",
|
||||
"color": "#3d3c40",
|
||||
"fontSize": 15,
|
||||
<View
|
||||
style={null}
|
||||
>
|
||||
<Search
|
||||
autoCapitalize="none"
|
||||
backArrowSize={24}
|
||||
backgroundColor="transparent"
|
||||
blurOnSubmit={false}
|
||||
cancelTitle="Cancel"
|
||||
containerHeight={40}
|
||||
deleteIconSize={20}
|
||||
editable={true}
|
||||
inputHeight={33}
|
||||
inputStyle={
|
||||
Object {
|
||||
"backgroundColor": "rgba(61,60,64,0.2)",
|
||||
"color": "#3d3c40",
|
||||
"fontSize": 15,
|
||||
}
|
||||
}
|
||||
}
|
||||
keyboardAppearance="light"
|
||||
keyboardShouldPersist={false}
|
||||
keyboardType="default"
|
||||
onBlur={[Function]}
|
||||
onCancelButtonPress={[Function]}
|
||||
onChangeText={[Function]}
|
||||
onSearchButtonPress={[Function]}
|
||||
onSelectionChange={[Function]}
|
||||
placeholder="Search"
|
||||
placeholderTextColor="rgba(61,60,64,0.5)"
|
||||
returnKeyType="search"
|
||||
searchBarRightMargin={0}
|
||||
searchIconSize={24}
|
||||
showArrow={false}
|
||||
showCancel={true}
|
||||
tintColorDelete="rgba(61,60,64,0.5)"
|
||||
tintColorSearch="rgba(61,60,64,0.5)"
|
||||
titleCancelColor="#3d3c40"
|
||||
value=""
|
||||
/>
|
||||
keyboardAppearance="light"
|
||||
keyboardShouldPersist={false}
|
||||
keyboardType="default"
|
||||
onBlur={[Function]}
|
||||
onCancelButtonPress={[Function]}
|
||||
onChangeText={[Function]}
|
||||
onSearchButtonPress={[Function]}
|
||||
onSelectionChange={[Function]}
|
||||
placeholder="Search"
|
||||
placeholderTextColor="rgba(61,60,64,0.5)"
|
||||
returnKeyType="search"
|
||||
searchBarRightMargin={0}
|
||||
searchIconSize={24}
|
||||
showArrow={false}
|
||||
showCancel={true}
|
||||
tintColorDelete="rgba(61,60,64,0.5)"
|
||||
tintColorSearch="rgba(61,60,64,0.5)"
|
||||
titleCancelColor="#3d3c40"
|
||||
value=""
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<CustomList
|
||||
canRefresh={true}
|
||||
|
||||
@@ -363,25 +363,27 @@ export default class ChannelMembers extends PureComponent {
|
||||
return (
|
||||
<KeyboardLayout>
|
||||
<StatusBar/>
|
||||
<View style={[style.searchBar, padding(isLandscape)]}>
|
||||
<SearchBar
|
||||
ref={this.setSearchBarRef}
|
||||
placeholder={formatMessage({id: 'search_bar.search', defaultMessage: 'Search'})}
|
||||
cancelTitle={formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'})}
|
||||
backgroundColor='transparent'
|
||||
inputHeight={33}
|
||||
inputStyle={searchBarInput}
|
||||
placeholderTextColor={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
tintColorSearch={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
tintColorDelete={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
titleCancelColor={theme.centerChannelColor}
|
||||
onChangeText={this.onSearch}
|
||||
onSearchButtonPress={this.onSearch}
|
||||
onCancelButtonPress={this.clearSearch}
|
||||
autoCapitalize='none'
|
||||
keyboardAppearance={getKeyboardAppearanceFromTheme(theme)}
|
||||
value={term}
|
||||
/>
|
||||
<View style={style.searchBar}>
|
||||
<View style={padding(isLandscape)}>
|
||||
<SearchBar
|
||||
ref={this.setSearchBarRef}
|
||||
placeholder={formatMessage({id: 'search_bar.search', defaultMessage: 'Search'})}
|
||||
cancelTitle={formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'})}
|
||||
backgroundColor='transparent'
|
||||
inputHeight={33}
|
||||
inputStyle={searchBarInput}
|
||||
placeholderTextColor={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
tintColorSearch={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
tintColorDelete={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
titleCancelColor={theme.centerChannelColor}
|
||||
onChangeText={this.onSearch}
|
||||
onSearchButtonPress={this.onSearch}
|
||||
onCancelButtonPress={this.clearSearch}
|
||||
autoCapitalize='none'
|
||||
keyboardAppearance={getKeyboardAppearanceFromTheme(theme)}
|
||||
value={term}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<CustomList
|
||||
data={data}
|
||||
|
||||
@@ -10,92 +10,12 @@ exports[`edit_profile should match snapshot 1`] = `
|
||||
}
|
||||
}
|
||||
>
|
||||
<ProfilePictureButton
|
||||
blurTextBox={[Function]}
|
||||
browseFileTypes="public.image"
|
||||
canBrowseVideoLibrary={false}
|
||||
canTakeVideo={false}
|
||||
currentUser={
|
||||
Object {
|
||||
"email": "dwight@schrutefarms.com",
|
||||
"first_name": "Dwight",
|
||||
"last_name": "Schrute",
|
||||
"nickname": "Dragon",
|
||||
"position": "position",
|
||||
"username": "ieatbeets",
|
||||
}
|
||||
}
|
||||
maxFileSize={20971520}
|
||||
onShowFileSizeWarning={[Function]}
|
||||
onShowUnsupportedMimeTypeWarning={[Function]}
|
||||
removeProfileImage={[Function]}
|
||||
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",
|
||||
}
|
||||
}
|
||||
uploadFiles={[Function]}
|
||||
validMimeTypes={
|
||||
Array [
|
||||
"image/jpeg",
|
||||
"image/jpeg",
|
||||
"image/jpg",
|
||||
"image/jp_",
|
||||
"application/jpg",
|
||||
"application/x-jpg",
|
||||
"image/pjpeg",
|
||||
"image/pipeg",
|
||||
"image/vnd.swiftview-jpeg",
|
||||
"image/x-xbitmap",
|
||||
"image/png",
|
||||
"application/png",
|
||||
"application/x-png",
|
||||
"image/bmp",
|
||||
"image/x-bmp",
|
||||
"image/x-bitmap",
|
||||
"image/x-xbitmap",
|
||||
"image/x-win-bitmap",
|
||||
"image/x-windows-bmp",
|
||||
"image/ms-bmp",
|
||||
"image/x-ms-bmp",
|
||||
"application/bmp",
|
||||
"application/x-bmp",
|
||||
"application/x-win-bitmap",
|
||||
]
|
||||
}
|
||||
wrapper={true}
|
||||
>
|
||||
<Connect(ProfilePicture)
|
||||
edit={true}
|
||||
imageUri={null}
|
||||
size={150}
|
||||
statusBorderWidth={6}
|
||||
statusSize={40}
|
||||
/>
|
||||
</ProfilePictureButton>
|
||||
<Connect(ProfilePicture)
|
||||
edit={false}
|
||||
imageUri={null}
|
||||
size={150}
|
||||
statusBorderWidth={6}
|
||||
statusSize={40}
|
||||
/>
|
||||
</View>
|
||||
`;
|
||||
|
||||
@@ -95,6 +95,7 @@ export default class EditProfile extends PureComponent {
|
||||
lastNameDisabled: PropTypes.bool.isRequired,
|
||||
nicknameDisabled: PropTypes.bool.isRequired,
|
||||
positionDisabled: PropTypes.bool.isRequired,
|
||||
profilePictureDisabled: PropTypes.bool.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
commandType: PropTypes.string.isRequired,
|
||||
isLandscape: PropTypes.bool.isRequired,
|
||||
@@ -504,6 +505,7 @@ export default class EditProfile extends PureComponent {
|
||||
renderProfilePicture = () => {
|
||||
const {
|
||||
currentUser,
|
||||
profilePictureDisabled,
|
||||
theme,
|
||||
} = this.props;
|
||||
|
||||
@@ -514,6 +516,25 @@ export default class EditProfile extends PureComponent {
|
||||
|
||||
const style = getStyleSheet(theme);
|
||||
const uri = profileImage ? profileImage.uri : null;
|
||||
const profilePicture = (
|
||||
<ProfilePicture
|
||||
userId={currentUser.id}
|
||||
size={150}
|
||||
statusBorderWidth={6}
|
||||
statusSize={40}
|
||||
edit={!profilePictureDisabled}
|
||||
imageUri={uri}
|
||||
profileImageRemove={profileImageRemove}
|
||||
/>
|
||||
);
|
||||
|
||||
if (profilePictureDisabled) {
|
||||
return (
|
||||
<View style={style.top}>
|
||||
{profilePicture}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={style.top}>
|
||||
@@ -532,15 +553,7 @@ export default class EditProfile extends PureComponent {
|
||||
onShowUnsupportedMimeTypeWarning={this.onShowUnsupportedMimeTypeWarning}
|
||||
validMimeTypes={VALID_MIME_TYPES}
|
||||
>
|
||||
<ProfilePicture
|
||||
userId={currentUser.id}
|
||||
size={150}
|
||||
statusBorderWidth={6}
|
||||
statusSize={40}
|
||||
edit={true}
|
||||
imageUri={uri}
|
||||
profileImageRemove={profileImageRemove}
|
||||
/>
|
||||
{profilePicture}
|
||||
</ProfilePictureButton>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -32,6 +32,7 @@ describe('edit_profile', () => {
|
||||
lastNameDisabled: true,
|
||||
nicknameDisabled: true,
|
||||
positionDisabled: true,
|
||||
profilePictureDisabled: true,
|
||||
theme: Preferences.THEMES.default,
|
||||
currentUser: {
|
||||
first_name: 'Dwight',
|
||||
@@ -58,6 +59,7 @@ describe('edit_profile', () => {
|
||||
const wrapper = shallow(
|
||||
<EditProfile
|
||||
{...baseProps}
|
||||
profilePictureDisabled={false}
|
||||
/>,
|
||||
{context: {intl: {formatMessage: jest.fn()}}},
|
||||
);
|
||||
|
||||
@@ -26,12 +26,12 @@ function mapStateToProps(state, ownProps) {
|
||||
const nicknameDisabled = (service === 'ldap' && config.LdapNicknameAttributeSet === 'true') ||
|
||||
(service === 'saml' && config.SamlNicknameAttributeSet === 'true');
|
||||
|
||||
let positionDisabled = false;
|
||||
if (isMinimumServerVersion(serverVersion, 5, 12)) {
|
||||
positionDisabled = (service === 'ldap' && config.LdapPositionAttributeSet === 'true') ||
|
||||
(service === 'saml' && config.SamlPositionAttributeSet === 'true');
|
||||
} else {
|
||||
positionDisabled = (service === 'ldap' || service === 'saml') && config.PositionAttribute === 'true';
|
||||
const positionDisabled = (service === 'ldap' && config.LdapPositionAttributeSet === 'true') ||
|
||||
(service === 'saml' && config.SamlPositionAttributeSet === 'true');
|
||||
|
||||
let profilePictureDisabled = false;
|
||||
if (isMinimumServerVersion(serverVersion, 5, 24)) {
|
||||
profilePictureDisabled = (service === 'ldap' || service === 'saml') && config.LdapPictureAttributeSet === 'true';
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -39,6 +39,7 @@ function mapStateToProps(state, ownProps) {
|
||||
lastNameDisabled,
|
||||
nicknameDisabled,
|
||||
positionDisabled,
|
||||
profilePictureDisabled,
|
||||
theme: getTheme(state),
|
||||
isLandscape: isLandscape(state),
|
||||
};
|
||||
|
||||
@@ -10,11 +10,10 @@ exports[`ErrorTeamsList should match snapshot 1`] = `
|
||||
>
|
||||
<Connect(StatusBar) />
|
||||
<FailedNetworkAction
|
||||
actionDefaultMessage="try again"
|
||||
actionId="mobile.failed_network_action.retry"
|
||||
errorDefaultMessage="Messages will load when you have an internet connection or {tryAgainAction}."
|
||||
errorId="mobile.failed_network_action.shortDescription"
|
||||
errorMessage="Teams could not be loaded."
|
||||
errorTitle="Something went wrong"
|
||||
onRetry={[Function]}
|
||||
showAction={true}
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {intlShape} from 'react-intl';
|
||||
import {
|
||||
InteractionManager,
|
||||
StyleSheet,
|
||||
@@ -26,6 +27,10 @@ export default class ErrorTeamsList extends PureComponent {
|
||||
theme: PropTypes.object,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
@@ -67,16 +72,25 @@ export default class ErrorTeamsList extends PureComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
const {formatMessage} = this.context.intl;
|
||||
const {theme} = this.props;
|
||||
|
||||
if (this.state.loading) {
|
||||
return <Loading color={theme.centerChannelColor}/>;
|
||||
}
|
||||
|
||||
const title = formatMessage({id: 'mobile.failed_network_action.teams_title', defaultMessage: 'Something went wrong'});
|
||||
const message = formatMessage({
|
||||
id: 'mobile.failed_network_action.teams_description',
|
||||
defaultMessage: 'Teams could not be loaded.',
|
||||
});
|
||||
|
||||
return (
|
||||
<View style={style.container}>
|
||||
<StatusBar/>
|
||||
<FailedNetworkAction
|
||||
errorMessage={message}
|
||||
errorTitle={title}
|
||||
onRetry={this.getUserInfo}
|
||||
theme={theme}
|
||||
/>
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {shallow} from 'enzyme';
|
||||
|
||||
import FailedNetworkAction from '@components/failed_network_action';
|
||||
import Preferences from '@mm-redux/constants/preferences';
|
||||
import {shallowWithIntl} from 'test/intl-test-helper.js';
|
||||
|
||||
import FailedNetworkAction from 'app/components/failed_network_action';
|
||||
import ErrorTeamsList from './error_teams_list';
|
||||
|
||||
describe('ErrorTeamsList', () => {
|
||||
@@ -28,7 +28,7 @@ describe('ErrorTeamsList', () => {
|
||||
};
|
||||
|
||||
test('should match snapshot', () => {
|
||||
const wrapper = shallow(
|
||||
const wrapper = shallowWithIntl(
|
||||
<ErrorTeamsList {...baseProps}/>,
|
||||
);
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
@@ -51,7 +51,7 @@ describe('ErrorTeamsList', () => {
|
||||
actions,
|
||||
};
|
||||
|
||||
const wrapper = shallow(
|
||||
const wrapper = shallowWithIntl(
|
||||
<ErrorTeamsList {...newProps}/>,
|
||||
);
|
||||
|
||||
|
||||
@@ -40,11 +40,8 @@ exports[`FlaggedPosts should match snapshot when getFlaggedPosts failed 1`] = `
|
||||
>
|
||||
<Connect(StatusBar) />
|
||||
<FailedNetworkAction
|
||||
actionDefaultMessage="try again"
|
||||
actionId="mobile.failed_network_action.retry"
|
||||
errorDefaultMessage="Messages will load when you have an internet connection or {tryAgainAction}."
|
||||
errorId="mobile.failed_network_action.shortDescription"
|
||||
onRetry={[Function]}
|
||||
showAction={true}
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
|
||||
@@ -16,10 +16,10 @@ export function registerScreens(store, Provider) {
|
||||
Root = require('app/components/root').default;
|
||||
}
|
||||
|
||||
const wrapper = (Comp) => (props) => ( // eslint-disable-line react/display-name
|
||||
const wrapper = (Comp, excludeEvents = true) => (props) => ( // eslint-disable-line react/display-name
|
||||
<Provider store={store}>
|
||||
<ThemeProvider>
|
||||
<Root>
|
||||
<Root excludeEvents={excludeEvents}>
|
||||
<Comp {...props}/>
|
||||
</Root>
|
||||
</ThemeProvider>
|
||||
@@ -29,7 +29,7 @@ export function registerScreens(store, Provider) {
|
||||
Navigation.registerComponent('About', () => wrapper(require('app/screens/about').default), () => require('app/screens/about').default);
|
||||
Navigation.registerComponent('AddReaction', () => wrapper(require('app/screens/add_reaction').default), () => require('app/screens/add_reaction').default);
|
||||
Navigation.registerComponent('AdvancedSettings', () => wrapper(require('app/screens/settings/advanced_settings').default), () => require('app/screens/settings/advanced_settings').default);
|
||||
Navigation.registerComponent('Channel', () => wrapper(require('app/screens/channel').default), () => require('app/screens/channel').default);
|
||||
Navigation.registerComponent('Channel', () => wrapper(require('app/screens/channel').default, false), () => require('app/screens/channel').default);
|
||||
Navigation.registerComponent('ChannelAddMembers', () => wrapper(require('app/screens/channel_add_members').default), () => require('app/screens/channel_add_members').default);
|
||||
Navigation.registerComponent('ChannelInfo', () => wrapper(require('app/screens/channel_info').default), () => require('app/screens/channel_info').default);
|
||||
Navigation.registerComponent('ChannelMembers', () => wrapper(require('app/screens/channel_members').default), () => require('app/screens/channel_members').default);
|
||||
@@ -52,7 +52,11 @@ export function registerScreens(store, Provider) {
|
||||
Navigation.registerComponent('MFA', () => wrapper(require('app/screens/mfa').default), () => require('app/screens/mfa').default);
|
||||
Navigation.registerComponent('MoreChannels', () => wrapper(require('app/screens/more_channels').default), () => require('app/screens/more_channels').default);
|
||||
Navigation.registerComponent('MoreDirectMessages', () => wrapper(require('app/screens/more_dms').default), () => require('app/screens/more_dms').default);
|
||||
Navigation.registerComponent('Notification', () => gestureHandlerRootHOC(wrapper(require('app/screens/notification').default)), () => require('app/screens/notification').default);
|
||||
if (Platform.OS === 'android') {
|
||||
Navigation.registerComponent('Notification', () => gestureHandlerRootHOC(wrapper(require('app/screens/notification').default), {flex: undefined, height: 100}), () => require('app/screens/notification').default);
|
||||
} else {
|
||||
Navigation.registerComponent('Notification', () => wrapper(require('app/screens/notification').default), () => require('app/screens/notification').default);
|
||||
}
|
||||
Navigation.registerComponent('NotificationSettings', () => wrapper(require('app/screens/settings/notification_settings').default), () => require('app/screens/settings/notification_settings').default);
|
||||
Navigation.registerComponent('NotificationSettingsAutoResponder', () => wrapper(require('app/screens/settings/notification_settings_auto_responder').default), () => require('app/screens/settings/notification_settings_auto_responder').default);
|
||||
Navigation.registerComponent('NotificationSettingsEmail', () => wrapper(require('app/screens/settings/notification_settings_email').default), () => require('app/screens/settings/notification_settings_email').default);
|
||||
@@ -67,7 +71,7 @@ export function registerScreens(store, Provider) {
|
||||
Navigation.registerComponent('RecentMentions', () => wrapper(require('app/screens/recent_mentions').default), () => require('app/screens/recent_mentions').default);
|
||||
Navigation.registerComponent('Search', () => wrapper(require('app/screens/search').default), () => require('app/screens/search').default);
|
||||
Navigation.registerComponent('SelectorScreen', () => wrapper(require('app/screens/selector_screen').default), () => require('app/screens/selector_screen').default);
|
||||
Navigation.registerComponent('SelectServer', () => wrapper(require('app/screens/select_server').default), () => require('app/screens/select_server').default);
|
||||
Navigation.registerComponent('SelectServer', () => wrapper(require('app/screens/select_server').default, false), () => require('app/screens/select_server').default);
|
||||
Navigation.registerComponent('SelectTeam', () => wrapper(require('app/screens/select_team').default), () => require('app/screens/select_team').default);
|
||||
Navigation.registerComponent('SelectTimezone', () => wrapper(require('app/screens/settings/timezone/select_timezone').default), () => require('app/screens/settings/timezone/select_timezone').default);
|
||||
Navigation.registerComponent('Settings', () => wrapper(require('app/screens/settings/general').default), () => require('app/screens/settings/general').default);
|
||||
|
||||
@@ -25,7 +25,6 @@ import FormattedText from '@components/formatted_text';
|
||||
import {paddingHorizontal as padding} from '@components/safe_area_view/iphone_x_spacing';
|
||||
import StatusBar from '@components/status_bar';
|
||||
import {t} from '@utils/i18n';
|
||||
import {setMfaPreflightDone, getMfaPreflightDone} from '@utils/security';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {changeOpacity} from '@utils/theme';
|
||||
import tracker from '@utils/time_tracker';
|
||||
@@ -68,7 +67,6 @@ export default class Login extends PureComponent {
|
||||
componentDidMount() {
|
||||
Dimensions.addEventListener('change', this.orientationDidChange);
|
||||
|
||||
setMfaPreflightDone(false);
|
||||
this.setEmmUsernameIfAvailable();
|
||||
}
|
||||
|
||||
@@ -92,7 +90,7 @@ export default class Login extends PureComponent {
|
||||
const loginId = this.loginId;
|
||||
const password = this.password;
|
||||
|
||||
goToScreen(screen, title, {onMfaComplete: this.checkLoginResponse, goToChannel: this.goToChannel, loginId, password});
|
||||
goToScreen(screen, title, {goToChannel: this.goToChannel, loginId, password});
|
||||
};
|
||||
|
||||
blur = () => {
|
||||
@@ -182,9 +180,6 @@ export default class Login extends PureComponent {
|
||||
if (!errorId) {
|
||||
return error.message;
|
||||
}
|
||||
if (mfaExpectedErrors.includes(errorId) && !getMfaPreflightDone()) {
|
||||
return null;
|
||||
}
|
||||
if (
|
||||
errorId === 'store.sql_user.get_for_login.app_error' ||
|
||||
errorId === 'ent.ldap.do_login.user_not_registered.app_error'
|
||||
|
||||
@@ -94,7 +94,6 @@ describe('Login', () => {
|
||||
'Multi-factor Authentication',
|
||||
{
|
||||
goToChannel: wrapper.instance().goToChannel,
|
||||
onMfaComplete: wrapper.instance().checkLoginResponse,
|
||||
loginId,
|
||||
password,
|
||||
},
|
||||
|
||||
@@ -21,6 +21,7 @@ import FormattedText from '@components/formatted_text';
|
||||
import StatusBar from '@components/status_bar';
|
||||
import {paddingHorizontal as padding} from '@components/safe_area_view/iphone_x_spacing';
|
||||
import {ViewTypes} from '@constants';
|
||||
import globalEventHandler from '@init/global_event_handler';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
|
||||
import {GlobalStyles} from 'app/styles';
|
||||
@@ -44,19 +45,21 @@ export default class LoginOptions extends PureComponent {
|
||||
Dimensions.removeEventListener('change', this.orientationDidChange);
|
||||
}
|
||||
|
||||
goToLogin = preventDoubleTap(() => {
|
||||
goToLogin = preventDoubleTap(async () => {
|
||||
const {intl} = this.context;
|
||||
const screen = 'Login';
|
||||
const title = intl.formatMessage({id: 'mobile.routes.login', defaultMessage: 'Login'});
|
||||
|
||||
globalEventHandler.clearCookiesAndWebData();
|
||||
goToScreen(screen, title);
|
||||
});
|
||||
|
||||
goToSSO = (ssoType) => {
|
||||
goToSSO = async (ssoType) => {
|
||||
const {intl} = this.context;
|
||||
const screen = 'SSO';
|
||||
const title = intl.formatMessage({id: 'mobile.routes.sso', defaultMessage: 'Single Sign-On'});
|
||||
|
||||
globalEventHandler.clearCookiesAndWebData();
|
||||
goToScreen(screen, title, {ssoType});
|
||||
};
|
||||
|
||||
|
||||
@@ -15,14 +15,12 @@ import {
|
||||
} from 'react-native';
|
||||
import Button from 'react-native-button';
|
||||
|
||||
import {popTopScreen} from '@actions/navigation';
|
||||
import ErrorText from '@components/error_text';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import StatusBar from '@components/status_bar';
|
||||
import TextInputWithLocalizedPlaceholder from '@components/text_input_with_localized_placeholder';
|
||||
import {t} from '@utils/i18n';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {setMfaPreflightDone} from '@utils/security';
|
||||
|
||||
import {GlobalStyles} from 'app/styles';
|
||||
|
||||
@@ -34,7 +32,6 @@ export default class Mfa extends PureComponent {
|
||||
goToChannel: PropTypes.func.isRequired,
|
||||
loginId: PropTypes.string.isRequired,
|
||||
password: PropTypes.string.isRequired,
|
||||
onMfaComplete: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
@@ -79,7 +76,7 @@ export default class Mfa extends PureComponent {
|
||||
};
|
||||
|
||||
submit = preventDoubleTap(() => {
|
||||
const {actions, goToChannel, loginId, password, onMfaComplete} = this.props;
|
||||
const {actions, goToChannel, loginId, password} = this.props;
|
||||
const {token} = this.state;
|
||||
|
||||
Keyboard.dismiss();
|
||||
@@ -94,16 +91,16 @@ export default class Mfa extends PureComponent {
|
||||
});
|
||||
return;
|
||||
}
|
||||
setMfaPreflightDone(true);
|
||||
|
||||
this.setState({isLoading: true});
|
||||
actions.login(loginId, password, token).then((result) => {
|
||||
this.setState({isLoading: false});
|
||||
if (onMfaComplete(result)) {
|
||||
goToChannel();
|
||||
if (result.error) {
|
||||
this.setState({error: result.error});
|
||||
return;
|
||||
}
|
||||
|
||||
popTopScreen();
|
||||
goToChannel();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -6,51 +6,52 @@ exports[`MoreChannels should match snapshot 1`] = `
|
||||
<React.Fragment>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"height": 38,
|
||||
"marginVertical": 5,
|
||||
"paddingLeft": 8,
|
||||
},
|
||||
null,
|
||||
]
|
||||
Object {
|
||||
"height": 38,
|
||||
"marginVertical": 5,
|
||||
"paddingLeft": 8,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Search
|
||||
autoCapitalize="none"
|
||||
backArrowSize={24}
|
||||
backgroundColor="transparent"
|
||||
blurOnSubmit={false}
|
||||
containerHeight={40}
|
||||
deleteIconSize={20}
|
||||
editable={true}
|
||||
inputHeight={33}
|
||||
inputStyle={
|
||||
Object {
|
||||
"backgroundColor": "rgba(61,60,64,0.2)",
|
||||
"color": "#3d3c40",
|
||||
"fontSize": 15,
|
||||
<View
|
||||
style={null}
|
||||
>
|
||||
<Search
|
||||
autoCapitalize="none"
|
||||
backArrowSize={24}
|
||||
backgroundColor="transparent"
|
||||
blurOnSubmit={false}
|
||||
containerHeight={40}
|
||||
deleteIconSize={20}
|
||||
editable={true}
|
||||
inputHeight={33}
|
||||
inputStyle={
|
||||
Object {
|
||||
"backgroundColor": "rgba(61,60,64,0.2)",
|
||||
"color": "#3d3c40",
|
||||
"fontSize": 15,
|
||||
}
|
||||
}
|
||||
}
|
||||
keyboardAppearance="light"
|
||||
keyboardShouldPersist={false}
|
||||
keyboardType="default"
|
||||
onBlur={[Function]}
|
||||
onCancelButtonPress={[Function]}
|
||||
onChangeText={[Function]}
|
||||
onSearchButtonPress={[Function]}
|
||||
onSelectionChange={[Function]}
|
||||
placeholderTextColor="rgba(61,60,64,0.5)"
|
||||
returnKeyType="search"
|
||||
searchBarRightMargin={0}
|
||||
searchIconSize={24}
|
||||
showArrow={false}
|
||||
showCancel={true}
|
||||
tintColorDelete="rgba(61,60,64,0.5)"
|
||||
tintColorSearch="rgba(61,60,64,0.5)"
|
||||
titleCancelColor="#3d3c40"
|
||||
value=""
|
||||
/>
|
||||
keyboardAppearance="light"
|
||||
keyboardShouldPersist={false}
|
||||
keyboardType="default"
|
||||
onBlur={[Function]}
|
||||
onCancelButtonPress={[Function]}
|
||||
onChangeText={[Function]}
|
||||
onSearchButtonPress={[Function]}
|
||||
onSelectionChange={[Function]}
|
||||
placeholderTextColor="rgba(61,60,64,0.5)"
|
||||
returnKeyType="search"
|
||||
searchBarRightMargin={0}
|
||||
searchIconSize={24}
|
||||
showArrow={false}
|
||||
showCancel={true}
|
||||
tintColorDelete="rgba(61,60,64,0.5)"
|
||||
tintColorSearch="rgba(61,60,64,0.5)"
|
||||
titleCancelColor="#3d3c40"
|
||||
value=""
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
|
||||
@@ -452,25 +452,27 @@ export default class MoreChannels extends PureComponent {
|
||||
|
||||
content = (
|
||||
<React.Fragment>
|
||||
<View style={[style.searchBar, padding(isLandscape)]}>
|
||||
<SearchBar
|
||||
ref={this.setSearchBarRef}
|
||||
placeholder={formatMessage({id: 'search_bar.search', defaultMessage: 'Search'})}
|
||||
cancelTitle={formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'})}
|
||||
backgroundColor='transparent'
|
||||
inputHeight={33}
|
||||
inputStyle={searchBarInput}
|
||||
placeholderTextColor={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
tintColorSearch={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
tintColorDelete={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
titleCancelColor={theme.centerChannelColor}
|
||||
onChangeText={this.searchChannels}
|
||||
onSearchButtonPress={this.searchChannels}
|
||||
onCancelButtonPress={this.cancelSearch}
|
||||
autoCapitalize='none'
|
||||
keyboardAppearance={getKeyboardAppearanceFromTheme(theme)}
|
||||
value={term}
|
||||
/>
|
||||
<View style={style.searchBar}>
|
||||
<View style={padding(isLandscape)}>
|
||||
<SearchBar
|
||||
ref={this.setSearchBarRef}
|
||||
placeholder={formatMessage({id: 'search_bar.search', defaultMessage: 'Search'})}
|
||||
cancelTitle={formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'})}
|
||||
backgroundColor='transparent'
|
||||
inputHeight={33}
|
||||
inputStyle={searchBarInput}
|
||||
placeholderTextColor={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
tintColorSearch={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
tintColorDelete={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
titleCancelColor={theme.centerChannelColor}
|
||||
onChangeText={this.searchChannels}
|
||||
onSearchButtonPress={this.searchChannels}
|
||||
onCancelButtonPress={this.cancelSearch}
|
||||
autoCapitalize='none'
|
||||
keyboardAppearance={getKeyboardAppearanceFromTheme(theme)}
|
||||
value={term}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
{channelDropdown}
|
||||
<CustomList
|
||||
|
||||
@@ -52,7 +52,7 @@ export default class MoreDirectMessages extends PureComponent {
|
||||
currentDisplayName: PropTypes.string,
|
||||
currentTeamId: PropTypes.string.isRequired,
|
||||
currentUserId: PropTypes.string.isRequired,
|
||||
isGuest: PropTypes.object.isRequired,
|
||||
isGuest: PropTypes.bool,
|
||||
restrictDirectMessage: PropTypes.bool.isRequired,
|
||||
teammateNameDisplay: PropTypes.string,
|
||||
theme: PropTypes.object.isRequired,
|
||||
@@ -472,25 +472,27 @@ export default class MoreDirectMessages extends PureComponent {
|
||||
return (
|
||||
<KeyboardLayout>
|
||||
<StatusBar/>
|
||||
<View style={[style.searchBar, padding(isLandscape)]}>
|
||||
<SearchBar
|
||||
ref={this.setSearchBarRef}
|
||||
placeholder={formatMessage({id: 'search_bar.search', defaultMessage: 'Search'})}
|
||||
cancelTitle={formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'})}
|
||||
backgroundColor='transparent'
|
||||
inputHeight={33}
|
||||
inputStyle={searchBarInput}
|
||||
placeholderTextColor={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
tintColorSearch={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
tintColorDelete={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
titleCancelColor={theme.centerChannelColor}
|
||||
onChangeText={this.onSearch}
|
||||
onSearchButtonPress={this.onSearch}
|
||||
onCancelButtonPress={this.clearSearch}
|
||||
autoCapitalize='none'
|
||||
keyboardAppearance={getKeyboardAppearanceFromTheme(theme)}
|
||||
value={term}
|
||||
/>
|
||||
<View style={style.searchBar}>
|
||||
<View style={padding(isLandscape)}>
|
||||
<SearchBar
|
||||
ref={this.setSearchBarRef}
|
||||
placeholder={formatMessage({id: 'search_bar.search', defaultMessage: 'Search'})}
|
||||
cancelTitle={formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'})}
|
||||
backgroundColor='transparent'
|
||||
inputHeight={33}
|
||||
inputStyle={searchBarInput}
|
||||
placeholderTextColor={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
tintColorSearch={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
tintColorDelete={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
titleCancelColor={theme.centerChannelColor}
|
||||
onChangeText={this.onSearch}
|
||||
onSearchButtonPress={this.onSearch}
|
||||
onCancelButtonPress={this.clearSearch}
|
||||
autoCapitalize='none'
|
||||
keyboardAppearance={getKeyboardAppearanceFromTheme(theme)}
|
||||
value={term}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<SelectedUsers
|
||||
selectedIds={this.state.selectedIds}
|
||||
@@ -501,6 +503,7 @@ export default class MoreDirectMessages extends PureComponent {
|
||||
<CustomList
|
||||
data={data}
|
||||
extraData={selectedIds}
|
||||
isLandscape={isLandscape}
|
||||
key='custom_list'
|
||||
listType={listType}
|
||||
loading={loading}
|
||||
|
||||
@@ -110,7 +110,7 @@ export default class OptionsModal extends PureComponent {
|
||||
|
||||
const style = StyleSheet.create({
|
||||
wrapper: {
|
||||
backgroundColor: Platform.select({ios: 'rgba(0, 0, 0, 0.5)'}),
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||||
flex: 1,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -40,11 +40,8 @@ exports[`PinnedPosts should match snapshot when getPinnedPosts failed 1`] = `
|
||||
>
|
||||
<Connect(StatusBar) />
|
||||
<FailedNetworkAction
|
||||
actionDefaultMessage="try again"
|
||||
actionId="mobile.failed_network_action.retry"
|
||||
errorDefaultMessage="Messages will load when you have an internet connection or {tryAgainAction}."
|
||||
errorId="mobile.failed_network_action.shortDescription"
|
||||
onRetry={[Function]}
|
||||
showAction={true}
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
|
||||
@@ -19,7 +19,7 @@ function makeMapStateToProps() {
|
||||
const getProfilesByIdsAndUsernames = makeGetProfilesByIdsAndUsernames();
|
||||
|
||||
return function mapStateToProps(state, ownProps) {
|
||||
const reactions = getReactionsForPostSelector(state, ownProps.postId);
|
||||
const reactions = getReactionsForPostSelector(state, ownProps.postId) || undefined;
|
||||
const allUserIds = getUniqueUserIds(reactions);
|
||||
|
||||
return {
|
||||
|
||||
@@ -40,11 +40,8 @@ exports[`RecentMentions should match snapshot when getRecentMentions failed 1`]
|
||||
>
|
||||
<Connect(StatusBar) />
|
||||
<FailedNetworkAction
|
||||
actionDefaultMessage="try again"
|
||||
actionId="mobile.failed_network_action.retry"
|
||||
errorDefaultMessage="Messages will load when you have an internet connection or {tryAgainAction}."
|
||||
errorId="mobile.failed_network_action.shortDescription"
|
||||
onRetry={[Function]}
|
||||
showAction={true}
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
|
||||
@@ -713,7 +713,7 @@ export default class Search extends PureComponent {
|
||||
paddingRes.paddingLeft = null;
|
||||
|
||||
if (isLandscape) {
|
||||
paddingRes.paddingTop = 10;
|
||||
paddingRes.paddingTop = 5;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -147,19 +147,19 @@ export default class SelectServer extends PureComponent {
|
||||
}
|
||||
};
|
||||
|
||||
getUrl = () => {
|
||||
const urlParse = require('url-parse');
|
||||
let preUrl = urlParse(this.state.url, true);
|
||||
getUrl = async (serverUrl, useHttp = false) => {
|
||||
let url = this.sanitizeUrl(serverUrl, useHttp);
|
||||
|
||||
if (!preUrl.host || preUrl.protocol === 'file:') {
|
||||
preUrl = urlParse('https://' + stripTrailingSlashes(this.state.url), true);
|
||||
try {
|
||||
const resp = await fetch(url, {method: 'HEAD'});
|
||||
if (resp?.rnfbRespInfo?.redirects?.length) {
|
||||
url = resp.rnfbRespInfo.redirects[resp.rnfbRespInfo.redirects.length - 1];
|
||||
}
|
||||
} catch {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
if (preUrl.protocol === 'http:') {
|
||||
preUrl.protocol = 'https:';
|
||||
}
|
||||
|
||||
return stripTrailingSlashes(preUrl.protocol + '//' + preUrl.host + preUrl.pathname);
|
||||
return this.sanitizeUrl(url, useHttp);
|
||||
};
|
||||
|
||||
goToNextScreen = (screen, title, passProps = {}, navOptions = {}) => {
|
||||
@@ -187,8 +187,6 @@ export default class SelectServer extends PureComponent {
|
||||
};
|
||||
|
||||
handleConnect = preventDoubleTap(async () => {
|
||||
const url = this.getUrl();
|
||||
|
||||
Keyboard.dismiss();
|
||||
|
||||
if (this.state.connecting || this.state.connected) {
|
||||
@@ -197,7 +195,7 @@ export default class SelectServer extends PureComponent {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isValidUrl(url)) {
|
||||
if (!isValidUrl(this.sanitizeUrl(this.state.url))) {
|
||||
this.setState({
|
||||
error: {
|
||||
intl: {
|
||||
@@ -219,11 +217,11 @@ export default class SelectServer extends PureComponent {
|
||||
auto: true,
|
||||
certificate,
|
||||
}).build();
|
||||
this.pingServer(url);
|
||||
this.pingServer(this.state.url);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.pingServer(url);
|
||||
this.pingServer(this.state.url);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -301,7 +299,7 @@ export default class SelectServer extends PureComponent {
|
||||
resetToChannel();
|
||||
};
|
||||
|
||||
pingServer = (url, retryWithHttp = true) => {
|
||||
pingServer = async (url, retryWithHttp = true) => {
|
||||
const {
|
||||
getPing,
|
||||
handleServerUrlChanged,
|
||||
@@ -315,9 +313,6 @@ export default class SelectServer extends PureComponent {
|
||||
error: null,
|
||||
});
|
||||
|
||||
Client4.setUrl(url);
|
||||
handleServerUrlChanged(url);
|
||||
|
||||
let cancel = false;
|
||||
this.cancelPing = () => {
|
||||
cancel = true;
|
||||
@@ -330,13 +325,20 @@ export default class SelectServer extends PureComponent {
|
||||
this.cancelPing = null;
|
||||
};
|
||||
|
||||
getPing().then((result) => {
|
||||
const serverUrl = await this.getUrl(url, !retryWithHttp);
|
||||
Client4.setUrl(serverUrl);
|
||||
handleServerUrlChanged(serverUrl);
|
||||
|
||||
try {
|
||||
const result = await getPing();
|
||||
|
||||
if (cancel) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.error && retryWithHttp) {
|
||||
this.pingServer(url.replace('https:', 'http:'), false);
|
||||
const nurl = serverUrl.replace('https:', 'http:');
|
||||
this.pingServer(nurl, false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -350,7 +352,7 @@ export default class SelectServer extends PureComponent {
|
||||
connecting: false,
|
||||
error: result.error,
|
||||
});
|
||||
}).catch(() => {
|
||||
} catch {
|
||||
if (cancel) {
|
||||
return;
|
||||
}
|
||||
@@ -358,9 +360,23 @@ export default class SelectServer extends PureComponent {
|
||||
this.setState({
|
||||
connecting: false,
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
sanitizeUrl = (url, useHttp = false) => {
|
||||
const urlParse = require('url-parse');
|
||||
let preUrl = urlParse(url, true);
|
||||
|
||||
if (!preUrl.host || preUrl.protocol === 'file:') {
|
||||
preUrl = urlParse('https://' + stripTrailingSlashes(url), true);
|
||||
}
|
||||
|
||||
if (preUrl.protocol === 'http:' && !useHttp) {
|
||||
preUrl.protocol = 'https:';
|
||||
}
|
||||
return stripTrailingSlashes(preUrl.protocol + '//' + preUrl.host + preUrl.pathname);
|
||||
}
|
||||
|
||||
scheduleSessionExpiredNotification = () => {
|
||||
const {intl} = this.context;
|
||||
const {actions} = this.props;
|
||||
|
||||
@@ -2,11 +2,8 @@
|
||||
|
||||
exports[`SelectTeam should match snapshot for fail of teams 1`] = `
|
||||
<FailedNetworkAction
|
||||
actionDefaultMessage="try again"
|
||||
actionId="mobile.failed_network_action.retry"
|
||||
errorDefaultMessage="Messages will load when you have an internet connection or {tryAgainAction}."
|
||||
errorId="mobile.failed_network_action.shortDescription"
|
||||
onRetry={[Function]}
|
||||
showAction={true}
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
|
||||
@@ -11,53 +11,54 @@ exports[`SelectorScreen should match snapshot for channels 1`] = `
|
||||
<Connect(StatusBar) />
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"height": 38,
|
||||
"marginVertical": 5,
|
||||
"paddingLeft": 8,
|
||||
},
|
||||
null,
|
||||
]
|
||||
Object {
|
||||
"height": 38,
|
||||
"marginVertical": 5,
|
||||
"paddingLeft": 8,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Search
|
||||
autoCapitalize="none"
|
||||
backArrowSize={24}
|
||||
backgroundColor="transparent"
|
||||
blurOnSubmit={false}
|
||||
cancelTitle="Cancel"
|
||||
containerHeight={40}
|
||||
deleteIconSize={20}
|
||||
editable={true}
|
||||
inputHeight={33}
|
||||
inputStyle={
|
||||
Object {
|
||||
"backgroundColor": "rgba(61,60,64,0.2)",
|
||||
"color": "#3d3c40",
|
||||
"fontSize": 15,
|
||||
<View
|
||||
style={null}
|
||||
>
|
||||
<Search
|
||||
autoCapitalize="none"
|
||||
backArrowSize={24}
|
||||
backgroundColor="transparent"
|
||||
blurOnSubmit={false}
|
||||
cancelTitle="Cancel"
|
||||
containerHeight={40}
|
||||
deleteIconSize={20}
|
||||
editable={true}
|
||||
inputHeight={33}
|
||||
inputStyle={
|
||||
Object {
|
||||
"backgroundColor": "rgba(61,60,64,0.2)",
|
||||
"color": "#3d3c40",
|
||||
"fontSize": 15,
|
||||
}
|
||||
}
|
||||
}
|
||||
keyboardAppearance="light"
|
||||
keyboardShouldPersist={false}
|
||||
keyboardType="default"
|
||||
onBlur={[Function]}
|
||||
onCancelButtonPress={[Function]}
|
||||
onChangeText={[Function]}
|
||||
onSearchButtonPress={[Function]}
|
||||
onSelectionChange={[Function]}
|
||||
placeholder="Search"
|
||||
placeholderTextColor="rgba(61,60,64,0.5)"
|
||||
returnKeyType="search"
|
||||
searchBarRightMargin={0}
|
||||
searchIconSize={24}
|
||||
showArrow={false}
|
||||
showCancel={true}
|
||||
tintColorDelete="rgba(61,60,64,0.5)"
|
||||
tintColorSearch="rgba(61,60,64,0.5)"
|
||||
titleCancelColor="#3d3c40"
|
||||
value=""
|
||||
/>
|
||||
keyboardAppearance="light"
|
||||
keyboardShouldPersist={false}
|
||||
keyboardType="default"
|
||||
onBlur={[Function]}
|
||||
onCancelButtonPress={[Function]}
|
||||
onChangeText={[Function]}
|
||||
onSearchButtonPress={[Function]}
|
||||
onSelectionChange={[Function]}
|
||||
placeholder="Search"
|
||||
placeholderTextColor="rgba(61,60,64,0.5)"
|
||||
returnKeyType="search"
|
||||
searchBarRightMargin={0}
|
||||
searchIconSize={24}
|
||||
showArrow={false}
|
||||
showCancel={true}
|
||||
tintColorDelete="rgba(61,60,64,0.5)"
|
||||
tintColorSearch="rgba(61,60,64,0.5)"
|
||||
titleCancelColor="#3d3c40"
|
||||
value=""
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<CustomList
|
||||
canRefresh={true}
|
||||
@@ -116,53 +117,54 @@ exports[`SelectorScreen should match snapshot for channels 2`] = `
|
||||
<Connect(StatusBar) />
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"height": 38,
|
||||
"marginVertical": 5,
|
||||
"paddingLeft": 8,
|
||||
},
|
||||
null,
|
||||
]
|
||||
Object {
|
||||
"height": 38,
|
||||
"marginVertical": 5,
|
||||
"paddingLeft": 8,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Search
|
||||
autoCapitalize="none"
|
||||
backArrowSize={24}
|
||||
backgroundColor="transparent"
|
||||
blurOnSubmit={false}
|
||||
cancelTitle="Cancel"
|
||||
containerHeight={40}
|
||||
deleteIconSize={20}
|
||||
editable={true}
|
||||
inputHeight={33}
|
||||
inputStyle={
|
||||
Object {
|
||||
"backgroundColor": "rgba(61,60,64,0.2)",
|
||||
"color": "#3d3c40",
|
||||
"fontSize": 15,
|
||||
<View
|
||||
style={null}
|
||||
>
|
||||
<Search
|
||||
autoCapitalize="none"
|
||||
backArrowSize={24}
|
||||
backgroundColor="transparent"
|
||||
blurOnSubmit={false}
|
||||
cancelTitle="Cancel"
|
||||
containerHeight={40}
|
||||
deleteIconSize={20}
|
||||
editable={true}
|
||||
inputHeight={33}
|
||||
inputStyle={
|
||||
Object {
|
||||
"backgroundColor": "rgba(61,60,64,0.2)",
|
||||
"color": "#3d3c40",
|
||||
"fontSize": 15,
|
||||
}
|
||||
}
|
||||
}
|
||||
keyboardAppearance="light"
|
||||
keyboardShouldPersist={false}
|
||||
keyboardType="default"
|
||||
onBlur={[Function]}
|
||||
onCancelButtonPress={[Function]}
|
||||
onChangeText={[Function]}
|
||||
onSearchButtonPress={[Function]}
|
||||
onSelectionChange={[Function]}
|
||||
placeholder="Search"
|
||||
placeholderTextColor="rgba(61,60,64,0.5)"
|
||||
returnKeyType="search"
|
||||
searchBarRightMargin={0}
|
||||
searchIconSize={24}
|
||||
showArrow={false}
|
||||
showCancel={true}
|
||||
tintColorDelete="rgba(61,60,64,0.5)"
|
||||
tintColorSearch="rgba(61,60,64,0.5)"
|
||||
titleCancelColor="#3d3c40"
|
||||
value=""
|
||||
/>
|
||||
keyboardAppearance="light"
|
||||
keyboardShouldPersist={false}
|
||||
keyboardType="default"
|
||||
onBlur={[Function]}
|
||||
onCancelButtonPress={[Function]}
|
||||
onChangeText={[Function]}
|
||||
onSearchButtonPress={[Function]}
|
||||
onSelectionChange={[Function]}
|
||||
placeholder="Search"
|
||||
placeholderTextColor="rgba(61,60,64,0.5)"
|
||||
returnKeyType="search"
|
||||
searchBarRightMargin={0}
|
||||
searchIconSize={24}
|
||||
showArrow={false}
|
||||
showCancel={true}
|
||||
tintColorDelete="rgba(61,60,64,0.5)"
|
||||
tintColorSearch="rgba(61,60,64,0.5)"
|
||||
titleCancelColor="#3d3c40"
|
||||
value=""
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<CustomList
|
||||
canRefresh={true}
|
||||
@@ -221,53 +223,54 @@ exports[`SelectorScreen should match snapshot for explicit options 1`] = `
|
||||
<Connect(StatusBar) />
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"height": 38,
|
||||
"marginVertical": 5,
|
||||
"paddingLeft": 8,
|
||||
},
|
||||
null,
|
||||
]
|
||||
Object {
|
||||
"height": 38,
|
||||
"marginVertical": 5,
|
||||
"paddingLeft": 8,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Search
|
||||
autoCapitalize="none"
|
||||
backArrowSize={24}
|
||||
backgroundColor="transparent"
|
||||
blurOnSubmit={false}
|
||||
cancelTitle="Cancel"
|
||||
containerHeight={40}
|
||||
deleteIconSize={20}
|
||||
editable={true}
|
||||
inputHeight={33}
|
||||
inputStyle={
|
||||
Object {
|
||||
"backgroundColor": "rgba(61,60,64,0.2)",
|
||||
"color": "#3d3c40",
|
||||
"fontSize": 15,
|
||||
<View
|
||||
style={null}
|
||||
>
|
||||
<Search
|
||||
autoCapitalize="none"
|
||||
backArrowSize={24}
|
||||
backgroundColor="transparent"
|
||||
blurOnSubmit={false}
|
||||
cancelTitle="Cancel"
|
||||
containerHeight={40}
|
||||
deleteIconSize={20}
|
||||
editable={true}
|
||||
inputHeight={33}
|
||||
inputStyle={
|
||||
Object {
|
||||
"backgroundColor": "rgba(61,60,64,0.2)",
|
||||
"color": "#3d3c40",
|
||||
"fontSize": 15,
|
||||
}
|
||||
}
|
||||
}
|
||||
keyboardAppearance="light"
|
||||
keyboardShouldPersist={false}
|
||||
keyboardType="default"
|
||||
onBlur={[Function]}
|
||||
onCancelButtonPress={[Function]}
|
||||
onChangeText={[Function]}
|
||||
onSearchButtonPress={[Function]}
|
||||
onSelectionChange={[Function]}
|
||||
placeholder="Search"
|
||||
placeholderTextColor="rgba(61,60,64,0.5)"
|
||||
returnKeyType="search"
|
||||
searchBarRightMargin={0}
|
||||
searchIconSize={24}
|
||||
showArrow={false}
|
||||
showCancel={true}
|
||||
tintColorDelete="rgba(61,60,64,0.5)"
|
||||
tintColorSearch="rgba(61,60,64,0.5)"
|
||||
titleCancelColor="#3d3c40"
|
||||
value=""
|
||||
/>
|
||||
keyboardAppearance="light"
|
||||
keyboardShouldPersist={false}
|
||||
keyboardType="default"
|
||||
onBlur={[Function]}
|
||||
onCancelButtonPress={[Function]}
|
||||
onChangeText={[Function]}
|
||||
onSearchButtonPress={[Function]}
|
||||
onSelectionChange={[Function]}
|
||||
placeholder="Search"
|
||||
placeholderTextColor="rgba(61,60,64,0.5)"
|
||||
returnKeyType="search"
|
||||
searchBarRightMargin={0}
|
||||
searchIconSize={24}
|
||||
showArrow={false}
|
||||
showCancel={true}
|
||||
tintColorDelete="rgba(61,60,64,0.5)"
|
||||
tintColorSearch="rgba(61,60,64,0.5)"
|
||||
titleCancelColor="#3d3c40"
|
||||
value=""
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<CustomList
|
||||
canRefresh={true}
|
||||
@@ -333,53 +336,54 @@ exports[`SelectorScreen should match snapshot for searching 1`] = `
|
||||
<Connect(StatusBar) />
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"height": 38,
|
||||
"marginVertical": 5,
|
||||
"paddingLeft": 8,
|
||||
},
|
||||
null,
|
||||
]
|
||||
Object {
|
||||
"height": 38,
|
||||
"marginVertical": 5,
|
||||
"paddingLeft": 8,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Search
|
||||
autoCapitalize="none"
|
||||
backArrowSize={24}
|
||||
backgroundColor="transparent"
|
||||
blurOnSubmit={false}
|
||||
cancelTitle="Cancel"
|
||||
containerHeight={40}
|
||||
deleteIconSize={20}
|
||||
editable={true}
|
||||
inputHeight={33}
|
||||
inputStyle={
|
||||
Object {
|
||||
"backgroundColor": "rgba(61,60,64,0.2)",
|
||||
"color": "#3d3c40",
|
||||
"fontSize": 15,
|
||||
<View
|
||||
style={null}
|
||||
>
|
||||
<Search
|
||||
autoCapitalize="none"
|
||||
backArrowSize={24}
|
||||
backgroundColor="transparent"
|
||||
blurOnSubmit={false}
|
||||
cancelTitle="Cancel"
|
||||
containerHeight={40}
|
||||
deleteIconSize={20}
|
||||
editable={true}
|
||||
inputHeight={33}
|
||||
inputStyle={
|
||||
Object {
|
||||
"backgroundColor": "rgba(61,60,64,0.2)",
|
||||
"color": "#3d3c40",
|
||||
"fontSize": 15,
|
||||
}
|
||||
}
|
||||
}
|
||||
keyboardAppearance="light"
|
||||
keyboardShouldPersist={false}
|
||||
keyboardType="default"
|
||||
onBlur={[Function]}
|
||||
onCancelButtonPress={[Function]}
|
||||
onChangeText={[Function]}
|
||||
onSearchButtonPress={[Function]}
|
||||
onSelectionChange={[Function]}
|
||||
placeholder="Search"
|
||||
placeholderTextColor="rgba(61,60,64,0.5)"
|
||||
returnKeyType="search"
|
||||
searchBarRightMargin={0}
|
||||
searchIconSize={24}
|
||||
showArrow={false}
|
||||
showCancel={true}
|
||||
tintColorDelete="rgba(61,60,64,0.5)"
|
||||
tintColorSearch="rgba(61,60,64,0.5)"
|
||||
titleCancelColor="#3d3c40"
|
||||
value="name2"
|
||||
/>
|
||||
keyboardAppearance="light"
|
||||
keyboardShouldPersist={false}
|
||||
keyboardType="default"
|
||||
onBlur={[Function]}
|
||||
onCancelButtonPress={[Function]}
|
||||
onChangeText={[Function]}
|
||||
onSearchButtonPress={[Function]}
|
||||
onSelectionChange={[Function]}
|
||||
placeholder="Search"
|
||||
placeholderTextColor="rgba(61,60,64,0.5)"
|
||||
returnKeyType="search"
|
||||
searchBarRightMargin={0}
|
||||
searchIconSize={24}
|
||||
showArrow={false}
|
||||
showCancel={true}
|
||||
tintColorDelete="rgba(61,60,64,0.5)"
|
||||
tintColorSearch="rgba(61,60,64,0.5)"
|
||||
titleCancelColor="#3d3c40"
|
||||
value="name2"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<CustomList
|
||||
canRefresh={true}
|
||||
@@ -438,53 +442,54 @@ exports[`SelectorScreen should match snapshot for users 1`] = `
|
||||
<Connect(StatusBar) />
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"height": 38,
|
||||
"marginVertical": 5,
|
||||
"paddingLeft": 8,
|
||||
},
|
||||
null,
|
||||
]
|
||||
Object {
|
||||
"height": 38,
|
||||
"marginVertical": 5,
|
||||
"paddingLeft": 8,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Search
|
||||
autoCapitalize="none"
|
||||
backArrowSize={24}
|
||||
backgroundColor="transparent"
|
||||
blurOnSubmit={false}
|
||||
cancelTitle="Cancel"
|
||||
containerHeight={40}
|
||||
deleteIconSize={20}
|
||||
editable={true}
|
||||
inputHeight={33}
|
||||
inputStyle={
|
||||
Object {
|
||||
"backgroundColor": "rgba(61,60,64,0.2)",
|
||||
"color": "#3d3c40",
|
||||
"fontSize": 15,
|
||||
<View
|
||||
style={null}
|
||||
>
|
||||
<Search
|
||||
autoCapitalize="none"
|
||||
backArrowSize={24}
|
||||
backgroundColor="transparent"
|
||||
blurOnSubmit={false}
|
||||
cancelTitle="Cancel"
|
||||
containerHeight={40}
|
||||
deleteIconSize={20}
|
||||
editable={true}
|
||||
inputHeight={33}
|
||||
inputStyle={
|
||||
Object {
|
||||
"backgroundColor": "rgba(61,60,64,0.2)",
|
||||
"color": "#3d3c40",
|
||||
"fontSize": 15,
|
||||
}
|
||||
}
|
||||
}
|
||||
keyboardAppearance="light"
|
||||
keyboardShouldPersist={false}
|
||||
keyboardType="default"
|
||||
onBlur={[Function]}
|
||||
onCancelButtonPress={[Function]}
|
||||
onChangeText={[Function]}
|
||||
onSearchButtonPress={[Function]}
|
||||
onSelectionChange={[Function]}
|
||||
placeholder="Search"
|
||||
placeholderTextColor="rgba(61,60,64,0.5)"
|
||||
returnKeyType="search"
|
||||
searchBarRightMargin={0}
|
||||
searchIconSize={24}
|
||||
showArrow={false}
|
||||
showCancel={true}
|
||||
tintColorDelete="rgba(61,60,64,0.5)"
|
||||
tintColorSearch="rgba(61,60,64,0.5)"
|
||||
titleCancelColor="#3d3c40"
|
||||
value=""
|
||||
/>
|
||||
keyboardAppearance="light"
|
||||
keyboardShouldPersist={false}
|
||||
keyboardType="default"
|
||||
onBlur={[Function]}
|
||||
onCancelButtonPress={[Function]}
|
||||
onChangeText={[Function]}
|
||||
onSearchButtonPress={[Function]}
|
||||
onSelectionChange={[Function]}
|
||||
placeholder="Search"
|
||||
placeholderTextColor="rgba(61,60,64,0.5)"
|
||||
returnKeyType="search"
|
||||
searchBarRightMargin={0}
|
||||
searchIconSize={24}
|
||||
showArrow={false}
|
||||
showCancel={true}
|
||||
tintColorDelete="rgba(61,60,64,0.5)"
|
||||
tintColorSearch="rgba(61,60,64,0.5)"
|
||||
titleCancelColor="#3d3c40"
|
||||
value=""
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<CustomList
|
||||
canRefresh={true}
|
||||
@@ -543,53 +548,54 @@ exports[`SelectorScreen should match snapshot for users 2`] = `
|
||||
<Connect(StatusBar) />
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"height": 38,
|
||||
"marginVertical": 5,
|
||||
"paddingLeft": 8,
|
||||
},
|
||||
null,
|
||||
]
|
||||
Object {
|
||||
"height": 38,
|
||||
"marginVertical": 5,
|
||||
"paddingLeft": 8,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Search
|
||||
autoCapitalize="none"
|
||||
backArrowSize={24}
|
||||
backgroundColor="transparent"
|
||||
blurOnSubmit={false}
|
||||
cancelTitle="Cancel"
|
||||
containerHeight={40}
|
||||
deleteIconSize={20}
|
||||
editable={true}
|
||||
inputHeight={33}
|
||||
inputStyle={
|
||||
Object {
|
||||
"backgroundColor": "rgba(61,60,64,0.2)",
|
||||
"color": "#3d3c40",
|
||||
"fontSize": 15,
|
||||
<View
|
||||
style={null}
|
||||
>
|
||||
<Search
|
||||
autoCapitalize="none"
|
||||
backArrowSize={24}
|
||||
backgroundColor="transparent"
|
||||
blurOnSubmit={false}
|
||||
cancelTitle="Cancel"
|
||||
containerHeight={40}
|
||||
deleteIconSize={20}
|
||||
editable={true}
|
||||
inputHeight={33}
|
||||
inputStyle={
|
||||
Object {
|
||||
"backgroundColor": "rgba(61,60,64,0.2)",
|
||||
"color": "#3d3c40",
|
||||
"fontSize": 15,
|
||||
}
|
||||
}
|
||||
}
|
||||
keyboardAppearance="light"
|
||||
keyboardShouldPersist={false}
|
||||
keyboardType="default"
|
||||
onBlur={[Function]}
|
||||
onCancelButtonPress={[Function]}
|
||||
onChangeText={[Function]}
|
||||
onSearchButtonPress={[Function]}
|
||||
onSelectionChange={[Function]}
|
||||
placeholder="Search"
|
||||
placeholderTextColor="rgba(61,60,64,0.5)"
|
||||
returnKeyType="search"
|
||||
searchBarRightMargin={0}
|
||||
searchIconSize={24}
|
||||
showArrow={false}
|
||||
showCancel={true}
|
||||
tintColorDelete="rgba(61,60,64,0.5)"
|
||||
tintColorSearch="rgba(61,60,64,0.5)"
|
||||
titleCancelColor="#3d3c40"
|
||||
value=""
|
||||
/>
|
||||
keyboardAppearance="light"
|
||||
keyboardShouldPersist={false}
|
||||
keyboardType="default"
|
||||
onBlur={[Function]}
|
||||
onCancelButtonPress={[Function]}
|
||||
onChangeText={[Function]}
|
||||
onSearchButtonPress={[Function]}
|
||||
onSelectionChange={[Function]}
|
||||
placeholder="Search"
|
||||
placeholderTextColor="rgba(61,60,64,0.5)"
|
||||
returnKeyType="search"
|
||||
searchBarRightMargin={0}
|
||||
searchIconSize={24}
|
||||
showArrow={false}
|
||||
showCancel={true}
|
||||
tintColorDelete="rgba(61,60,64,0.5)"
|
||||
tintColorSearch="rgba(61,60,64,0.5)"
|
||||
titleCancelColor="#3d3c40"
|
||||
value=""
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<CustomList
|
||||
canRefresh={true}
|
||||
|
||||
@@ -316,25 +316,27 @@ export default class SelectorScreen extends PureComponent {
|
||||
return (
|
||||
<View style={style.container}>
|
||||
<StatusBar/>
|
||||
<View style={[style.searchBar, padding(isLandscape)]}>
|
||||
<SearchBar
|
||||
ref={this.setSearchBarRef}
|
||||
placeholder={formatMessage({id: 'search_bar.search', defaultMessage: 'Search'})}
|
||||
cancelTitle={formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'})}
|
||||
backgroundColor='transparent'
|
||||
inputHeight={33}
|
||||
inputStyle={searchBarInput}
|
||||
placeholderTextColor={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
tintColorSearch={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
tintColorDelete={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
titleCancelColor={theme.centerChannelColor}
|
||||
onChangeText={this.onSearch}
|
||||
onSearchButtonPress={this.onSearch}
|
||||
onCancelButtonPress={this.clearSearch}
|
||||
autoCapitalize='none'
|
||||
keyboardAppearance={getKeyboardAppearanceFromTheme(theme)}
|
||||
value={term}
|
||||
/>
|
||||
<View style={style.searchBar}>
|
||||
<View style={padding(isLandscape)}>
|
||||
<SearchBar
|
||||
ref={this.setSearchBarRef}
|
||||
placeholder={formatMessage({id: 'search_bar.search', defaultMessage: 'Search'})}
|
||||
cancelTitle={formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'})}
|
||||
backgroundColor='transparent'
|
||||
inputHeight={33}
|
||||
inputStyle={searchBarInput}
|
||||
placeholderTextColor={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
tintColorSearch={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
tintColorDelete={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
titleCancelColor={theme.centerChannelColor}
|
||||
onChangeText={this.onSearch}
|
||||
onSearchButtonPress={this.onSearch}
|
||||
onCancelButtonPress={this.clearSearch}
|
||||
autoCapitalize='none'
|
||||
keyboardAppearance={getKeyboardAppearanceFromTheme(theme)}
|
||||
value={term}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<CustomList
|
||||
data={data}
|
||||
|
||||
@@ -32,7 +32,7 @@ class Settings extends PureComponent {
|
||||
currentTeamId: PropTypes.string.isRequired,
|
||||
currentUserId: PropTypes.string.isRequired,
|
||||
currentUrl: PropTypes.string.isRequired,
|
||||
errors: PropTypes.array.isRequired,
|
||||
errors: PropTypes.object.isRequired,
|
||||
intl: intlShape.isRequired,
|
||||
joinableTeams: PropTypes.array.isRequired,
|
||||
theme: PropTypes.object,
|
||||
|
||||
@@ -112,26 +112,28 @@ export default class Timezone extends PureComponent {
|
||||
return (
|
||||
<View style={style.container}>
|
||||
<StatusBar/>
|
||||
<View style={[style.header, padding(isLandscape)]}>
|
||||
<SearchBar
|
||||
ref={this.setSearchBarRef}
|
||||
placeholder={intl.formatMessage({id: 'search_bar.search', defaultMessage: 'Search'})}
|
||||
cancelTitle={intl.formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'})}
|
||||
backgroundColor='transparent'
|
||||
inputHeight={Platform.OS === 'ios' ? 33 : 46}
|
||||
inputStyle={searchBarInput}
|
||||
placeholderTextColor={changeOpacity(theme.sidebarHeaderTextColor, 0.5)}
|
||||
selectionColor={changeOpacity(theme.sidebarHeaderTextColor, 0.5)}
|
||||
tintColorSearch={changeOpacity(theme.sidebarHeaderTextColor, 0.5)}
|
||||
tintColorDelete={changeOpacity(theme.sidebarHeaderTextColor, 0.5)}
|
||||
titleCancelColor={theme.sidebarHeaderTextColor}
|
||||
onChangeText={this.handleTextChanged}
|
||||
autoCapitalize='none'
|
||||
value={value}
|
||||
containerStyle={style.searchBarContainer}
|
||||
showArrow={false}
|
||||
keyboardAppearance={getKeyboardAppearanceFromTheme(theme)}
|
||||
/>
|
||||
<View style={style.header}>
|
||||
<View style={padding(isLandscape)}>
|
||||
<SearchBar
|
||||
ref={this.setSearchBarRef}
|
||||
placeholder={intl.formatMessage({id: 'search_bar.search', defaultMessage: 'Search'})}
|
||||
cancelTitle={intl.formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'})}
|
||||
backgroundColor='transparent'
|
||||
inputHeight={Platform.OS === 'ios' ? 33 : 46}
|
||||
inputStyle={searchBarInput}
|
||||
placeholderTextColor={changeOpacity(theme.sidebarHeaderTextColor, 0.5)}
|
||||
selectionColor={changeOpacity(theme.sidebarHeaderTextColor, 0.5)}
|
||||
tintColorSearch={changeOpacity(theme.sidebarHeaderTextColor, 0.5)}
|
||||
tintColorDelete={changeOpacity(theme.sidebarHeaderTextColor, 0.5)}
|
||||
titleCancelColor={theme.sidebarHeaderTextColor}
|
||||
onChangeText={this.handleTextChanged}
|
||||
autoCapitalize='none'
|
||||
value={value}
|
||||
containerStyle={style.searchBarContainer}
|
||||
showArrow={false}
|
||||
keyboardAppearance={getKeyboardAppearanceFromTheme(theme)}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<FlatList
|
||||
data={this.filteredTimezones(value)}
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
Platform,
|
||||
} from 'react-native';
|
||||
import {WebView} from 'react-native-webview';
|
||||
import CookieManager from '@react-native-community/cookies';
|
||||
import CookieManager from 'react-native-cookies';
|
||||
import urlParse from 'url-parse';
|
||||
|
||||
import {Client4} from '@mm-redux/client';
|
||||
@@ -27,7 +27,7 @@ const HEADERS = {
|
||||
'X-Mobile-App': 'mattermost',
|
||||
};
|
||||
|
||||
const postMessageJS = "window.ReactNativeWebView.postMessage(document.body.innerText, '*');";
|
||||
const postMessageJS = "window.postMessage(document.body.innerText, '*');";
|
||||
|
||||
// Used to make sure that OneLogin forms scale appropriately on both platforms.
|
||||
const oneLoginFormScalingJS = `
|
||||
@@ -87,15 +87,15 @@ class SSO extends PureComponent {
|
||||
switch (props.ssoType) {
|
||||
case ViewTypes.GITLAB:
|
||||
this.loginUrl = `${props.serverUrl}/oauth/gitlab/mobile_login`;
|
||||
this.completedUrl = '/signup/gitlab/complete';
|
||||
this.completeUrlPath = '/signup/gitlab/complete';
|
||||
break;
|
||||
case ViewTypes.SAML:
|
||||
this.loginUrl = `${props.serverUrl}/login/sso/saml?action=mobile`;
|
||||
this.completedUrl = '/login/sso/saml';
|
||||
this.completeUrlPath = '/login/sso/saml';
|
||||
break;
|
||||
case ViewTypes.OFFICE365:
|
||||
this.loginUrl = `${props.serverUrl}/oauth/office365/mobile_login`;
|
||||
this.completedUrl = '/signup/office365/complete';
|
||||
this.completeUrlPath = '/signup/office365/complete';
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -104,6 +104,45 @@ class SSO extends PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
clearTimeout(this.cookiesTimeout);
|
||||
}
|
||||
|
||||
extractCookie = (parsedUrl) => {
|
||||
const original = urlParse(this.props.serverUrl);
|
||||
|
||||
// Check whether we need to set a sub-path
|
||||
parsedUrl.set('pathname', original.pathname || '');
|
||||
|
||||
parsedUrl.set('query', '');
|
||||
Client4.setUrl(parsedUrl.href);
|
||||
|
||||
CookieManager.get(parsedUrl.href, true).then((res) => {
|
||||
const mmtoken = res.MMAUTHTOKEN;
|
||||
const token = typeof mmtoken === 'object' ? mmtoken.value : mmtoken;
|
||||
|
||||
if (token) {
|
||||
clearTimeout(this.cookiesTimeout);
|
||||
this.setState({renderWebView: false});
|
||||
const {
|
||||
ssoLogin,
|
||||
} = this.props.actions;
|
||||
|
||||
Client4.setToken(token);
|
||||
ssoLogin(token).then((result) => {
|
||||
if (result.error) {
|
||||
this.onLoadEndError(result.error);
|
||||
return;
|
||||
}
|
||||
this.goToChannel();
|
||||
});
|
||||
} else if (this.webView && !this.state.error) {
|
||||
this.webView.injectJavaScript(postMessageJS);
|
||||
this.cookiesTimeout = setTimeout(this.extractCookie.bind(null, parsedUrl), 250);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
goToChannel = () => {
|
||||
tracker.initialLoad = Date.now();
|
||||
|
||||
@@ -122,6 +161,7 @@ class SSO extends PureComponent {
|
||||
status_code: statusCode,
|
||||
} = response;
|
||||
if (id && message && statusCode !== 200) {
|
||||
clearTimeout(this.cookiesTimeout);
|
||||
this.setState({error: message});
|
||||
}
|
||||
}
|
||||
@@ -139,7 +179,7 @@ class SSO extends PureComponent {
|
||||
|
||||
if (parsed.host.includes('.onelogin.com')) {
|
||||
nextState.jsCode = oneLoginFormScalingJS;
|
||||
} else if (parsed.pathname === this.completedUrl) {
|
||||
} else if (parsed.pathname === this.completeUrlPath) {
|
||||
// To avoid `window.postMessage` conflicts in any of the SSO flows
|
||||
// we enable the onMessage handler only When the webView navigates to the final SSO URL.
|
||||
nextState.messagingEnabled = true;
|
||||
@@ -150,29 +190,15 @@ class SSO extends PureComponent {
|
||||
|
||||
onLoadEnd = (event) => {
|
||||
const url = event.nativeEvent.url;
|
||||
if (url.includes(this.completedUrl)) {
|
||||
CookieManager.get(this.props.serverUrl, this.useWebkit).then((res) => {
|
||||
const mmtoken = res.MMAUTHTOKEN;
|
||||
const token = typeof mmtoken === 'object' ? mmtoken.value : mmtoken;
|
||||
const parsed = urlParse(url);
|
||||
|
||||
if (token) {
|
||||
this.setState({renderWebView: false});
|
||||
const {
|
||||
ssoLogin,
|
||||
} = this.props.actions;
|
||||
let isLastRedirect = url.includes(this.completeUrlPath);
|
||||
if (this.props.ssoType === ViewTypes.SAML) {
|
||||
isLastRedirect = isLastRedirect && !parsed.query;
|
||||
}
|
||||
|
||||
Client4.setToken(token);
|
||||
ssoLogin(token).then((result) => {
|
||||
if (result.error) {
|
||||
this.onLoadEndError(result.error);
|
||||
return;
|
||||
}
|
||||
this.goToChannel();
|
||||
});
|
||||
} else if (this.webView && !this.state.error) {
|
||||
this.webView.injectJavaScript(postMessageJS);
|
||||
}
|
||||
});
|
||||
if (isLastRedirect) {
|
||||
this.extractCookie(parsed);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -214,7 +240,7 @@ class SSO extends PureComponent {
|
||||
<WebView
|
||||
ref={this.webViewRef}
|
||||
source={{uri: this.loginUrl, headers: HEADERS}}
|
||||
javaScriptEnabled={true}
|
||||
javaScriptEnabledAndroid={true}
|
||||
automaticallyAdjustContentInsets={false}
|
||||
startInLoadingState={true}
|
||||
onNavigationStateChange={this.onNavigationStateChange}
|
||||
@@ -222,7 +248,7 @@ class SSO extends PureComponent {
|
||||
injectedJavaScript={jsCode}
|
||||
onLoadEnd={this.onLoadEnd}
|
||||
onMessage={messagingEnabled ? this.onMessage : null}
|
||||
sharedCookiesEnabled={Platform.OS === 'android'}
|
||||
useSharedProcessPool={true}
|
||||
cacheEnabled={false}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -288,11 +288,8 @@ exports[`TermsOfService should match snapshot for fail of get terms 1`] = `
|
||||
>
|
||||
<Connect(StatusBar) />
|
||||
<FailedNetworkAction
|
||||
actionDefaultMessage="try again"
|
||||
actionId="mobile.failed_network_action.retry"
|
||||
errorDefaultMessage="Messages will load when you have an internet connection or {tryAgainAction}."
|
||||
errorId="mobile.failed_network_action.shortDescription"
|
||||
onRetry={[Function]}
|
||||
showAction={true}
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
|
||||
131
app/selectors/emojis.js
Normal file
131
app/selectors/emojis.js
Normal file
@@ -0,0 +1,131 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {createSelector} from 'reselect';
|
||||
|
||||
import {t} from '@utils/i18n';
|
||||
import {getCustomEmojisByName as selectCustomEmojisByName} from '@mm-redux/selectors/entities/emojis';
|
||||
import {createIdsSelector} from '@mm-redux/utils/helpers';
|
||||
import {BuiltInEmojis, CategoryNames, Emojis, EmojiIndicesByAlias, EmojiIndicesByCategory} from '@utils/emojis';
|
||||
|
||||
const categoryToI18n = {
|
||||
activity: {
|
||||
id: t('mobile.emoji_picker.activity'),
|
||||
defaultMessage: 'ACTIVITY',
|
||||
icon: 'futbol-o',
|
||||
},
|
||||
custom: {
|
||||
id: t('mobile.emoji_picker.custom'),
|
||||
defaultMessage: 'CUSTOM',
|
||||
icon: 'at',
|
||||
},
|
||||
flags: {
|
||||
id: t('mobile.emoji_picker.flags'),
|
||||
defaultMessage: 'FLAGS',
|
||||
icon: 'flag-o',
|
||||
},
|
||||
foods: {
|
||||
id: t('mobile.emoji_picker.foods'),
|
||||
defaultMessage: 'FOODS',
|
||||
icon: 'cutlery',
|
||||
},
|
||||
nature: {
|
||||
id: t('mobile.emoji_picker.nature'),
|
||||
defaultMessage: 'NATURE',
|
||||
icon: 'leaf',
|
||||
},
|
||||
objects: {
|
||||
id: t('mobile.emoji_picker.objects'),
|
||||
defaultMessage: 'OBJECTS',
|
||||
icon: 'lightbulb-o',
|
||||
},
|
||||
people: {
|
||||
id: t('mobile.emoji_picker.people'),
|
||||
defaultMessage: 'PEOPLE',
|
||||
icon: 'smile-o',
|
||||
},
|
||||
places: {
|
||||
id: t('mobile.emoji_picker.places'),
|
||||
defaultMessage: 'PLACES',
|
||||
icon: 'plane',
|
||||
},
|
||||
recent: {
|
||||
id: t('mobile.emoji_picker.recent'),
|
||||
defaultMessage: 'RECENTLY USED',
|
||||
icon: 'clock-o',
|
||||
},
|
||||
symbols: {
|
||||
id: t('mobile.emoji_picker.symbols'),
|
||||
defaultMessage: 'SYMBOLS',
|
||||
icon: 'heart-o',
|
||||
},
|
||||
};
|
||||
|
||||
function fillEmoji(indice) {
|
||||
const emoji = Emojis[indice];
|
||||
return {
|
||||
name: emoji.aliases[0],
|
||||
aliases: emoji.aliases,
|
||||
};
|
||||
}
|
||||
|
||||
export const selectEmojisByName = createIdsSelector(
|
||||
selectCustomEmojisByName,
|
||||
(customEmojis) => {
|
||||
const emoticons = new Set();
|
||||
for (const [key] of [...EmojiIndicesByAlias.entries(), ...customEmojis.entries()]) {
|
||||
emoticons.add(key);
|
||||
}
|
||||
|
||||
return Array.from(emoticons);
|
||||
},
|
||||
);
|
||||
|
||||
export const selectEmojisBySection = createSelector(
|
||||
selectCustomEmojisByName,
|
||||
(state) => state.views.recentEmojis,
|
||||
(customEmojis, recentEmojis) => {
|
||||
const emoticons = CategoryNames.filter((name) => name !== 'custom').map((category) => {
|
||||
const items = EmojiIndicesByCategory.get(category).map(fillEmoji);
|
||||
|
||||
const section = {
|
||||
...categoryToI18n[category],
|
||||
key: category,
|
||||
data: items,
|
||||
};
|
||||
|
||||
return section;
|
||||
});
|
||||
|
||||
const customEmojiItems = [];
|
||||
BuiltInEmojis.forEach((emoji) => {
|
||||
customEmojiItems.push({
|
||||
name: emoji,
|
||||
});
|
||||
});
|
||||
|
||||
for (const [key] of customEmojis) {
|
||||
customEmojiItems.push({
|
||||
name: key,
|
||||
});
|
||||
}
|
||||
|
||||
emoticons.push({
|
||||
...categoryToI18n.custom,
|
||||
key: 'custom',
|
||||
data: customEmojiItems,
|
||||
});
|
||||
|
||||
if (recentEmojis.length) {
|
||||
const items = recentEmojis.map((emoji) => ({name: emoji}));
|
||||
|
||||
emoticons.unshift({
|
||||
...categoryToI18n.recent,
|
||||
key: 'recent',
|
||||
data: items,
|
||||
});
|
||||
}
|
||||
|
||||
return emoticons;
|
||||
},
|
||||
);
|
||||
@@ -5,11 +5,11 @@ import AsyncStorage from '@react-native-community/async-storage';
|
||||
import * as redux from 'redux';
|
||||
import {createPersistoid, createTransform, persistReducer, persistStore, Persistor, PersistConfig} from 'redux-persist';
|
||||
import {createBlacklistFilter} from 'redux-persist-transform-filter';
|
||||
import reduxReset from 'redux-reset';
|
||||
import DeviceInfo from 'react-native-device-info';
|
||||
|
||||
import {General} from '@mm-redux/constants';
|
||||
import serviceReducer from '@mm-redux/reducers';
|
||||
import {GenericAction} from '@mm-redux/types/actions';
|
||||
import {GlobalState} from '@mm-redux/types/store';
|
||||
|
||||
import initialState from '@store/initial_state';
|
||||
@@ -178,11 +178,22 @@ export default function configureStore(storage: any, preloadedState: any = {}, o
|
||||
emojiBlackListFilter,
|
||||
],
|
||||
throttle: 100,
|
||||
timeout: 60000,
|
||||
};
|
||||
|
||||
const persistConfig: PersistConfig<GlobalState> = Object.assign({}, defaultConfig, optionalConfig);
|
||||
const baseState: any = Object.assign({}, initialState, preloadedState);
|
||||
const rootReducer: any = createReducer(serviceReducer as any, appReducer as any);
|
||||
const baseReducer: any = createReducer(serviceReducer as any, appReducer as any);
|
||||
const rootReducer: any = (state: GlobalState, action: GenericAction) => {
|
||||
if (action.type === General.OFFLINE_STORE_PURGE) {
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
if (action.data?._persist) {
|
||||
delete action?.data?._persist;
|
||||
}
|
||||
return baseReducer(action.data, action as any);
|
||||
}
|
||||
return baseReducer(state as any, action as any);
|
||||
};
|
||||
const persistedReducer = persistReducer({...persistConfig}, rootReducer);
|
||||
const options: ClientOptions = Object.assign({}, defaultOptions, optionalOptions);
|
||||
|
||||
@@ -193,7 +204,6 @@ export default function configureStore(storage: any, preloadedState: any = {}, o
|
||||
redux.applyMiddleware(
|
||||
...createMiddlewares(options),
|
||||
),
|
||||
reduxReset(General.OFFLINE_STORE_PURGE),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -222,7 +232,7 @@ export default function configureStore(storage: any, preloadedState: any = {}, o
|
||||
|
||||
store.dispatch({
|
||||
type: General.OFFLINE_STORE_PURGE,
|
||||
state,
|
||||
data: state,
|
||||
});
|
||||
|
||||
console.log('HYDRATED FROM v4', storeKeys); // eslint-disable-line no-console
|
||||
@@ -232,6 +242,8 @@ export default function configureStore(storage: any, preloadedState: any = {}, o
|
||||
});
|
||||
store.dispatch({type: General.REHYDRATED});
|
||||
AsyncStorage.multiRemove(storeKeys);
|
||||
} else if (store.getState()._persist?.rehydrated) { // eslint-disable-line no-underscore-dangle
|
||||
store.dispatch({type: General.REHYDRATED});
|
||||
} else {
|
||||
let executed = false;
|
||||
const unsubscribe = store.subscribe(() => {
|
||||
|
||||
@@ -31,9 +31,9 @@ export function cleanUpState(payload, keepCurrent = false) {
|
||||
postsInChannel: {},
|
||||
postsInThread: {},
|
||||
reactions: {},
|
||||
openGraph: payload.entities.posts.openGraph,
|
||||
selectedPostId: payload.entities.posts.selectedPostId,
|
||||
currentFocusedPostId: payload.entities.posts.currentFocusedPostId,
|
||||
openGraph: payload.entities?.posts?.openGraph,
|
||||
selectedPostId: payload.entities?.posts?.selectedPostId,
|
||||
currentFocusedPostId: payload.entities?.posts?.currentFocusedPostId,
|
||||
},
|
||||
files: {
|
||||
files: {},
|
||||
@@ -42,21 +42,20 @@ export function cleanUpState(payload, keepCurrent = false) {
|
||||
};
|
||||
|
||||
let retentionPeriod = 0;
|
||||
if (payload.entities.general && payload.entities.general.dataRetentionPolicy &&
|
||||
payload.entities.general.dataRetentionPolicy.message_deletion_enabled) {
|
||||
if (payload.entities?.general?.dataRetentionPolicy?.message_deletion_enabled) {
|
||||
retentionPeriod = payload.entities.general.dataRetentionPolicy.message_retention_cutoff;
|
||||
}
|
||||
|
||||
const postIdsToKeep = [];
|
||||
|
||||
// Keep the last 60 posts in each recently viewed channel
|
||||
nextEntities.posts.postsInChannel = cleanUpPostsInChannel(payload.entities.posts.postsInChannel, lastChannelForTeam, keepCurrent ? currentChannelId : '');
|
||||
nextEntities.posts.postsInChannel = cleanUpPostsInChannel(payload.entities.posts?.postsInChannel, lastChannelForTeam, keepCurrent ? currentChannelId : '');
|
||||
postIdsToKeep.push(...getAllFromPostsInChannel(nextEntities.posts.postsInChannel));
|
||||
|
||||
// Keep any posts that appear in search results
|
||||
let searchResults = [];
|
||||
let flaggedPosts = [];
|
||||
if (payload.entities.search) {
|
||||
if (payload.entities?.search) {
|
||||
if (payload.entities.search.results?.length) {
|
||||
const {results} = payload.entities.search;
|
||||
searchResults = results;
|
||||
@@ -71,50 +70,57 @@ export function cleanUpState(payload, keepCurrent = false) {
|
||||
}
|
||||
|
||||
const nextSearch = {
|
||||
...payload.entities.search,
|
||||
...(payload.entities.search || {}),
|
||||
results: searchResults,
|
||||
flagged: flaggedPosts,
|
||||
};
|
||||
|
||||
postIdsToKeep.forEach((postId) => {
|
||||
const post = payload.entities.posts.posts[postId];
|
||||
if (payload.entities.posts?.posts) {
|
||||
const reactions = payload.entities.posts.reactions || {};
|
||||
const fileIdsByPostId = payload.entities.files?.fileIdsByPostId || {};
|
||||
const files = payload.entities.files?.files || {};
|
||||
const postsInThread = payload.entities.posts.postsInThread || {};
|
||||
|
||||
if (post) {
|
||||
if (retentionPeriod && post.create_at < retentionPeriod) {
|
||||
// This post has been removed by data retention, so don't keep it
|
||||
removeFromPostsInChannel(nextEntities.posts.postsInChannel, post.channel_id, postId);
|
||||
postIdsToKeep.forEach((postId) => {
|
||||
const post = payload.entities.posts.posts[postId];
|
||||
|
||||
return;
|
||||
if (post) {
|
||||
if (retentionPeriod && post.create_at < retentionPeriod) {
|
||||
// This post has been removed by data retention, so don't keep it
|
||||
removeFromPostsInChannel(nextEntities.posts.postsInChannel, post.channel_id, postId);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Keep the post
|
||||
nextEntities.posts.posts[postId] = post;
|
||||
|
||||
// And its reactions
|
||||
const reaction = reactions[postId];
|
||||
if (reaction) {
|
||||
nextEntities.posts.reactions[postId] = reaction;
|
||||
}
|
||||
|
||||
// And its files
|
||||
const fileIds = fileIdsByPostId[postId];
|
||||
if (fileIds) {
|
||||
nextEntities.files.fileIdsByPostId[postId] = fileIds;
|
||||
fileIds.forEach((fileId) => {
|
||||
nextEntities.files.files[fileId] = files[fileId];
|
||||
});
|
||||
}
|
||||
|
||||
// And its comments
|
||||
const threadPosts = postsInThread[postId];
|
||||
if (threadPosts) {
|
||||
nextEntities.posts.postsInThread[postId] = threadPosts;
|
||||
}
|
||||
}
|
||||
|
||||
// Keep the post
|
||||
nextEntities.posts.posts[postId] = post;
|
||||
|
||||
// And its reactions
|
||||
const reaction = payload.entities.posts.reactions[postId];
|
||||
if (reaction) {
|
||||
nextEntities.posts.reactions[postId] = reaction;
|
||||
}
|
||||
|
||||
// And its files
|
||||
const fileIds = payload.entities.files.fileIdsByPostId[postId];
|
||||
if (fileIds) {
|
||||
nextEntities.files.fileIdsByPostId[postId] = fileIds;
|
||||
fileIds.forEach((fileId) => {
|
||||
nextEntities.files.files[fileId] = payload.entities.files.files[fileId];
|
||||
});
|
||||
}
|
||||
|
||||
// And its comments
|
||||
const postsInThread = payload.entities.posts.postsInThread[postId];
|
||||
if (postsInThread) {
|
||||
nextEntities.posts.postsInThread[postId] = postsInThread;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Remove any pending posts that haven't failed
|
||||
if (payload.entities.posts && payload.entities.posts.pendingPostIds && payload.entities.posts.pendingPostIds.length) {
|
||||
if (payload.entities.posts?.pendingPostIds?.length) {
|
||||
const nextPendingPostIds = [...payload.entities.posts.pendingPostIds];
|
||||
payload.entities.posts.pendingPostIds.forEach((id) => {
|
||||
const posts = nextEntities.posts.posts;
|
||||
@@ -135,9 +141,9 @@ export function cleanUpState(payload, keepCurrent = false) {
|
||||
}
|
||||
|
||||
nextState.views = {
|
||||
...nextState.views,
|
||||
...(nextState.views || {}),
|
||||
root: {
|
||||
...nextState.views?.root,
|
||||
...(nextState.views?.root || {}),
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
hydrationComplete: nextState.views?.root?.hydrationComplete || !nextState._persist,
|
||||
},
|
||||
@@ -161,41 +167,43 @@ export function cleanUpState(payload, keepCurrent = false) {
|
||||
export function cleanUpPostsInChannel(postsInChannel, lastChannelForTeam, currentChannelId, recentPostCount = 60) {
|
||||
const nextPostsInChannel = {};
|
||||
|
||||
for (const channelIds of Object.values(lastChannelForTeam)) {
|
||||
for (const channelId of channelIds) {
|
||||
if (nextPostsInChannel[channelId]) {
|
||||
// This is a DM or GM channel that we've already seen on another team
|
||||
continue;
|
||||
}
|
||||
|
||||
const postsForChannel = postsInChannel[channelId];
|
||||
|
||||
if (!postsForChannel) {
|
||||
// We don't have anything to keep for this channel
|
||||
continue;
|
||||
}
|
||||
|
||||
let nextPostsForChannel;
|
||||
|
||||
if (channelId === currentChannelId) {
|
||||
// Keep all of the posts for this channel
|
||||
nextPostsForChannel = postsForChannel;
|
||||
} else {
|
||||
// Only keep the most recent posts for this channel
|
||||
const recentBlock = postsForChannel.find((block) => block.recent);
|
||||
|
||||
if (!recentBlock) {
|
||||
// We don't have recent posts for this channel
|
||||
if (postsInChannel && lastChannelForTeam) {
|
||||
for (const channelIds of Object.values(lastChannelForTeam)) {
|
||||
for (const channelId of channelIds) {
|
||||
if (nextPostsInChannel[channelId]) {
|
||||
// This is a DM or GM channel that we've already seen on another team
|
||||
continue;
|
||||
}
|
||||
|
||||
nextPostsForChannel = [{
|
||||
...recentBlock,
|
||||
order: recentBlock.order.slice(0, recentPostCount),
|
||||
}];
|
||||
}
|
||||
const postsForChannel = postsInChannel[channelId];
|
||||
|
||||
nextPostsInChannel[channelId] = nextPostsForChannel;
|
||||
if (!postsForChannel) {
|
||||
// We don't have anything to keep for this channel
|
||||
continue;
|
||||
}
|
||||
|
||||
let nextPostsForChannel;
|
||||
|
||||
if (channelId === currentChannelId) {
|
||||
// Keep all of the posts for this channel
|
||||
nextPostsForChannel = postsForChannel;
|
||||
} else {
|
||||
// Only keep the most recent posts for this channel
|
||||
const recentBlock = postsForChannel.find((block) => block.recent);
|
||||
|
||||
if (!recentBlock) {
|
||||
// We don't have recent posts for this channel
|
||||
continue;
|
||||
}
|
||||
|
||||
nextPostsForChannel = [{
|
||||
...recentBlock,
|
||||
order: recentBlock.order.slice(0, recentPostCount),
|
||||
}];
|
||||
}
|
||||
|
||||
nextPostsInChannel[channelId] = nextPostsForChannel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,9 +214,11 @@ export function cleanUpPostsInChannel(postsInChannel, lastChannelForTeam, curren
|
||||
export function getAllFromPostsInChannel(postsInChannel) {
|
||||
const postIds = [];
|
||||
|
||||
for (const postsForChannel of Object.values(postsInChannel)) {
|
||||
for (const block of postsForChannel) {
|
||||
postIds.push(...block.order);
|
||||
if (postsInChannel) {
|
||||
for (const postsForChannel of Object.values(postsInChannel)) {
|
||||
for (const block of postsForChannel) {
|
||||
postIds.push(...block.order);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,6 @@ export default async function getStorage(identifier = 'default') {
|
||||
const MMKV = await new MMKVStorage.Loader().
|
||||
withInstanceID(identifier).
|
||||
setProcessingMode(MMKVStorage.MODES.MULTI_PROCESS).
|
||||
withEncryption().
|
||||
initialize();
|
||||
|
||||
return {
|
||||
|
||||
@@ -93,6 +93,12 @@ export function getStateForReset(initialState, currentState) {
|
||||
},
|
||||
teams: {
|
||||
currentTeamId,
|
||||
teams: {
|
||||
[currentTeamId]: currentState.entities.teams.teams[currentTeamId],
|
||||
},
|
||||
myMembers: {
|
||||
[currentTeamId]: currentState.entities.teams.myMembers[currentTeamId],
|
||||
},
|
||||
},
|
||||
preferences,
|
||||
},
|
||||
|
||||
@@ -37,6 +37,16 @@ describe('getStateForReset', () => {
|
||||
},
|
||||
teams: {
|
||||
currentTeamId,
|
||||
teams: {
|
||||
[currentTeamId]: {
|
||||
id: 'currentTeamId',
|
||||
name: 'test',
|
||||
display_name: 'Test',
|
||||
},
|
||||
},
|
||||
myMembers: {
|
||||
[currentTeamId]: {},
|
||||
},
|
||||
},
|
||||
preferences: {
|
||||
myPreferences: {
|
||||
@@ -74,10 +84,12 @@ describe('getStateForReset', () => {
|
||||
expect(users.profiles[currentUserId]).toBeDefined();
|
||||
});
|
||||
|
||||
it('should keep the current team ID', () => {
|
||||
it('should keep the current team', () => {
|
||||
const resetState = getStateForReset(initialState, currentState);
|
||||
const {teams} = resetState.entities;
|
||||
expect(teams.currentTeamId).toEqual(currentTeamId);
|
||||
expect(teams.teams[currentTeamId]).toEqual(currentState.entities.teams.teams[currentTeamId]);
|
||||
expect(teams.myMembers[currentTeamId]).toEqual(currentState.entities.teams.myMembers[currentTeamId]);
|
||||
});
|
||||
|
||||
it('should keep theme preferences', () => {
|
||||
|
||||
@@ -1,27 +1,22 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import {
|
||||
Alert,
|
||||
} from 'react-native';
|
||||
|
||||
import {
|
||||
setJSExceptionHandler,
|
||||
setNativeExceptionHandler,
|
||||
} from 'react-native-exception-handler';
|
||||
import {Alert} from 'react-native';
|
||||
import {setJSExceptionHandler, setNativeExceptionHandler} from 'react-native-exception-handler';
|
||||
|
||||
import {dismissAllModals} from '@actions/navigation';
|
||||
import {purgeOfflineStore} from '@actions/views/root';
|
||||
import {close as closeWebSocket} from '@actions/websocket';
|
||||
import {DEFAULT_LOCALE, getTranslations} from '@i18n';
|
||||
import {Client4} from '@mm-redux/client';
|
||||
import {logError} from '@mm-redux/actions/errors';
|
||||
import {close as closeWebSocket} from '@actions/websocket';
|
||||
|
||||
import {purgeOfflineStore} from 'app/actions/views/root';
|
||||
import {DEFAULT_LOCALE, getTranslations} from 'app/i18n';
|
||||
import {t} from 'app/utils/i18n';
|
||||
import {t} from '@utils/i18n';
|
||||
import {
|
||||
captureException,
|
||||
captureJSException,
|
||||
initializeSentry,
|
||||
LOGGER_NATIVE,
|
||||
} from 'app/utils/sentry';
|
||||
} from '@utils/sentry';
|
||||
|
||||
class JavascriptAndNativeErrorHandler {
|
||||
initializeErrorHandling = (store) => {
|
||||
@@ -60,10 +55,12 @@ class JavascriptAndNativeErrorHandler {
|
||||
|
||||
Alert.alert(
|
||||
translations[t('mobile.error_handler.title')],
|
||||
translations[t('mobile.error_handler.description')],
|
||||
translations[t('mobile.error_handler.description')] + `\n\n${e.message}\n\n${e.stack}`,
|
||||
[{
|
||||
text: translations[t('mobile.error_handler.button')],
|
||||
onPress: () => {
|
||||
onPress: async () => {
|
||||
await dismissAllModals();
|
||||
|
||||
// purge the store
|
||||
dispatch(purgeOfflineStore());
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Alert} from 'react-native';
|
||||
import {Alert, Platform} from 'react-native';
|
||||
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
|
||||
|
||||
import {Posts} from '@mm-redux/constants';
|
||||
@@ -86,7 +86,11 @@ export function isPendingPost(postId, userId) {
|
||||
}
|
||||
|
||||
export function validatePreviousVersion(previousVersion) {
|
||||
if (!previousVersion || INVALID_VERSIONS.includes(previousVersion)) {
|
||||
if (Platform.OS === 'ios') {
|
||||
INVALID_VERSIONS.push('1.31.0', '1.31.1');
|
||||
}
|
||||
|
||||
if (INVALID_VERSIONS.includes(previousVersion)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,17 +2,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Client4} from '@mm-redux/client';
|
||||
import CookieManager from '@react-native-community/cookies';
|
||||
|
||||
let mfaPreflightDone = false;
|
||||
|
||||
export function setMfaPreflightDone(state) {
|
||||
mfaPreflightDone = state;
|
||||
}
|
||||
|
||||
export function getMfaPreflightDone() {
|
||||
return mfaPreflightDone;
|
||||
}
|
||||
import CookieManager from 'react-native-cookies';
|
||||
|
||||
export function setCSRFFromCookie(url) {
|
||||
return new Promise((resolve) => {
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
// See LICENSE.txt for license information.
|
||||
// @flow
|
||||
|
||||
export function selectFirstAvailableTeam(teams, primaryTeam) {
|
||||
export function selectFirstAvailableTeam(teams, primaryTeamName) {
|
||||
let defaultTeam;
|
||||
if (primaryTeam) {
|
||||
defaultTeam = teams.find((t) => t.name === primaryTeam.toLowerCase());
|
||||
if (primaryTeamName) {
|
||||
defaultTeam = teams.find((t) => t?.name === primaryTeamName.toLowerCase());
|
||||
}
|
||||
|
||||
if (!defaultTeam) {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"about.teamEditionSt": "Ihre gesamte Team-Kommunikation an einem Ort, sofort durchsuchbar und überall verfügbar.",
|
||||
"about.teamEditiont0": "Team Edition",
|
||||
"about.teamEditiont1": "Enterprise Edition",
|
||||
"about.title": "Über Mattermost",
|
||||
"about.title": "Über {appTitle}",
|
||||
"announcment_banner.dont_show_again": "Nicht erneut anzeigen",
|
||||
"api.channel.add_member.added": "{addedUsername} durch {username} zum Kanal hinzugefügt.",
|
||||
"archivedChannelMessage": "Sie sehen einen **archivierten Kanal**. Neue Nachrichten können nicht geschickt werden.",
|
||||
@@ -27,7 +27,7 @@
|
||||
"channel_modal.descriptionHelp": "Beschreiben Sie,wie dieser Kanal genutzt werden soll.",
|
||||
"channel_modal.header": "Überschrift",
|
||||
"channel_modal.headerEx": "Z.B.: \"[Link Titel](http://beispiel.de)\"",
|
||||
"channel_modal.headerHelp": "Der Text der in der Kopfzeile des Kanals neben dem Namen steht. Zum Beispiel könnten Sie häufig genutzte Links durch Hinzufügen von [Link Titel](http://example.de) anzeigen lassen.",
|
||||
"channel_modal.headerHelp": "Der Text, der in der Kopfzeile des Kanals neben dem Namen steht. Zum Beispiel könnten Sie häufig genutzte Links durch Hinzufügen von [Link Titel](http://example.de) anzeigen lassen.",
|
||||
"channel_modal.name": "Name",
|
||||
"channel_modal.nameEx": "Z.B.: \"Bugs\", \"Marketing\", \"客户支持\"",
|
||||
"channel_modal.optional": "(optional)",
|
||||
@@ -35,8 +35,11 @@
|
||||
"channel_modal.purposeEx": "Z.B.: \"Ein Kanal um Fehler und Verbesserungsvorschläge abzulegen\"",
|
||||
"channel_notifications.ignoreChannelMentions.settings": "@channel, @here, @all ignorieren",
|
||||
"channel_notifications.muteChannel.settings": "Kanal stummschalten",
|
||||
"channel.channelHasGuests": "Dieser Kanal hat Gäste",
|
||||
"channel.hasGuests": "Diese Gruppennachricht hat Gäste",
|
||||
"channel.isGuest": "Diese Person ist ein Gast.",
|
||||
"combined_system_message.added_to_channel.many_expanded": "{users} und {lastUser} wurden durch {actor} **zum Kanal hinzugefügt**.",
|
||||
"combined_system_message.added_to_channel.one": "{firstUser} von {actor} **zum Kanal hinzugefügt**.",
|
||||
"combined_system_message.added_to_channel.one": "{firstUser} durch {actor} **zum Kanal hinzugefügt**.",
|
||||
"combined_system_message.added_to_channel.one_you": "Sie wurden durch {actor} **zum Kanal hinzugefügt**.",
|
||||
"combined_system_message.added_to_channel.two": "{firstUser} und {secondUser} wurden durch {actor} **zum Kanal hinzugefügt**.",
|
||||
"combined_system_message.added_to_team.many_expanded": "{users} und {lastUser} wurden durch {actor} **zum Team hinzugefügt**.",
|
||||
@@ -45,19 +48,19 @@
|
||||
"combined_system_message.added_to_team.two": "{firstUser} und {secondUser} wurden durch {actor} **zum Team hinzugefügt**.",
|
||||
"combined_system_message.joined_channel.many_expanded": "{users} und {lastUser} **sind dem Kanal beigetreten**.",
|
||||
"combined_system_message.joined_channel.one": "{firstUser} **ist dem Kanal beigetreten**.",
|
||||
"combined_system_message.joined_channel.one_you": "**sind dem Kanal beigetreten**.",
|
||||
"combined_system_message.joined_channel.one_you": "Sie **sind dem Kanal beigetreten**.",
|
||||
"combined_system_message.joined_channel.two": "{firstUser} und {secondUser} **sind dem Kanal beigetreten**.",
|
||||
"combined_system_message.joined_team.many_expanded": "{users} und {lastUser} **sind dem Team beigetreten**.",
|
||||
"combined_system_message.joined_team.one": "{firstUser} **ist dem Team beigetreten**.",
|
||||
"combined_system_message.joined_team.one_you": "**ist dem Team beigetreten**.",
|
||||
"combined_system_message.joined_team.one_you": "Sie **sind dem Team beigetreten**.",
|
||||
"combined_system_message.joined_team.two": "{firstUser} und {secondUser} **sind dem Team beigetreten**.",
|
||||
"combined_system_message.left_channel.many_expanded": "{users} und {lastUser} **haben den Kanal verlassen**.",
|
||||
"combined_system_message.left_channel.one": "{firstUser} **hat den Kanal verlassen**.",
|
||||
"combined_system_message.left_channel.one_you": "**hat den Kanal verlassen**.",
|
||||
"combined_system_message.left_channel.one_you": "Sie **haben den Kanal verlassen**.",
|
||||
"combined_system_message.left_channel.two": "{firstUser} und {secondUser} **haben den Kanal verlassen**.",
|
||||
"combined_system_message.left_team.many_expanded": "{users} und {lastUser} **haben das Team verlassen**.",
|
||||
"combined_system_message.left_team.one": "{firstUser} **hat das Team verlassen**.",
|
||||
"combined_system_message.left_team.one_you": "**hat das Team verlassen**.",
|
||||
"combined_system_message.left_team.one_you": "Sie **haben das Team verlassen**.",
|
||||
"combined_system_message.left_team.two": "{firstUser} und {secondUser} **haben das Team verlassen**.",
|
||||
"combined_system_message.removed_from_channel.many_expanded": "{users} und {lastUser} wurden **aus dem Kanal entfernt**.",
|
||||
"combined_system_message.removed_from_channel.one": "{firstUser} wurde **aus dem Kanal entfernt**.",
|
||||
@@ -70,21 +73,21 @@
|
||||
"combined_system_message.you": "Sie",
|
||||
"create_comment.addComment": "Kommentar hinzufügen...",
|
||||
"create_post.deactivated": "Sie betrachten einen archivierten Kanal mit einem deaktivierten Benutzer.",
|
||||
"create_post.write": "Write to {channelDisplayName}",
|
||||
"create_post.write": "In {channelDisplayName} schreiben",
|
||||
"date_separator.today": "Heute",
|
||||
"date_separator.yesterday": "Gestern",
|
||||
"edit_post.editPost": "Nachricht bearbeiten...",
|
||||
"edit_post.save": "Speichern",
|
||||
"error.team_not_found.title": "Team nicht gefunden",
|
||||
"file_attachment.download": "Download",
|
||||
"file_upload.fileAbove": "Datei über {max}MB kann nicht hochgeladen werden: {filename}",
|
||||
"get_post_link_modal.title": "Kopiere Permalink",
|
||||
"get_post_link_modal.title": "Link kopieren",
|
||||
"integrations.add": "Hinzufügen",
|
||||
"intro_messages.anyMember": " Jedes Mitglied kann diesem Kanal beitreten und folgen.",
|
||||
"intro_messages.beginning": "Start von {name}",
|
||||
"intro_messages.channel": "Kanal",
|
||||
"intro_messages.creator": "Dies ist der Start von {type} {name}, erstellt durch {creator} am {date}.",
|
||||
"intro_messages.group": "Privater Kanal",
|
||||
"intro_messages.group_message": "Dies ist der Start ihres Gruppennachrichten-Verlaufs mit diesen Teammitgliedern. Nachrichten und hier geteilte Dateien sind für Personen außerhalb dieses Bereichs nicht sichtbar.",
|
||||
"intro_messages.noCreator": "Dies ist der Start von {type} {name}, erstellt am {date}.",
|
||||
"intro_messages.creator": "Dies ist der Start von {name}, erstellt durch {creator} am {date}.",
|
||||
"intro_messages.creatorPrivate": "Dies ist der Start von {name}, erstellt durch {creator} am {date}.",
|
||||
"intro_messages.group_message": "Dies ist der Start ihres Gruppennachrichtenverlaufs mit diesen Teammitgliedern. Nachrichten und hier geteilte Dateien sind für Personen außerhalb dieses Bereichs nicht sichtbar.",
|
||||
"intro_messages.noCreator": "Dies ist der Start von {name}, erstellt am {date}.",
|
||||
"intro_messages.onlyInvited": " Nur eingeladene Mitglieder können diesen privaten Kanal sehen.",
|
||||
"last_users_message.added_to_channel.type": "wurden durch {actor} **dem Kanal hinzugefügt**.",
|
||||
"last_users_message.added_to_team.type": "wurden durch {actor} **dem Team hinzugefügt**.",
|
||||
@@ -98,9 +101,9 @@
|
||||
"last_users_message.removed_from_team.type": "wurden **aus dem Team entfernt**.",
|
||||
"login_mfa.enterToken": "Um ihre Anmeldung zu vervollständigen, geben Sie bitte den Token des Authenticators ein",
|
||||
"login_mfa.token": "MFA Token",
|
||||
"login_mfa.tokenReq": "Bitte geben Sie den MFA Token ein",
|
||||
"login_mfa.tokenReq": "Bitte geben Sie den MFA-Token ein",
|
||||
"login.email": "E-Mail-Adresse",
|
||||
"login.forgot": "Ich habe mein Passwort vergessen",
|
||||
"login.forgot": "Ich habe mein Passwort vergessen.",
|
||||
"login.invalidPassword": "Ihr Passwort ist falsch.",
|
||||
"login.ldapUsername": "AD/LDAP-Benutzername",
|
||||
"login.ldapUsernameLower": "AD/LDAP-Benutzername",
|
||||
@@ -128,7 +131,6 @@
|
||||
"mobile.account_notifications.threads_mentions": "Erwähnungen in Antworten",
|
||||
"mobile.account_notifications.threads_start": "Diskussionen die ich starte",
|
||||
"mobile.account_notifications.threads_start_participate": "Diskussionen die ich starte oder an denen ich teilnehme",
|
||||
"mobile.account.settings.cancel": "Abbrechen",
|
||||
"mobile.account.settings.save": "Speichern",
|
||||
"mobile.action_menu.select": "Wählen Sie eine Option",
|
||||
"mobile.advanced_settings.clockDisplay": "Uhrzeit-Format",
|
||||
@@ -137,36 +139,44 @@
|
||||
"mobile.advanced_settings.delete_title": "Dokumente & Daten löschen",
|
||||
"mobile.advanced_settings.timezone": "Zeitzone",
|
||||
"mobile.advanced_settings.title": "Erweiterte Einstellungen",
|
||||
"mobile.android.camera_permission_denied_description": "Um Fotos und Videos mit ihrer Kamera aufzunehmen, ändern Sie bitte ihre Berechtigungseinstellungen.",
|
||||
"mobile.android.camera_permission_denied_title": "Kamerazugrif wird benötigt",
|
||||
"mobile.android.permission_denied_dismiss": "Verwerfen",
|
||||
"mobile.android.permission_denied_retry": "Berechtigung einstellen",
|
||||
"mobile.android.photos_permission_denied_description": "Um Bilder aus ihrer Bibliothek hochzuladen, ändern Sie bitte ihre Berechtigungseinstellungen.",
|
||||
"mobile.android.photos_permission_denied_title": "Zugriff auf Fotobibliothek wird benötigt",
|
||||
"mobile.android.storage_permission_denied_description": "Um Bilder von ihrem Android-Gerät hochzuladen, ändern Sie bitte ihre Berechtigungseinstellungen.",
|
||||
"mobile.android.storage_permission_denied_title": "Zugriff auf Dateisystem wird benötigt",
|
||||
"mobile.android.videos_permission_denied_description": "Um Videos aus ihrer Bibliothek hochzuladen, ändern Sie bitte ihre Berechtigungseinstellungen.",
|
||||
"mobile.android.videos_permission_denied_title": "Zugriff auf Videobibliothek wird benötigt",
|
||||
"mobile.alert_dialog.alertCancel": "Abbrechen",
|
||||
"mobile.android.photos_permission_denied_description": "Laden Sie Fotos auf ihre Mattermost-Instanz hoch oder speichern Sie sie auf Ihrem Gerät. Öffnen Sie die Einstellungen, um Mattermost Lese- und Schreibzugriff auf ihre Fotobibliothek zu gewähren.",
|
||||
"mobile.android.photos_permission_denied_title": "{applicationName} möchte auf Ihre Fotos zugreifen",
|
||||
"mobile.android.videos_permission_denied_description": "Laden Sie Videos auf ihre Mattermost-Instanz hoch oder speichern Sie sie auf Ihrem Gerät. Öffnen Sie die Einstellungen, um Mattermost Lese- und Schreibzugriff auf ihre Videobibliothek zu gewähren.",
|
||||
"mobile.android.videos_permission_denied_title": "{applicationName} möchte auf Ihre Videos zugreifen",
|
||||
"mobile.announcement_banner.title": "Ankündigung",
|
||||
"mobile.authentication_error.message": "Mattermost hat einen Fehler festgestellt. Bitte authentifizieren Sie sich erneut, um eine neue Sitzung zu beginnen.",
|
||||
"mobile.authentication_error.title": "Authentifizierungsfehler",
|
||||
"mobile.calendar.dayNames": "Montag,Dienstag,Mittwoch,Donnerstag,Freitag, Samstag,Sonntag",
|
||||
"mobile.calendar.dayNamesShort": "Mo,Di,Mi,Do,Fr,Sa,So",
|
||||
"mobile.calendar.monthNames": "Januar,Februar,März,April,Mai,Juni,July,August,September,Oktober,November,Dezember",
|
||||
"mobile.calendar.monthNamesShort": "Jan,Feb,Mär,Apr,Mai,Jun,Jul,Aug,Sep,Okt,Nov,Dez",
|
||||
"mobile.camera_photo_permission_denied_description": "Nehmen Sie Fotos auf und laden sie auf ihre Mattermost-Instanz hoch oder speichern Sie sie auf Ihrem Gerät. Öffnen Sie die Einstellungen, um Mattermost Lese- und Schreibzugriff auf ihre Kamera zu gewähren.",
|
||||
"mobile.camera_photo_permission_denied_title": "{applicationName} möchte auf Ihre Kamera zugreifen",
|
||||
"mobile.camera_video_permission_denied_description": "Nehmen Sie Videos auf und laden sie auf ihre Mattermost-Instanz hoch oder speichern Sie sie auf Ihrem Gerät. Öffnen Sie die Einstellungen, um Mattermost Lese- und Schreibzugriff auf ihre Kamera zu gewähren.",
|
||||
"mobile.camera_video_permission_denied_title": "{applicationName} möchte auf Ihre Kamera zugreifen",
|
||||
"mobile.channel_drawer.search": "Springe zu...",
|
||||
"mobile.channel_info.alertMessageConvertChannel": "Wenn Sie **{displayName}**** in einen privaten Kanal umwandeln, bleiben Historie und Mitgliedschaft erhalten. Öffentlich freigegebene Dateien bleiben für jeden mit dem Link zugänglich. Die Mitgliedschaft in einem privaten Kanal ist nur auf Einladung möglich. \n\nDie Änderung ist dauerhaft und kann nicht rückgängig gemacht werden.\n\nSind Sie sicher, dass Sie **{displayName}**** in einen privaten Kanal umwandeln möchten?",
|
||||
"mobile.channel_info.alertMessageDeleteChannel": "Sind Sie sicher, dass Sie den {term} {name} archivieren möchten?",
|
||||
"mobile.channel_info.alertMessageLeaveChannel": "Sind Sie sicher, dass Sie den {term} {name} verlassen möchten?",
|
||||
"mobile.channel_info.alertMessageUnarchiveChannel": "Sind Sie sicher, dass Sie den {term} {name} wiederherstellen möchten?",
|
||||
"mobile.channel_info.alertNo": "Nein",
|
||||
"mobile.channel_info.alertTitleConvertChannel": "{displayName} in privaten Kanal umwandeln?",
|
||||
"mobile.channel_info.alertTitleDeleteChannel": "{term} archivieren",
|
||||
"mobile.channel_info.alertTitleLeaveChannel": "{term} verlassen",
|
||||
"mobile.channel_info.alertTitleUnarchiveChannel": "{term} wiederherstellen",
|
||||
"mobile.channel_info.alertYes": "Ja",
|
||||
"mobile.channel_info.convert": "In privaten Kanal umwandeln",
|
||||
"mobile.channel_info.convert_failed": "Wir konnten {displayName} nicht in einen privaten Kanal umwandeln.",
|
||||
"mobile.channel_info.convert_success": "{displayName} ist nun ein privater Kanal.",
|
||||
"mobile.channel_info.copy_header": "Header kopieren",
|
||||
"mobile.channel_info.copy_purpose": "Zweck kopieren",
|
||||
"mobile.channel_info.delete_failed": "Der Kanal {displayName} konnte nicht archiviert werden. Bitte überprüfen Sie ihre Verbindung und versuchen es erneut.",
|
||||
"mobile.channel_info.edit": "Kanal bearbeiten",
|
||||
"mobile.channel_info.privateChannel": "Privater Kanal",
|
||||
"mobile.channel_info.publicChannel": "Öffentlicher Kanal",
|
||||
"mobile.channel_info.unarchive_failed": "Der Kanal {displayName} konnte nicht wiederhergestellt werden. Bitte überprüfen Sie ihre Verbindung und versuchen es erneut.",
|
||||
"mobile.channel_list.alertNo": "Nein",
|
||||
"mobile.channel_list.alertYes": "Ja",
|
||||
"mobile.channel_list.archived": "ARCHIVIERT",
|
||||
"mobile.channel_list.channels": "KANÄLE",
|
||||
"mobile.channel_list.closeDM": "Direktnachricht schließen",
|
||||
"mobile.channel_list.closeGM": "Gruppennachricht schließen",
|
||||
@@ -174,14 +184,13 @@
|
||||
"mobile.channel_list.not_member": "KEIN MITGLIED",
|
||||
"mobile.channel_list.unreads": "UNGELESENE",
|
||||
"mobile.channel_members.add_members_alert": "Sie müssen mindestens ein Mitglied auswählen, um es dem Kanal hinzuzufügen.",
|
||||
"mobile.channel.markAsRead": "Als gelesen markieren",
|
||||
"mobile.client_upgrade": "App aktualisieren",
|
||||
"mobile.client_upgrade.can_upgrade_subtitle": "Es steht eine neue Version zum Herunterladen bereit.",
|
||||
"mobile.client_upgrade.can_upgrade_title": "Aktualisierung verfügbar",
|
||||
"mobile.client_upgrade.close": "Später Aktualisieren",
|
||||
"mobile.client_upgrade.current_version": "Neueste Version: {version}",
|
||||
"mobile.client_upgrade.download_error.message": "Es ist ein Fehler beim Herunterladen der neuen Version aufgetreten.",
|
||||
"mobile.client_upgrade.download_error.title": "Aktualisierung konnte nicht installiert werden",
|
||||
"mobile.client_upgrade.download_error.title": "Konnte Aktualisierung nicht installieren.",
|
||||
"mobile.client_upgrade.latest_version": "Ihre Version: {version}",
|
||||
"mobile.client_upgrade.listener.dismiss_button": "Verwerfen",
|
||||
"mobile.client_upgrade.listener.learn_more_button": "Mehr erfahren",
|
||||
@@ -204,25 +213,26 @@
|
||||
"mobile.create_channel.public": "Neuer Öffentlicher Kanal",
|
||||
"mobile.create_post.read_only": "Dieser Kanal ist schreibgeschützt",
|
||||
"mobile.custom_list.no_results": "Keine Ergebnisse",
|
||||
"mobile.display_settings.sidebar": "Seitenleiste",
|
||||
"mobile.display_settings.theme": "Motiv",
|
||||
"mobile.document_preview.failed_description": "Es trat ein Fehler beim Öffnen des Dokuments auf. Bitte stellen Sie sicher Sie haben einen Betrachter für {fileType} installiert und versuchen es erneut.\n",
|
||||
"mobile.document_preview.failed_description": "Es ist ein Fehler beim Öffnen des Dokuments aufgetreten. Bitte stellen Sie sicher, dass Sie einen Betrachter für {fileType} installiert haben und versuchen es erneut.\n",
|
||||
"mobile.document_preview.failed_title": "Dokument öffnen fehlgeschlagen",
|
||||
"mobile.downloader.android_complete": "Herunterladen abgeschlossen",
|
||||
"mobile.downloader.android_failed": "Herunterladen gescheitert",
|
||||
"mobile.downloader.android_permission": "Wir benötigen Zugriff auf den Donwload-Ordner, um Dateien speichern zu können.",
|
||||
"mobile.downloader.android_started": "Herunterladen gestartet",
|
||||
"mobile.downloader.android_success": "Herunterladen erfolgreich",
|
||||
"mobile.downloader.complete": "Herunterladen abgeschlossen",
|
||||
"mobile.downloader.disabled_description": "Das Herunterladen von Dateien ist auf diesem Server deaktiviert. Bitte kontaktieren Sie ihren Systemadministrator.\n",
|
||||
"mobile.downloader.disabled_title": "Herunterladen deaktiviert",
|
||||
"mobile.downloader.downloading": "Wird heruntergeladen...",
|
||||
"mobile.downloader.failed_description": "Beim Herunterladen der Datei ist ein Fehler aufgetreten. Überprüfen Sie ihre Internetverbindung und versuchen Sie es erneut.\n",
|
||||
"mobile.downloader.failed_description": "Es ist ein Fehler beim Herunterladen der Datei aufgetreten. Überprüfen Sie ihre Internetverbindung und versuchen Sie es erneut.\n",
|
||||
"mobile.downloader.failed_title": "Herunterladen gescheitert",
|
||||
"mobile.downloader.image_saved": "Bild gespeichert",
|
||||
"mobile.downloader.video_saved": "Video gespeichert",
|
||||
"mobile.drawer.teamsTitle": "Teams",
|
||||
"mobile.edit_channel": "Speichern",
|
||||
"mobile.edit_post.title": "Nachricht bearbeiten",
|
||||
"mobile.edit_profile.remove_profile_photo": "Foto entfernen",
|
||||
"mobile.emoji_picker.activity": "AKTIVITÄTEN",
|
||||
"mobile.emoji_picker.custom": "BENUTZERDEFINIERT",
|
||||
"mobile.emoji_picker.flags": "FLAGGEN",
|
||||
@@ -234,24 +244,31 @@
|
||||
"mobile.emoji_picker.recent": "ZULETZT VERWENDET",
|
||||
"mobile.emoji_picker.symbols": "SYMBOLE",
|
||||
"mobile.error_handler.button": "Neustarten",
|
||||
"mobile.error_handler.description": "\nKlicken Sie auf Neustarten um die App neu zu öffnen. Nach dem Neustart können Sie das Problem über das Einstellungsmenü melden.\n",
|
||||
"mobile.error_handler.description": "\nTippen Sie auf Neustarten um die App neu zu öffnen. Nach dem Neustart können Sie das Problem über das Einstellungsmenü melden.\n",
|
||||
"mobile.error_handler.title": "Ein unerwarteter Fehler ist aufgetreten",
|
||||
"mobile.extension.authentication_required": "Authentifizierung erforderlich: Bitte melden Sie sich zuerst in der App an.",
|
||||
"mobile.extension.file_error": "Es gab einen Fehler beim Lesen der zu teilenden Datei.\nBitte erneut versuchen.",
|
||||
"mobile.extension.file_limit": "Dateiaustausch ist auf ein Maximum von 5 Dateien begrenzt.",
|
||||
"mobile.extension.max_file_size": "Dateianhänge, die in Mattermost geteilt werden, müssen kleiner als {size} sein.",
|
||||
"mobile.extension.permission": "Mattermost benötigt Zugriff auf den Gerätespeicher, um Dateien teilen zu können.",
|
||||
"mobile.extension.team_required": "Sie müssen zu einem Team gehören, bevor Sie Dateien austauschen können.",
|
||||
"mobile.extension.title": "In Mattermost teilen",
|
||||
"mobile.failed_network_action.description": "Es scheint ein Problem mit ihrer Internetverbindung zu geben. Stellen Sie sicher, dass Sie über eine aktive Verbindung verfügen und versuchen Sie es erneut.",
|
||||
"mobile.failed_network_action.retry": "Erneut versuchen",
|
||||
"mobile.failed_network_action.shortDescription": "Stellen Sie sicher, dass Sie eine aktive Verbindung haben und versuchen es erneut.",
|
||||
"mobile.failed_network_action.shortDescription": "Messages will load when you have an internet connection.",
|
||||
"mobile.failed_network_action.teams_channel_description": "Channels could not be loaded for {teamName}.",
|
||||
"mobile.failed_network_action.teams_description": "Teams could not be loaded.",
|
||||
"mobile.failed_network_action.teams_title": "Something went wrong",
|
||||
"mobile.failed_network_action.title": "Keine Internetverbindung",
|
||||
"mobile.file_upload.browse": "Dateien durchsuchen",
|
||||
"mobile.file_upload.camera_photo": "Foto aufnehmen",
|
||||
"mobile.file_upload.camera_video": "Video aufnehmen",
|
||||
"mobile.file_upload.library": "Foto-Bibliothek",
|
||||
"mobile.file_upload.max_warning": "Uploads sind auf maximal fünf Dateien beschränkt.",
|
||||
"mobile.file_upload.unsupportedMimeType": "Nur BMP-, JPG- oder PNG-Bilder sind als Profilbilder zugelassen.",
|
||||
"mobile.file_upload.video": "Videobibliothek",
|
||||
"mobile.files_paste.error_description": "Fehler beim Einfügen der Datei(en). Bitte erneut versuchen.",
|
||||
"mobile.files_paste.error_dismiss": "Verwerfen",
|
||||
"mobile.files_paste.error_title": "Einfügen fehlgeschlagen",
|
||||
"mobile.flagged_posts.empty_description": "Markierungen dienen als Möglichkeit, Nachrichten für eine Wiedervorlage zu markieren. Ihre Markierungen sind persönlich und können nicht von anderen Benutzern gesehen werden.",
|
||||
"mobile.flagged_posts.empty_title": "Keine markierte Nachrichten",
|
||||
"mobile.help.title": "Hilfe",
|
||||
@@ -259,8 +276,9 @@
|
||||
"mobile.image_preview.save_video": "Video speichern",
|
||||
"mobile.intro_messages.default_message": "Dies ist der Kanal, den Teammitglieder sehen, wenn sie sich anmelden - benutzen Sie ihn zum Veröffentlichen von Aktualisierungen, die jeder kennen muss.",
|
||||
"mobile.intro_messages.default_welcome": "Willkommen bei {name}!",
|
||||
"mobile.intro_messages.DM": "Dies ist der Start der Privatnachrichten mit {teammate}. Privatnachrichten und hier geteilte Dateien sind für Personen außerhalb dieses Bereichs nicht sichtbar.",
|
||||
"mobile.ios.photos_permission_denied_description": "Um Fotos und Videos in ihrer Bibliothek speichern zu können, ändern Sie bitte ihre Berechtigungseinstellungen.",
|
||||
"mobile.intro_messages.DM": "Dies ist der Start der Privatnachrichtenverlaufs mit {teammate}. Privatnachrichten und hier geteilte Dateien sind für Personen außerhalb dieses Bereichs nicht sichtbar.",
|
||||
"mobile.ios.photos_permission_denied_description": "Laden Sie Fotos und Videos auf ihre Mattermost-Instanz hoch oder speichern Sie sie auf Ihrem Gerät. Öffnen Sie die Einstellungen, um Mattermost Lese- und Schreibzugriff auf ihre Foto- und Videobibliothek zu gewähren.",
|
||||
"mobile.ios.photos_permission_denied_title": "{applicationName} möchte auf Ihre Fotos zugreifen",
|
||||
"mobile.join_channel.error": "Dem Kanal {displayName} konnte nicht beigetreten werden. Bitte überprüfen Sie ihre Verbindung und versuchen es erneut.",
|
||||
"mobile.loading_channels": "Lade Kanäle...",
|
||||
"mobile.loading_members": "Lade Mitglieder...",
|
||||
@@ -271,13 +289,17 @@
|
||||
"mobile.managed.blocked_by": "Blockiert durch {vendor}",
|
||||
"mobile.managed.exit": "Beenden",
|
||||
"mobile.managed.jailbreak": "Geräten mit Jailbreak wird von {vendor} nicht vertraut, bitte beenden Sie die App.",
|
||||
"mobile.managed.not_secured.android": "Dieses Gerät muss mit einer Bildschirmsperre gesichert werden, um Mattermost verwenden zu können.",
|
||||
"mobile.managed.not_secured.ios": "Dieses Gerät muss mit einem Passcode gesichert werden, um Mattermost verwenden zu können.\n \nGehen Sie zu Einstellungen > Face ID & Passwort.",
|
||||
"mobile.managed.not_secured.ios.touchId": "Dieses Gerät muss mit einem Passcode gesichert werden, um Mattermost zu verwenden.\n \nGehen Sie zu Einstellungen > Touch ID & Passwort.",
|
||||
"mobile.managed.secured_by": "Gesichert durch {vendor}",
|
||||
"mobile.managed.settings": "Zu Einstellungen gehen",
|
||||
"mobile.markdown.code.copy_code": "Code kopieren",
|
||||
"mobile.markdown.code.plusMoreLines": "+{count, number} weitere {count, plural, one {Zeile} other {Zeilen}}",
|
||||
"mobile.markdown.image.too_large": "Bild überschreitet die maximale Auflösung von {maxWidth} x {maxHeight}:",
|
||||
"mobile.markdown.link.copy_url": "Adresse (URL) kopieren",
|
||||
"mobile.mention.copy_mention": "Erwähnung kopieren",
|
||||
"mobile.message_length.message": "Die Nachricht ist zu lang. Anzahl der Zeichen: {max}/{count}",
|
||||
"mobile.message_length.message": "Die Nachricht ist zu lang. Anzahl der Zeichen: {count}/{max}",
|
||||
"mobile.message_length.title": "Länge der Nachricht",
|
||||
"mobile.more_dms.add_more": "Sie können {remaining, number} weitere Benutzer hinzufügen",
|
||||
"mobile.more_dms.cannot_add_more": "Sie können keine weiteren Benutzer hinzufügen.",
|
||||
@@ -302,7 +324,7 @@
|
||||
"mobile.notification_settings_mobile.sound": "Ton",
|
||||
"mobile.notification_settings_mobile.sounds_title": "Benachrichtigungston",
|
||||
"mobile.notification_settings_mobile.test": "Schicke mir eine Testbenachrichtigung",
|
||||
"mobile.notification_settings_mobile.test_push": "Dies ist eine Test-Push-Benachrichtigung",
|
||||
"mobile.notification_settings_mobile.test_push": "Dies ist eine Test-Push-Benachrichtigung.",
|
||||
"mobile.notification_settings_mobile.vibrate": "Vibrieren",
|
||||
"mobile.notification_settings.auto_responder_short": "Automatische Antworten",
|
||||
"mobile.notification_settings.auto_responder.default_message": "Hallo, ich bin derzeit nicht im Büro und kann nicht auf Nachrichten antworten.",
|
||||
@@ -332,20 +354,28 @@
|
||||
"mobile.open_dm.error": "Der Direktnachrichtenkanal mit {displayName} konnte nicht geöffnet werden. Bitte überprüfen Sie ihre Verbindung und versuchen es erneut.",
|
||||
"mobile.open_gm.error": "Der Gruppennachrichtenkanal mit diesen Benutzern konnte nicht geöffnet werden. Bitte überprüfen Sie ihre Verbindung und versuchen es erneut.",
|
||||
"mobile.open_unknown_channel.error": "Konnte Kanal nicht beitreten. Bitte setzen Sie den Cache zurück und versuchen es erneut.",
|
||||
"mobile.permission_denied_dismiss": "Nicht erlauben",
|
||||
"mobile.permission_denied_retry": "Einstellungen",
|
||||
"mobile.photo_library_permission_denied_description": "Um Fotos und Videos in ihrer Bibliothek speichern zu können, ändern Sie bitte ihre Berechtigungseinstellungen.",
|
||||
"mobile.photo_library_permission_denied_title": "{applicationName} möchte auf Ihre Fotobibliothek zugreifen",
|
||||
"mobile.pinned_posts.empty_description": "Wichtige Elemente anheften durch gedrückt halten einer Nachricht und wählen von \"An Kanal anheften\".",
|
||||
"mobile.pinned_posts.empty_title": "Keine angehefteten Nachrichten",
|
||||
"mobile.post_info.add_reaction": "Reaktion hinzufügen",
|
||||
"mobile.post_info.copy_text": "Text kopieren",
|
||||
"mobile.post_info.flag": "Markieren",
|
||||
"mobile.post_info.mark_unread": "Als ungelesen markieren",
|
||||
"mobile.post_info.pin": "An Kanal anheften",
|
||||
"mobile.post_info.reply": "Antworten",
|
||||
"mobile.post_info.unflag": "Markierung entfernen",
|
||||
"mobile.post_info.unpin": "Vom Kanal abheften",
|
||||
"mobile.post_pre_header.flagged": "Markiert",
|
||||
"mobile.post_pre_header.pinned": "Angeheftet",
|
||||
"mobile.post_pre_header.pinned_flagged": "Angeheftet und markiert",
|
||||
"mobile.post_textbox.empty.message": "Sie versuchen, eine leere Nachricht zu senden.\nBitte stellen Sie sicher, dass die Nachricht Text oder eine angehängte Datei enthält.",
|
||||
"mobile.post_textbox.empty.ok": "OK",
|
||||
"mobile.post_textbox.empty.title": "Leere Nachricht",
|
||||
"mobile.post_textbox.entire_channel.cancel": "Abbrechen",
|
||||
"mobile.post_textbox.entire_channel.confirm": "Bestätigen",
|
||||
"mobile.post_textbox.entire_channel.message": "Durch die Verwendung von @all oder @channel benachrichtigen Sie {totalMembers, number} {totalMembers, plural, one {Person} other {Personen}}. Sind Sie sicher, dass sie dies tun möchten?",
|
||||
"mobile.post_textbox.entire_channel.message.with_timezones": "Durch die Verwendung von @all oder @channel benachrichtigen Sie {totalMembers, number} {totalMembers, plural, one {Person} other {Personen}} in {timezones, number} {timezones, plural, one {Zeitzone} other {Zeitzonen}}. Sind Sie sicher, dass sie dies tun möchten?",
|
||||
"mobile.post_textbox.entire_channel.title": "Bestätigen Sie das Senden von Benachrichtigungen an den gesamten Kanal",
|
||||
"mobile.post_textbox.uploadFailedDesc": "Einige Anhänge konnten nicht auf den Server hochgeladen werden. Sind Sie sicher, dass sie die Nachricht abschicken wollen?",
|
||||
"mobile.post_textbox.uploadFailedTitle": "Anhang Fehler",
|
||||
"mobile.post.cancel": "Abbrechen",
|
||||
@@ -353,9 +383,14 @@
|
||||
"mobile.post.delete_title": "Nachricht löschen",
|
||||
"mobile.post.failed_delete": "Nachricht löschen",
|
||||
"mobile.post.failed_retry": "Erneut versuchen",
|
||||
"mobile.post.failed_title": "Ihre Nachricht konnte nicht gesendet werden",
|
||||
"mobile.post.failed_title": "Konnte ihre Nachricht nicht senden.",
|
||||
"mobile.post.retry": "Aktualisieren",
|
||||
"mobile.posts_view.moreMsg": "Weitere neue Nachrichten oberhalb",
|
||||
"mobile.privacy_link": "Datenschutzbedingungen",
|
||||
"mobile.push_notification_reply.button": "Senden",
|
||||
"mobile.push_notification_reply.placeholder": "Eine Antwort schreiben...",
|
||||
"mobile.push_notification_reply.title": "Antworten",
|
||||
"mobile.reaction_header.all_emojis": "Alle",
|
||||
"mobile.recent_mentions.empty_description": "Hier werden Nachrichten auftauchen, die ihren Benutzernamen oder andere Wörter enthalten, die Erwähnungen auslösen.",
|
||||
"mobile.recent_mentions.empty_title": "Keine letzten Erwähnungen",
|
||||
"mobile.rename_channel.display_name_maxLength": "Kanalname muss kürzer als {maxLength, number} Zeichen sein",
|
||||
@@ -378,6 +413,8 @@
|
||||
"mobile.routes.channelInfo.createdBy": "Erstellt durch {creator} am ",
|
||||
"mobile.routes.channelInfo.delete_channel": "Kanal archivieren",
|
||||
"mobile.routes.channelInfo.favorite": "Favoriten",
|
||||
"mobile.routes.channelInfo.groupManaged": "Mitglieder werden von verknüpften Gruppen verwaltet.",
|
||||
"mobile.routes.channelInfo.unarchive_channel": "Kanal wiederherstellen",
|
||||
"mobile.routes.code": "{language}-Code",
|
||||
"mobile.routes.code.noLanguage": "Code",
|
||||
"mobile.routes.edit_profile": "Profil bearbeiten",
|
||||
@@ -393,6 +430,7 @@
|
||||
"mobile.routes.thread": "{channelName} Diskussion",
|
||||
"mobile.routes.thread_dm": "Direktnachrichten-Diskussion",
|
||||
"mobile.routes.user_profile": "Profil",
|
||||
"mobile.routes.user_profile.edit": "Bearbeiten",
|
||||
"mobile.routes.user_profile.local_time": "LOKALE ZEIT",
|
||||
"mobile.routes.user_profile.send_message": "Nachricht senden",
|
||||
"mobile.search.after_modifier_description": "um Nachrichten nach einem spezifischen Datum zu finden",
|
||||
@@ -405,13 +443,20 @@
|
||||
"mobile.search.no_results": "Keine Ergebnisse gefunden",
|
||||
"mobile.search.on_modifier_description": "um Nachrichten an einem spezifischen Datum zu finden",
|
||||
"mobile.search.recent_title": "Letzte Suchen",
|
||||
"mobile.select_team.guest_cant_join_team": "Ihr Gastkonto ist keinem Team oder Kanal zugeordnet. Bitte kontaktieren Sie den Administrator.",
|
||||
"mobile.select_team.join_open": "Offene Teams, denen Sie beitreten können",
|
||||
"mobile.select_team.no_teams": "Es sind keine Teams zum Betreten für Sie verfügbar.",
|
||||
"mobile.server_link.error.text": "Der Link konnte auf diesem Server nicht gefunden werden.",
|
||||
"mobile.server_link.error.title": "Link-Fehler",
|
||||
"mobile.server_link.unreachable_channel.error": "Der Link gehört zu einem gelöschten Kanal oder einem Kanal auf den Sie keinen Zugriff haben.",
|
||||
"mobile.server_link.unreachable_team.error": "Der Link verweist auf ein gelöschtes Team oder ein Team auf das Sie keinen Zugriff haben.",
|
||||
"mobile.server_ssl.error.text": "The certificate from {host} is not trusted.\n\nPlease contact your System Administrator to resolve the certificate issues and allow connections to this server.",
|
||||
"mobile.server_ssl.error.title": "Untrusted Certificate",
|
||||
"mobile.server_upgrade.button": "OK",
|
||||
"mobile.server_upgrade.description": "\nEine Serveraktualisierung ist erforderlich, um die Mattermost-App zu verwenden. Bitte Fragen Sie ihren Systemadministrator für Details.\n",
|
||||
"mobile.server_upgrade.title": "Serveraktualisierung erforderlich",
|
||||
"mobile.server_url.invalid_format": "URL muss mit http:// oder https:// beginnen",
|
||||
"mobile.session_expired": "Sitzung abgelaufen: Bitte anmelden um weiterhin Benachrichtigungen zu erhalten.",
|
||||
"mobile.session_expired": "Die Sitzung ist abgelaufen: Bitte melden Sie sich an, um weiterhin Benachrichtigungen zu erhalten. Sitzungen für {siteName} sind so konfiguriert, dass sie nach {daysCount, number} {daysCount, plural, one {Tag} other {Tagen}} ablaufen.",
|
||||
"mobile.set_status.away": "Abwesend",
|
||||
"mobile.set_status.dnd": "Nicht stören",
|
||||
"mobile.set_status.offline": "Offline",
|
||||
@@ -422,19 +467,33 @@
|
||||
"mobile.share_extension.error_message": "Es ist ein Fehler bei der Verwendung der Teilen-Erweiterung aufgetreten.",
|
||||
"mobile.share_extension.error_title": "Erweiterungs-Fehler",
|
||||
"mobile.share_extension.team": "Team",
|
||||
"mobile.share_extension.too_long_message": "Zeichenanzahl: {count}/{max}",
|
||||
"mobile.share_extension.too_long_title": "Nachricht ist zu lang",
|
||||
"mobile.sidebar_settings.permanent": "Permanente Seitenleiste",
|
||||
"mobile.sidebar_settings.permanent_description": "Seitenleiste permanent geöffnet lassen",
|
||||
"mobile.storage_permission_denied_description": "Laden Sie Dateien auf ihre Mattermost-Instanz hoch. Öffnen Sie die Einstellungen, um Mattermost Lese- und Schreibzugriff auf Dateien auf diesem Gerät zu gewähren.",
|
||||
"mobile.storage_permission_denied_title": "{applicationName} möchte auf Ihre Dateien zugreifen.",
|
||||
"mobile.suggestion.members": "Mitglieder",
|
||||
"mobile.system_message.channel_archived_message": "{username} hat den Kanal archiviert.",
|
||||
"mobile.system_message.channel_unarchived_message": "{username} hat den Kanal wiederhergestellt",
|
||||
"mobile.system_message.update_channel_displayname_message_and_forget.updated_from": "{username} hat den Kanal-Anzeigenamen geändert von: {oldDisplayName} auf: {newDisplayName}",
|
||||
"mobile.system_message.update_channel_header_message_and_forget.removed": "{username} hat die Kanalüberschrift entfernt (war: {oldHeader})",
|
||||
"mobile.system_message.update_channel_header_message_and_forget.updated_from": "{username} hat die Kanalüberschrift aktualisiert von: {oldHeader} auf: {newHeader}",
|
||||
"mobile.system_message.update_channel_header_message_and_forget.updated_to": "{username} hat die Kanalüberschrift geändert zu: {newHeader}",
|
||||
"mobile.system_message.update_channel_purpose_message.removed": "{username} hat den Kanalzweck entfernt (war: {oldPurpose})",
|
||||
"mobile.system_message.update_channel_purpose_message.updated_from": "{username} hat den Kanalzweck aktualisiert von: {oldPurpose} auf: {newPurpose}",
|
||||
"mobile.system_message.update_channel_purpose_message.updated_to": "{username} hat den Kanalzweck geändert zu: {newPurpose}",
|
||||
"mobile.terms_of_service.alert_cancel": "Abbrechen",
|
||||
"mobile.terms_of_service.alert_ok": "OK",
|
||||
"mobile.terms_of_service.alert_retry": "Erneut versuchen",
|
||||
"mobile.terms_of_service.get_terms_error_description": "Stellen Sie sicher, dass Sie über eine Internetverbindung verfügen und probieren Sie es erneut. Falls das Problem weiterhin besteht, kontaktieren Sie ihren Systemadministrator.",
|
||||
"mobile.terms_of_service.get_terms_error_title": "Konnte Nutzungsbedingungen nicht laden.",
|
||||
"mobile.terms_of_service.terms_rejected": "Sie müssen die Nutzungsbedingungen akzeptieren, bevor Sie {siteName} verwenden können. Bitte kontaktieren Sie ihren Systemadministrator für mehr Details.",
|
||||
"mobile.timezone_settings.automatically": "Automatisch einstellen",
|
||||
"mobile.timezone_settings.manual": "Zeitzone ändern",
|
||||
"mobile.timezone_settings.select": "Zeitzone auswählen",
|
||||
"mobile.tos_link": "Nutzungsbedingungen",
|
||||
"mobile.user_list.deactivated": "Deaktiviert",
|
||||
"mobile.user.settings.notifications.email.fifteenMinutes": "Alle 15 Minuten",
|
||||
"mobile.video_playback.failed_description": "Beim Abspielen des Videos ist ein Fehler aufgetreten.\n",
|
||||
"mobile.video_playback.failed_description": "Es ist ein Fehler beim Abspielen des Videos aufgetreten.\n",
|
||||
"mobile.video_playback.failed_title": "Videowiedergabe fehlgeschlagen",
|
||||
"mobile.video.save_error_message": "Um das Video zu speichern, müssen Sie es zuerst herunterladen.",
|
||||
"mobile.video.save_error_title": "Fehler beim Speichern des Videos",
|
||||
@@ -445,11 +504,17 @@
|
||||
"modal.manual_status.auto_responder.message_dnd": "Möchten Sie ihren Status auf \"Nicht stören\" umschalten und automatische Antworten deaktivieren?",
|
||||
"modal.manual_status.auto_responder.message_offline": "Möchten Sie ihren Status auf \"Offline\" umschalten und automatische Antworten deaktivieren?",
|
||||
"modal.manual_status.auto_responder.message_online": "Möchten Sie ihren Status auf \"Online\" umschalten und automatische Antworten deaktivieren?",
|
||||
"more_channels.archivedChannels": "Archivierte Kanäle",
|
||||
"more_channels.dropdownTitle": "Anzeigen",
|
||||
"more_channels.noMore": "Keine weiteren Kanäle zum Betreten",
|
||||
"more_channels.publicChannels": "Öffentliche Kanäle",
|
||||
"more_channels.showArchivedChannels": "Anzeigen: Archivierte Kanäle",
|
||||
"more_channels.showPublicChannels": "Anzeigen: Öffentliche Kanäle",
|
||||
"more_channels.title": "Weitere Kanäle",
|
||||
"msg_typing.areTyping": "{users} und {last} tippen gerade...",
|
||||
"msg_typing.isTyping": "{user} tippt...",
|
||||
"navbar_dropdown.logout": "Abmelden",
|
||||
"navbar.channel_drawer.button": "Kanäle und Teams",
|
||||
"navbar.channel_drawer.hint": "Öffnet die Kanal- und Team-Schublade.",
|
||||
"navbar.leave": "Kanal verlassen",
|
||||
"password_form.title": "Passwort zurücksetzen",
|
||||
"password_send.checkInbox": "Bitte prüfen Sie den Posteingang.",
|
||||
@@ -458,25 +523,26 @@
|
||||
"password_send.link": "Falls das Konto existiert, wurde eine E-Mail zur Passwortzurücksetzung gesendet an:",
|
||||
"password_send.reset": "Mein Passwort zurücksetzen",
|
||||
"permalink.error.access": "Der dauerhafte Link verweist auf eine gelöschte Nachricht oder einen Kanal auf den Sie keinen Zugriff haben.",
|
||||
"permalink.error.link_not_found": "Link nicht gefunden",
|
||||
"post_body.check_for_out_of_channel_groups_mentions.message": "wurde durch diese Erwähnung nicht benachrichtigt, da sich der Benutzer nicht im Kanal befinden. Er kann dem Kanal nicht hinzugefügt werden, da er nicht Mitglied der verknüpften Gruppen ist. Um ihn zu diesem Kanal hinzuzufügen, müssen er zu den verknüpften Gruppen hinzugefügt werden.",
|
||||
"post_body.check_for_out_of_channel_mentions.link.and": " und ",
|
||||
"post_body.check_for_out_of_channel_mentions.link.private": "sie zu diesem privaten Kanal hinzufügen",
|
||||
"post_body.check_for_out_of_channel_mentions.link.public": "sie zu diesem Kanal hinzufügen",
|
||||
"post_body.check_for_out_of_channel_mentions.message_last": "? Sie werden Zugriff auf den Nachrichtenverlauf haben.",
|
||||
"post_body.check_for_out_of_channel_mentions.message.multiple": "wurden erwähnt, befinden sich aber nicht im Kanal. Möchten Sie ",
|
||||
"post_body.check_for_out_of_channel_mentions.message.one": "wurde erwähnt, befinden sich aber nicht im Kanal. Möchten Sie ",
|
||||
"post_body.check_for_out_of_channel_mentions.message.multiple": "wurde durch diese Erwähnung nicht benachrichtigt, da der Benutzer sich nicht im Kanal befindet. Möchten Sie ",
|
||||
"post_body.check_for_out_of_channel_mentions.message.one": "wurde durch diese Erwähnung nicht benachrichtigt, da der Benutzer sich nicht im Kanal befindet. Möchten Sie ",
|
||||
"post_body.commentedOn": "Kommentierte auf die Nachricht von {name}: ",
|
||||
"post_body.deleted": "(Nachricht gelöscht)",
|
||||
"post_info.auto_responder": "AUTOMATISCHE ANTWORT",
|
||||
"post_info.bot": "BOT",
|
||||
"post_info.del": "Löschen",
|
||||
"post_info.edit": "Bearbeiten",
|
||||
"post_info.guest": "GAST",
|
||||
"post_info.message.show_less": "Weniger anzeigen",
|
||||
"post_info.message.show_more": "Mehr anzeigen",
|
||||
"post_info.system": "System",
|
||||
"post_message_view.edited": "(bearbeitet)",
|
||||
"posts_view.newMsg": "Neue Nachrichten",
|
||||
"rename_channel.handleHolder": "Kleinbuchstaben oder Ziffern",
|
||||
"rename_channel.url": "URL",
|
||||
"rhs_thread.rootPostDeletedMessage.body": "Ein Teil dieses Nachrichtenverlaufes wurde wegen einer Datenaufbewahrungsrichtlinie gelöscht. Sie können nicht länger auf diesen Strang antworten.",
|
||||
"search_bar.search": "Suche",
|
||||
"search_header.results": "Suchergebnisse",
|
||||
@@ -498,20 +564,22 @@
|
||||
"status_dropdown.set_offline": "Offline",
|
||||
"status_dropdown.set_online": "Online",
|
||||
"status_dropdown.set_ooo": "Nicht im Büro",
|
||||
"suggestion.mention.all": "ACHTUNG: Dies erwähnt jeden im Kanal",
|
||||
"suggestion.mention.all": "Benachrichtigt jeden in diesem Kanal",
|
||||
"suggestion.mention.channel": "Benachrichtigt jeden in diesem Kanal",
|
||||
"suggestion.mention.channels": "Meine Kanäle",
|
||||
"suggestion.mention.here": "Benachrichtigt jeden der im Kanal und online ist",
|
||||
"suggestion.mention.here": "Benachrichtigt jeden in diesem Kanal",
|
||||
"suggestion.mention.members": "Kanalmitglieder",
|
||||
"suggestion.mention.morechannels": "Andere Kanäle",
|
||||
"suggestion.mention.nonmembers": "Nicht im Kanal",
|
||||
"suggestion.mention.special": "Spezielle Erwähnungen",
|
||||
"suggestion.mention.you": "(Sie)",
|
||||
"suggestion.search.direct": "Direktnachrichten",
|
||||
"suggestion.search.private": "Private Kanäle",
|
||||
"suggestion.search.public": "Öffentliche Kanäle",
|
||||
"terms_of_service.agreeButton": "Ich stimme zu",
|
||||
"terms_of_service.api_error": "Konnte die Anfrage nicht abschließen. Falls der Fehler weiterhin besteht, fragen Sie den Systemadministrator.",
|
||||
"user.settings.display.clockDisplay": "Uhrzeit-Format",
|
||||
"user.settings.display.custom_theme": "Benutzerdefiniertes Motiv",
|
||||
"user.settings.display.militaryClock": "24-Stunden-Format (z.B.: 16:00)",
|
||||
"user.settings.display.normalClock": "12-Stunden-Format (z.B.: 4:00 PM)",
|
||||
"user.settings.display.preferTime": "Wählen Sie das bevorzugte Zeitformat aus.",
|
||||
@@ -538,7 +606,7 @@
|
||||
"user.settings.notifications.email.immediately": "Sofort",
|
||||
"user.settings.notifications.email.never": "Nie",
|
||||
"user.settings.notifications.email.send": "E-Mail-Benachrichtigungen senden",
|
||||
"user.settings.notifications.emailInfo": "E-Mail-Benachrichtigungen werden bei Erwähnungen und Direktnachrichten gesendet, sobald Sie von {siteName} mehr als 5 Minuten offline oder abwesend waren.",
|
||||
"user.settings.notifications.emailInfo": "E-Mail-Benachrichtigungen werden bei Erwähnungen und Direktnachrichten gesendet, sobald Sie mehr als 5 Minuten offline oder abwesend sind.",
|
||||
"user.settings.notifications.never": "Nie",
|
||||
"user.settings.notifications.onlyMentions": "Nur für Erwähnungen und Direktnachrichten",
|
||||
"user.settings.push_notification.away": "Abwesend oder offline",
|
||||
@@ -547,4 +615,4 @@
|
||||
"user.settings.push_notification.offline": "Offline",
|
||||
"user.settings.push_notification.online": "Online, abwesend oder offline",
|
||||
"web.root.signup_info": "Die gesamte Teamkommunikation an einem Ort, durchsuchbar und überall verfügbar"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,8 +253,11 @@
|
||||
"mobile.extension.permission": "Mattermost needs access to the device storage to share files.",
|
||||
"mobile.extension.team_required": "You must belong to a team before you can share files.",
|
||||
"mobile.extension.title": "Share in Mattermost",
|
||||
"mobile.failed_network_action.retry": "try again",
|
||||
"mobile.failed_network_action.shortDescription": "Messages will load when you have an internet connection or {tryAgainAction}.",
|
||||
"mobile.failed_network_action.retry": "Try again",
|
||||
"mobile.failed_network_action.shortDescription": "Messages will load when you have an internet connection.",
|
||||
"mobile.failed_network_action.teams_channel_description": "Channels could not be loaded for {teamName}.",
|
||||
"mobile.failed_network_action.teams_description": "Teams could not be loaded.",
|
||||
"mobile.failed_network_action.teams_title": "Something went wrong",
|
||||
"mobile.failed_network_action.title": "No internet connection",
|
||||
"mobile.file_upload.browse": "Browse Files",
|
||||
"mobile.file_upload.camera_photo": "Take Photo",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"about.teamEditionSt": "Todas las comunicaciones de tu equipo en un solo lugar, con búsquedas instantáneas y accesible de todas partes.",
|
||||
"about.teamEditiont0": "Edición Team T0",
|
||||
"about.teamEditiont1": "Edición Team T1",
|
||||
"about.title": "Acerca de Mattermost",
|
||||
"about.title": "Acerca de {appTitle}",
|
||||
"announcment_banner.dont_show_again": "No volver a mostrar",
|
||||
"api.channel.add_member.added": "{addedUsername} agregado al canal por {username}",
|
||||
"archivedChannelMessage": "Estás viendo un **canal archivado**. No serán publicados nuevos mensajes.",
|
||||
@@ -17,7 +17,7 @@
|
||||
"channel_header.addMembers": "Agregar Miembros",
|
||||
"channel_header.directchannel.you": "{displayname} (tu) ",
|
||||
"channel_header.manageMembers": "Administrar Miembros",
|
||||
"channel_header.pinnedPosts": "Mensajes Anclados",
|
||||
"channel_header.pinnedPosts": "Mensajes Destacados",
|
||||
"channel_header.viewMembers": "Ver Miembros",
|
||||
"channel_info.header": "Encabezado:",
|
||||
"channel_info.purpose": "Propósito:",
|
||||
@@ -35,6 +35,9 @@
|
||||
"channel_modal.purposeEx": "Ej: \"Un canal para describir errores y mejoras\"",
|
||||
"channel_notifications.ignoreChannelMentions.settings": "Ignorar @channel, @here, @all",
|
||||
"channel_notifications.muteChannel.settings": "Silenciar canal",
|
||||
"channel.channelHasGuests": "Este canal tiene huéspedes",
|
||||
"channel.hasGuests": "Este grupo de mensajes tiene huéspedes",
|
||||
"channel.isGuest": "Esta persona es un huésped",
|
||||
"combined_system_message.added_to_channel.many_expanded": "{users} y {lastUser} fueron **agregados al canal** por {actor}.",
|
||||
"combined_system_message.added_to_channel.one": "{firstUser} **agregado al canal** por {actor}.",
|
||||
"combined_system_message.added_to_channel.one_you": "Fuiste **agregado al canal** por {actor}.",
|
||||
@@ -45,19 +48,19 @@
|
||||
"combined_system_message.added_to_team.two": "{firstUser} y {secondUser} **agregados al equipo** por {actor}.",
|
||||
"combined_system_message.joined_channel.many_expanded": "{users} and {lastUser} **se unieron al canal**.",
|
||||
"combined_system_message.joined_channel.one": "{firstUser} **se unió al canal**.",
|
||||
"combined_system_message.joined_channel.one_you": "**unieron al canal**.",
|
||||
"combined_system_message.joined_channel.one_you": "Tú **te uniste al canal**.",
|
||||
"combined_system_message.joined_channel.two": "{firstUser} y {secondUser} **se unieron al canal**.",
|
||||
"combined_system_message.joined_team.many_expanded": "{users} y {lastUser} **se unieron al equipo**.",
|
||||
"combined_system_message.joined_team.one": "{firstUser} **se unió al equipo**.",
|
||||
"combined_system_message.joined_team.one_you": "**unieron al equipo**.",
|
||||
"combined_system_message.joined_team.one_you": "Tú **te uniste al equipo**.",
|
||||
"combined_system_message.joined_team.two": "{firstUser} y {secondUser} **se unieron al equipo**.",
|
||||
"combined_system_message.left_channel.many_expanded": "{users} y {lastUser} **abandonaron el canal**.",
|
||||
"combined_system_message.left_channel.one": "{firstUser} **abandonó el canal**.",
|
||||
"combined_system_message.left_channel.one_you": "**abandonaron el canal**.",
|
||||
"combined_system_message.left_channel.one_you": "Tú **abandonaste el canal**.",
|
||||
"combined_system_message.left_channel.two": "{firstUser} y {secondUser} **abandonaron el canal**.",
|
||||
"combined_system_message.left_team.many_expanded": "{users} y {lastUser} **abandonaron el equipo**.",
|
||||
"combined_system_message.left_team.one": "{firstUser} **abandonó el equipo**.",
|
||||
"combined_system_message.left_team.one_you": "**abandonó el equipo**.",
|
||||
"combined_system_message.left_team.one_you": "Tú **abandonaste el equipo**.",
|
||||
"combined_system_message.left_team.two": "{firstUser} y {secondUser} **abandonaron el equipo**.",
|
||||
"combined_system_message.removed_from_channel.many_expanded": "{users} y {lastUser} fueron **eliminados del canal**.",
|
||||
"combined_system_message.removed_from_channel.one": "{firstUser} fue **eliminado del canal**.",
|
||||
@@ -70,21 +73,21 @@
|
||||
"combined_system_message.you": "Tu",
|
||||
"create_comment.addComment": "Agregar un comentario...",
|
||||
"create_post.deactivated": "Estás viendo un canal archivado con un usuario desactivado.",
|
||||
"create_post.write": "Write to {channelDisplayName}",
|
||||
"create_post.write": "Escribir en {channelDisplayName}",
|
||||
"date_separator.today": "Hoy",
|
||||
"date_separator.yesterday": "Ayer",
|
||||
"edit_post.editPost": "Editar el mensaje...",
|
||||
"edit_post.save": "Guardar",
|
||||
"error.team_not_found.title": "Equipo no encontrado",
|
||||
"file_attachment.download": "Descargar",
|
||||
"file_upload.fileAbove": "No se puede cargar un archivo de más de {max}MB: {filename}",
|
||||
"get_post_link_modal.title": "Copiar enlace Permanente",
|
||||
"get_post_link_modal.title": "Copiar Enlace",
|
||||
"integrations.add": "Agregar",
|
||||
"intro_messages.anyMember": " Cualquier miembro se puede unir y leer este canal.",
|
||||
"intro_messages.beginning": "Inicio de {name}",
|
||||
"intro_messages.channel": "canal",
|
||||
"intro_messages.creator": "Este es el inicio del {type} {name}, creado por {creator} el {date}.",
|
||||
"intro_messages.group": "canal privado",
|
||||
"intro_messages.creator": "Este es el inicio del canal {name}, creado por {creator} el {date}.",
|
||||
"intro_messages.creatorPrivate": "Este es el inicio del canal privado {name}, creado por {creator} el {date}.",
|
||||
"intro_messages.group_message": "Este es el inicio de tu historial del grupo de mensajes con estos compañeros. Los mensajes y archivos que se comparten aquí no son mostrados a personas fuera de esta área.",
|
||||
"intro_messages.noCreator": "Este es el inicio del {type} {name}, creado el {date}.",
|
||||
"intro_messages.noCreator": "Este es el inicio del canal {name}, creado el {date}.",
|
||||
"intro_messages.onlyInvited": " Sólo miembros invitados pueden ver este canal privado.",
|
||||
"last_users_message.added_to_channel.type": "fueron **agregados al canal** por {actor}.",
|
||||
"last_users_message.added_to_team.type": "fueron **agregados al equipo** por {actor}.",
|
||||
@@ -100,7 +103,7 @@
|
||||
"login_mfa.token": "Token MFA",
|
||||
"login_mfa.tokenReq": "Por favor ingresa un token MFA",
|
||||
"login.email": "Correo electrónico",
|
||||
"login.forgot": "Olvide mi contraseña",
|
||||
"login.forgot": "Olvide mi contraseña.",
|
||||
"login.invalidPassword": "La Contraseña es incorrecta.",
|
||||
"login.ldapUsername": "Usuario AD/LDAP",
|
||||
"login.ldapUsernameLower": "Nombre de Usuario AD/LDAP",
|
||||
@@ -128,7 +131,6 @@
|
||||
"mobile.account_notifications.threads_mentions": "Menciones en hilos",
|
||||
"mobile.account_notifications.threads_start": "Hilos que yo comience",
|
||||
"mobile.account_notifications.threads_start_participate": "Hilos que yo comience o participe",
|
||||
"mobile.account.settings.cancel": "Cancelar",
|
||||
"mobile.account.settings.save": "Guardar",
|
||||
"mobile.action_menu.select": "Selecciona una opción",
|
||||
"mobile.advanced_settings.clockDisplay": "Visualización de la hora",
|
||||
@@ -137,36 +139,44 @@
|
||||
"mobile.advanced_settings.delete_title": "Eliminar Documentos & Datos",
|
||||
"mobile.advanced_settings.timezone": "Zona horaria",
|
||||
"mobile.advanced_settings.title": "Configuración Avanzada",
|
||||
"mobile.android.camera_permission_denied_description": "Para tomar fotos y videos con la cámara, por favor, cambia la configuración de permisos.",
|
||||
"mobile.android.camera_permission_denied_title": "Acceso a la cámara es necesario",
|
||||
"mobile.android.permission_denied_dismiss": "Cerrar",
|
||||
"mobile.android.permission_denied_retry": "Establecer permisos",
|
||||
"mobile.android.photos_permission_denied_description": "Para cargar imágenes desde tu biblioteca, por favor, cambia la configuración de permisos.",
|
||||
"mobile.android.photos_permission_denied_title": "Acceso a la biblioteca de fotos es necesario",
|
||||
"mobile.android.storage_permission_denied_description": "Para cargar imágenes desde tu biblioteca de Android, por favor, cambia la configuración de permisos.",
|
||||
"mobile.android.storage_permission_denied_title": "Acceso a los Archivos es necesario",
|
||||
"mobile.android.videos_permission_denied_description": "Para subir los vídeos de tu biblioteca, por favor, cambia la configuración de permisos.",
|
||||
"mobile.android.videos_permission_denied_title": "Acceso a la biblioteca de vídeos es necesario",
|
||||
"mobile.alert_dialog.alertCancel": "Cancelar",
|
||||
"mobile.android.photos_permission_denied_description": "Subir fotos a tu servidor de Mattermost o guardarlos en el dispositivo. Abre la Configuración y otorga a Mattermost permisos de Lectura y Escritura a tu librería de fotos.",
|
||||
"mobile.android.photos_permission_denied_title": "{applicationName} desea acceder a tus fotos",
|
||||
"mobile.android.videos_permission_denied_description": "Subir videos a tu servidor de Mattermost o guardarlos en el dispositivo. Abre la Configuración y otorga a Mattermost permisos de Lectura y Escritura a tu librería de videos.",
|
||||
"mobile.android.videos_permission_denied_title": "{applicationName} desea acceder a tus videos",
|
||||
"mobile.announcement_banner.title": "Anuncio",
|
||||
"mobile.authentication_error.message": "Mattermost a encontrado un error. Por favor vuelve a autenticar tu usuario para iniciar una nueva sesión.",
|
||||
"mobile.authentication_error.title": "Error de Autenticación",
|
||||
"mobile.calendar.dayNames": "Domingo,Lunes,Martes,Miércoles,Jueves,Viernes,Sábado",
|
||||
"mobile.calendar.dayNamesShort": "Dom,Lun,Mar,Mié,Jue,Vie,Sab",
|
||||
"mobile.calendar.monthNames": "Enero,Febrero,Marzo,Abril,Mayo,Junio,Julio,Agosto,Septiembre,Octubre,Noviembre,Diciembre",
|
||||
"mobile.calendar.monthNamesShort": "Ene,Feb,Mar,Abr,May,Jun,Jul,Ago,Sep,Oct,Nov,Dic",
|
||||
"mobile.camera_photo_permission_denied_description": "Captura fotos y súbelas a tu servidor de Mattermost o guárdalas en tu dispositivo. Abre la Configuración y otorga a Mattermost permisos de Lectura y Escritura para usar la cámara.",
|
||||
"mobile.camera_photo_permission_denied_title": "{applicationName} desea acceder a tu cámara",
|
||||
"mobile.camera_video_permission_denied_description": "Captura videos y súbelos a a tu servidor de Mattermost o guárdalos en tu dispositivo. Abre la Configuración y otorga a Mattermost permisos de Lectura y Escritura para acceder tu cámara.",
|
||||
"mobile.camera_video_permission_denied_title": "{applicationName} desea acceder a tu cámara",
|
||||
"mobile.channel_drawer.search": "Saltar a...",
|
||||
"mobile.channel_info.alertMessageConvertChannel": "Al convertir {displayName} a un canal privado, la historia y la membresía son preservados. Archivos compartidos públicamente permanecerán accesibles para cualquier que tenga el enlace. La membresía en un canal privado es solo por invitación \n\nEste es un cambio permanente y no puede deshacerse.\n\n¿Estás seguro que quieres convertir {displayName} a un canal privado?",
|
||||
"mobile.channel_info.alertMessageDeleteChannel": "¿Seguro quieres archivar el {term} {name}?",
|
||||
"mobile.channel_info.alertMessageLeaveChannel": "¿Seguro quieres abandonar el {term} {name}?",
|
||||
"mobile.channel_info.alertMessageUnarchiveChannel": "¿Seguro quieres restaurar el {term} {name}?",
|
||||
"mobile.channel_info.alertNo": "No",
|
||||
"mobile.channel_info.alertTitleConvertChannel": "¿Convertir {displayName} a un canal privado?",
|
||||
"mobile.channel_info.alertTitleDeleteChannel": "Archivar {term}",
|
||||
"mobile.channel_info.alertTitleLeaveChannel": "Abandonar {term}",
|
||||
"mobile.channel_info.alertTitleUnarchiveChannel": "Restaurar {term}",
|
||||
"mobile.channel_info.alertYes": "Sí",
|
||||
"mobile.channel_info.convert": "Convertir a Canal Privado",
|
||||
"mobile.channel_info.convert_failed": "No se pudo convertir {displayName} a canal privado.",
|
||||
"mobile.channel_info.convert_success": "{displayName} ahora es un canal privado.",
|
||||
"mobile.channel_info.copy_header": "Copiar Encabezado",
|
||||
"mobile.channel_info.copy_purpose": "Copiar Propósito",
|
||||
"mobile.channel_info.delete_failed": "No se pudo archivar el canal {displayName}. Por favor revisa tu conexión e intenta de nuevo.",
|
||||
"mobile.channel_info.edit": "Editar Canal",
|
||||
"mobile.channel_info.privateChannel": "Canal Privado",
|
||||
"mobile.channel_info.publicChannel": "Canal Público",
|
||||
"mobile.channel_info.unarchive_failed": "No se pudo restaurar el canal {displayName}. Por favor revisa tu conexión e intenta de nuevo.",
|
||||
"mobile.channel_list.alertNo": "No",
|
||||
"mobile.channel_list.alertYes": "Sí",
|
||||
"mobile.channel_list.archived": "ARCHIVADO",
|
||||
"mobile.channel_list.channels": "CANALES",
|
||||
"mobile.channel_list.closeDM": "Cerrar Mensaje Directo",
|
||||
"mobile.channel_list.closeGM": "Cerrar Mensaje de Grupo",
|
||||
@@ -174,7 +184,6 @@
|
||||
"mobile.channel_list.not_member": "NO MIEMBRO DE",
|
||||
"mobile.channel_list.unreads": "SIN LEER",
|
||||
"mobile.channel_members.add_members_alert": "Debes seleccionar al menos un miembro a agregar al canal.",
|
||||
"mobile.channel.markAsRead": "Marcar como leído",
|
||||
"mobile.client_upgrade": "Actualizar App",
|
||||
"mobile.client_upgrade.can_upgrade_subtitle": "Una nueva versión está disponible para su descarga.",
|
||||
"mobile.client_upgrade.can_upgrade_title": "Actualización Disponible",
|
||||
@@ -204,6 +213,7 @@
|
||||
"mobile.create_channel.public": "Nuevo Canal Público",
|
||||
"mobile.create_post.read_only": "Este canal es de sólo lectura",
|
||||
"mobile.custom_list.no_results": "Sin resultados",
|
||||
"mobile.display_settings.sidebar": "Barra lateral",
|
||||
"mobile.display_settings.theme": "Tema",
|
||||
"mobile.document_preview.failed_description": "Ocurrió un error al abrir el documento. Por favor verifica que tienes instalado un visor para archivos {fileType} e intenta de nuevo.\n",
|
||||
"mobile.document_preview.failed_title": "Error Abriendo Documento",
|
||||
@@ -211,7 +221,6 @@
|
||||
"mobile.downloader.android_failed": "Descarga fallida",
|
||||
"mobile.downloader.android_permission": "Se necesita acceso al directorio de descargas para guardar los archivos.",
|
||||
"mobile.downloader.android_started": "Descarga iniciada",
|
||||
"mobile.downloader.android_success": "descarga con éxito",
|
||||
"mobile.downloader.complete": "Descarga completa",
|
||||
"mobile.downloader.disabled_description": "La descarga de archivos ha sido desactivada en este servidor. Por favor contacta a tu Administrador de Sistemas para más detalles.\n",
|
||||
"mobile.downloader.disabled_title": "Descarga desactivada",
|
||||
@@ -223,6 +232,7 @@
|
||||
"mobile.drawer.teamsTitle": "Equipos",
|
||||
"mobile.edit_channel": "Guardar",
|
||||
"mobile.edit_post.title": "Editando Mensaje",
|
||||
"mobile.edit_profile.remove_profile_photo": "Quitar Foto",
|
||||
"mobile.emoji_picker.activity": "ACTIVIDAD",
|
||||
"mobile.emoji_picker.custom": "PERSONALIZADO",
|
||||
"mobile.emoji_picker.flags": "BANDERAS",
|
||||
@@ -241,17 +251,24 @@
|
||||
"mobile.extension.file_limit": "Compartir está limitado a un máximo de 5 archivos.",
|
||||
"mobile.extension.max_file_size": "Los archivos a compartir en Mattermost no deben superar los {size}.",
|
||||
"mobile.extension.permission": "Mattermost necesita acceso al almacenamiento del dispositivo para poder compartir archivos.",
|
||||
"mobile.extension.team_required": "Debes pertenecer a un equipo antes de poder compartir archivos.",
|
||||
"mobile.extension.title": "Compartir en Mattermost",
|
||||
"mobile.failed_network_action.description": "Parece haber un problema con tu conexión de internet. Asegura que tienes una conexión activa e intenta de nuevo.",
|
||||
"mobile.failed_network_action.retry": "Intentar de nuevo",
|
||||
"mobile.failed_network_action.shortDescription": "Asegura de tener una conexión activa e intenta de nuevo.",
|
||||
"mobile.failed_network_action.shortDescription": "Los mensajes serán cargados una vez tengas conexión a internet.",
|
||||
"mobile.failed_network_action.teams_channel_description": "Los canales de {teamName} no pudieron ser cargados.",
|
||||
"mobile.failed_network_action.teams_description": "Los Equipos no pudieron ser cargados.",
|
||||
"mobile.failed_network_action.teams_title": "Algo salió mal",
|
||||
"mobile.failed_network_action.title": "Sin conexión a Internet",
|
||||
"mobile.file_upload.browse": "Explorar Archivos",
|
||||
"mobile.file_upload.camera_photo": "Capturar Foto",
|
||||
"mobile.file_upload.camera_video": "Capturar Vídeo",
|
||||
"mobile.file_upload.library": "Librería de Fotos",
|
||||
"mobile.file_upload.max_warning": "Se pueden subir un máximo de 5 archivos.",
|
||||
"mobile.file_upload.unsupportedMimeType": "Sólo pueden ser utilizadas imágenes BMP, JPG o PNG como imagen de perfil.",
|
||||
"mobile.file_upload.video": "Librería de Videos",
|
||||
"mobile.files_paste.error_description": "Ocurrió un error mientras se pegaba(n) archivo(s). Por favor inténtelo de nuevo.",
|
||||
"mobile.files_paste.error_dismiss": "Descartar",
|
||||
"mobile.files_paste.error_title": "Pegar falló",
|
||||
"mobile.flagged_posts.empty_description": "Las banderas son una forma de marcar los mensajes para hacerles seguimiento. Tus banderas son personales, y no puede ser vistas por otros usuarios.",
|
||||
"mobile.flagged_posts.empty_title": "No hay Mensajes Marcados",
|
||||
"mobile.help.title": "Ayuda",
|
||||
@@ -260,7 +277,8 @@
|
||||
"mobile.intro_messages.default_message": "Es es el primer canal que tus compañeros ven cuando se registran - puedes utilizarlo para enviar mensajes que todos deben leer.",
|
||||
"mobile.intro_messages.default_welcome": "¡Bienvenido a {name}!",
|
||||
"mobile.intro_messages.DM": "Este es el inicio de tu historial de mensajes directos con {teammate}.Los mensajes directos y archivos que se comparten aquí no son mostrados a personas fuera de esta área.",
|
||||
"mobile.ios.photos_permission_denied_description": "Para guardar imágenes y vídeos en tu librería, por favor, cambia la configuración de permisos.",
|
||||
"mobile.ios.photos_permission_denied_description": "Sube fotos y videos a tu servidor de Mattermost o guárdalos en tu dispositivo. Abre la Configuración y otorga a Mattermost permisos de Lectura y Escritura para acceder tu librería de fotos y videos.",
|
||||
"mobile.ios.photos_permission_denied_title": "{applicationName} desea acceder a tu librería de fotos",
|
||||
"mobile.join_channel.error": "No pudimos unirnos al canal {displayName}. Por favor revisa tu conexión e intenta de nuevo.",
|
||||
"mobile.loading_channels": "Cargando Canales...",
|
||||
"mobile.loading_members": "Cargando Miembros...",
|
||||
@@ -271,13 +289,17 @@
|
||||
"mobile.managed.blocked_by": "Bloqueado por {vendor}",
|
||||
"mobile.managed.exit": "Salir",
|
||||
"mobile.managed.jailbreak": "{vendor} no confía en los dispositivos con jailbreak, por favor, salga de la aplicación.",
|
||||
"mobile.managed.not_secured.android": "Este dispositivo debe estar asegurado con un bloqueo de pantalla para utilizar Mattermost.",
|
||||
"mobile.managed.not_secured.ios": "Este dispositivo debe estar protegido con un código de acceso para utilizar Mattermost.\n\nVaya a Configuración > Identificación Facial y clave de acceso.",
|
||||
"mobile.managed.not_secured.ios.touchId": "Este dispositivo debe estar protegido con un código de acceso para utilizar Mattermost.\n\nVaya a Configuración > Identificación Táctil y clave de acceso.",
|
||||
"mobile.managed.secured_by": "Asegurado por {vendor}",
|
||||
"mobile.managed.settings": "Ir a configuración",
|
||||
"mobile.markdown.code.copy_code": "Copiar código",
|
||||
"mobile.markdown.code.plusMoreLines": "+{count, number} más {count, plural, one {línea} other {líneas}}",
|
||||
"mobile.markdown.image.too_large": "La imagen excede la dimensión máxima de {maxWidth} x {maxHeight}:",
|
||||
"mobile.markdown.link.copy_url": "Copiar URL",
|
||||
"mobile.mention.copy_mention": "Copiar Mención",
|
||||
"mobile.message_length.message": "El mensaje es demasiado largo. Actual número de caracteres: {max}/{count}",
|
||||
"mobile.message_length.message": "El mensaje es demasiado largo. Actual número de caracteres: {count}/{max}",
|
||||
"mobile.message_length.title": "Longitud del Mensaje",
|
||||
"mobile.more_dms.add_more": "Puedes agregar {remaining, number} usuarios más",
|
||||
"mobile.more_dms.cannot_add_more": "No puedes agregar más usuarios.",
|
||||
@@ -289,16 +311,16 @@
|
||||
"mobile.notice_platform_link": "servidor",
|
||||
"mobile.notice_text": "Mattermost es hecho posible con software de código abierto utilizado en nuestra {platform} y {mobile}.",
|
||||
"mobile.notification_settings_mentions.keywords": "Palabras clave",
|
||||
"mobile.notification_settings_mentions.keywordsDescription": "Otras palabras que desencadenan menciones",
|
||||
"mobile.notification_settings_mentions.keywordsDescription": "Otras palabras que disparan menciones",
|
||||
"mobile.notification_settings_mentions.keywordsHelp": "Las palabras clave son sin distinción de mayúsculas y deben estar separadas por comas.",
|
||||
"mobile.notification_settings_mentions.wordsTrigger": "PALABRAS QUE DESENCADENAN MENCIONES",
|
||||
"mobile.notification_settings_mentions.wordsTrigger": "PALABRAS QUE DISPARAN MENCIONES",
|
||||
"mobile.notification_settings_mobile.default_sound": "Predeterminado ({sound})",
|
||||
"mobile.notification_settings_mobile.light": "Luz",
|
||||
"mobile.notification_settings_mobile.no_sound": "Ninguno",
|
||||
"mobile.notification_settings_mobile.push_activity": "ENVIAR NOTIFICACIONES",
|
||||
"mobile.notification_settings_mobile.push_activity_android": "Enviar notificaciones",
|
||||
"mobile.notification_settings_mobile.push_status": "ACTIVA LAS NOTIFICACIONES A DISPOSITIVOS MÓVILES CUANDO",
|
||||
"mobile.notification_settings_mobile.push_status_android": "Activa las notificaciones a dispositivos móviles cuando",
|
||||
"mobile.notification_settings_mobile.push_status": "DISPARAR NOTIFICACIONES A DISPOSITIVOS MÓVILES CUANDO",
|
||||
"mobile.notification_settings_mobile.push_status_android": "disparar notificaciones a dispositivos móviles cuando",
|
||||
"mobile.notification_settings_mobile.sound": "Sonido",
|
||||
"mobile.notification_settings_mobile.sounds_title": "Sonido de la notificación",
|
||||
"mobile.notification_settings_mobile.test": "Notificación de prueba",
|
||||
@@ -307,7 +329,7 @@
|
||||
"mobile.notification_settings.auto_responder_short": "Respuestas Automáticas",
|
||||
"mobile.notification_settings.auto_responder.default_message": "Hola, actualmente estoy fuera de la oficina y no puedo responder mensajes.",
|
||||
"mobile.notification_settings.auto_responder.enabled": "Activada",
|
||||
"mobile.notification_settings.auto_responder.footer_message": "Asigna un mensaje personalizado que será enviado automáticamente en respuesta a Mensajes Directos. Las menciones en canales Públicos y Privados no enviarán una respuesta automática. Al habilitar la Respuestas Automáticas tu estado será Fuera de Oficina y apagará las notificaciones por correo electrónico y a dispositivos móviles.",
|
||||
"mobile.notification_settings.auto_responder.footer_message": "Asigna un mensaje personalizado que será enviado automáticamente en respuesta a Mensajes Directos. Las menciones en canales Públicos y Privados no dispararán una respuesta automática. Al habilitar la Respuestas Automáticas tu estado será Fuera de Oficina y apagará las notificaciones por correo electrónico y a dispositivos móviles.",
|
||||
"mobile.notification_settings.auto_responder.message_placeholder": "Mensaje",
|
||||
"mobile.notification_settings.auto_responder.message_title": "MENSAJE PERSONALIZADO",
|
||||
"mobile.notification_settings.email": "Correo electrónico",
|
||||
@@ -332,20 +354,28 @@
|
||||
"mobile.open_dm.error": "No pudimos abrir el canal de mensajes directos con {displayName}. Por favor revisa tu conexión e intenta de nuevo.",
|
||||
"mobile.open_gm.error": "No pudimos abrir el canal del grupo con esos usuarios. Por favor revisa tu conexión e intenta de nuevo.",
|
||||
"mobile.open_unknown_channel.error": "No se pudo unir al canal. Por favor elimina el cache e intenta de nuevo.",
|
||||
"mobile.pinned_posts.empty_description": "Ancla elementos importantes manteniendo pulsado cualquier mensaje y selecciona la opción \"Anclar al Canal\".",
|
||||
"mobile.pinned_posts.empty_title": "No hay Mensajes Anclados",
|
||||
"mobile.permission_denied_dismiss": "No Permitir",
|
||||
"mobile.permission_denied_retry": "Ajustes",
|
||||
"mobile.photo_library_permission_denied_description": "Para guardar imágenes y vídeos en tu librería, por favor, cambia la configuración de permisos.",
|
||||
"mobile.photo_library_permission_denied_title": "{applicationName} desea acceder a tu librería de fotos",
|
||||
"mobile.pinned_posts.empty_description": "Destaca elementos importantes manteniendo pulsado cualquier mensaje y selecciona la opción \"Destacar\".",
|
||||
"mobile.pinned_posts.empty_title": "No hay Mensajes Destacados",
|
||||
"mobile.post_info.add_reaction": "Reaccionar",
|
||||
"mobile.post_info.copy_text": "Copiar Texto",
|
||||
"mobile.post_info.flag": "Marcar",
|
||||
"mobile.post_info.pin": "Anclar al Canal",
|
||||
"mobile.post_info.mark_unread": "Marcar No leído",
|
||||
"mobile.post_info.pin": "Destacar",
|
||||
"mobile.post_info.reply": "Responder",
|
||||
"mobile.post_info.unflag": "Desmarcar",
|
||||
"mobile.post_info.unpin": "Desprender del Canal",
|
||||
"mobile.post_info.unpin": "No Destacar",
|
||||
"mobile.post_pre_header.flagged": "Marcado",
|
||||
"mobile.post_pre_header.pinned": "Anclado",
|
||||
"mobile.post_pre_header.pinned_flagged": "Anclado y Marcado",
|
||||
"mobile.post_textbox.empty.message": "Estas intentando enviar un mensaje vacío.\nPor favor asegurate de estar enviando un mensaje con texto o con al menos un archivo adjunto.",
|
||||
"mobile.post_textbox.empty.ok": "Aceptar",
|
||||
"mobile.post_textbox.empty.title": "Mensaje vacío",
|
||||
"mobile.post_pre_header.pinned": "Destacado",
|
||||
"mobile.post_pre_header.pinned_flagged": "Destacado y Marcado",
|
||||
"mobile.post_textbox.entire_channel.cancel": "Cancelar",
|
||||
"mobile.post_textbox.entire_channel.confirm": "Confirmar",
|
||||
"mobile.post_textbox.entire_channel.message": "Al utilizar @all ó @channel estás a punto de enviar notificaciones a {totalMembers, number} {totalMembers, plural, one {persona} other {personas}}. ¿Estás seguro de querer hacer esto?",
|
||||
"mobile.post_textbox.entire_channel.message.with_timezones": "Al utilizar @all ó @channel estás a punto de enviar notificaciones a {totalMembers, number} {totalMembers, plural, one {persona} other {personas}} en {timezones, number} {zonas horarias, plural, one {zona horaria} other {zonas horarias}}. ¿Estás seguro de querer hacer esto?",
|
||||
"mobile.post_textbox.entire_channel.title": "Confirma el envío de notificaciones a todo el canal",
|
||||
"mobile.post_textbox.uploadFailedDesc": "Algunos archivos adjuntos no se han subido al servidor. ¿Quieres publicar el mensaje?",
|
||||
"mobile.post_textbox.uploadFailedTitle": "Error Adjuntando",
|
||||
"mobile.post.cancel": "Cancelar",
|
||||
@@ -356,7 +386,12 @@
|
||||
"mobile.post.failed_title": "No se pudo enviar el mensaje",
|
||||
"mobile.post.retry": "Actualizar",
|
||||
"mobile.posts_view.moreMsg": "Más Mensajes Arriba",
|
||||
"mobile.recent_mentions.empty_description": "Mensajes que contienen tu nombre de usuario u otras palabras que desencadenan menciones aparecerán aquí.",
|
||||
"mobile.privacy_link": "Política de Privacidad",
|
||||
"mobile.push_notification_reply.button": "Enviar",
|
||||
"mobile.push_notification_reply.placeholder": "Escribe una respuesta...",
|
||||
"mobile.push_notification_reply.title": "Responder",
|
||||
"mobile.reaction_header.all_emojis": "Todos",
|
||||
"mobile.recent_mentions.empty_description": "Mensajes que contienen tu nombre de usuario u otras palabras que disparan menciones aparecerán aquí.",
|
||||
"mobile.recent_mentions.empty_title": "No hay Menciones recientes",
|
||||
"mobile.rename_channel.display_name_maxLength": "El nombre del canal debe tener menos de {maxLength, number} caracteres",
|
||||
"mobile.rename_channel.display_name_minLength": "El nombre del canal debe ser de {minLength, number} o más caracteres",
|
||||
@@ -378,6 +413,8 @@
|
||||
"mobile.routes.channelInfo.createdBy": "Creado por {creator} el ",
|
||||
"mobile.routes.channelInfo.delete_channel": "Archivar Canal",
|
||||
"mobile.routes.channelInfo.favorite": "Favorito",
|
||||
"mobile.routes.channelInfo.groupManaged": "Los miembros son gestionados por grupos enlazados",
|
||||
"mobile.routes.channelInfo.unarchive_channel": "Restaurar Canal",
|
||||
"mobile.routes.code": "Código {language}",
|
||||
"mobile.routes.code.noLanguage": "Código",
|
||||
"mobile.routes.edit_profile": "Editar Perfil",
|
||||
@@ -393,6 +430,7 @@
|
||||
"mobile.routes.thread": "Hilo en {channelName}",
|
||||
"mobile.routes.thread_dm": "Hilo de Mensaje Directo",
|
||||
"mobile.routes.user_profile": "Perfil",
|
||||
"mobile.routes.user_profile.edit": "Editar",
|
||||
"mobile.routes.user_profile.local_time": "HORA LOCAL",
|
||||
"mobile.routes.user_profile.send_message": "Enviar Mensaje",
|
||||
"mobile.search.after_modifier_description": "encontrar mensajes después de una fecha específica",
|
||||
@@ -405,13 +443,20 @@
|
||||
"mobile.search.no_results": "No se han encontrado resultados",
|
||||
"mobile.search.on_modifier_description": "encontrar mensajes de una fecha específica",
|
||||
"mobile.search.recent_title": "Búsquedas recientes",
|
||||
"mobile.select_team.guest_cant_join_team": "Tu cuenta de huésped no tiene equipos o canales asignados. Por favor contacta a un administrador.",
|
||||
"mobile.select_team.join_open": "Equipos a los que te puedes unir",
|
||||
"mobile.select_team.no_teams": "No hay equipos disponibles a los que te puedas unir.",
|
||||
"mobile.server_link.error.text": "No se pudo encontrar el enlace en este servidor.",
|
||||
"mobile.server_link.error.title": "Error de enlace",
|
||||
"mobile.server_link.unreachable_channel.error": "Este enlace pertenece a un canal eliminado o a un canal al cual no tienes acceso.",
|
||||
"mobile.server_link.unreachable_team.error": "Este enlace pertenece a un equipo eliminado o a un equipo al cual no tienes acceso.",
|
||||
"mobile.server_ssl.error.text": "El certificado de {host} no es confiable.\n\nPor favor contacta a tu Administrador de Sistema para resolver los problemas con el certificado y permitir la conexión a este servidor.",
|
||||
"mobile.server_ssl.error.title": "Certificado no confiable",
|
||||
"mobile.server_upgrade.button": "Aceptar",
|
||||
"mobile.server_upgrade.description": "\nEs necesaria una actualización del servidor para utilizar la aplicación de Mattermost. Por favor, preguntale a tu Administrador del Sistema para obtener más detalles.\n",
|
||||
"mobile.server_upgrade.title": "Es necesario actualizar el Servidor",
|
||||
"mobile.server_url.invalid_format": "URL debe comenzar con http:// o https://",
|
||||
"mobile.session_expired": "Sesión Caducada: Por favor, inicia sesión para seguir recibiendo notificaciones.",
|
||||
"mobile.session_expired": "Sesión expirada: Inicia sesión para continuar recibiendo notificaciones. Las sesiones para {siteName} están configuradas para caducar cada {daysCount, number} {daysCount, plural, un {día} other {días}}.",
|
||||
"mobile.set_status.away": "Ausente",
|
||||
"mobile.set_status.dnd": "No Molestar",
|
||||
"mobile.set_status.offline": "Desconectado",
|
||||
@@ -422,16 +467,30 @@
|
||||
"mobile.share_extension.error_message": "Ocurrió un error utilizando la extensión para compartir.",
|
||||
"mobile.share_extension.error_title": "Error en la Extensión",
|
||||
"mobile.share_extension.team": "Equipo",
|
||||
"mobile.share_extension.too_long_message": "Número de letras: {count}/{max}",
|
||||
"mobile.share_extension.too_long_title": "Mensaje es un largo",
|
||||
"mobile.sidebar_settings.permanent": "Barra lateral permanente",
|
||||
"mobile.sidebar_settings.permanent_description": "Mantiene la barra lateral abierta permanentemente",
|
||||
"mobile.storage_permission_denied_description": "Sube archivos a tu servidor de Mattermost. Abre la Configuración para otorgar permisos de Lectura y Escritura para acceder archivos en este dispositivo.",
|
||||
"mobile.storage_permission_denied_title": "{applicationName} desea acceder a tus archivos",
|
||||
"mobile.suggestion.members": "Miembros",
|
||||
"mobile.system_message.channel_archived_message": "{username} archivó el canal",
|
||||
"mobile.system_message.channel_unarchived_message": "{username} restauró el canal",
|
||||
"mobile.system_message.update_channel_displayname_message_and_forget.updated_from": "{username} actualizó el nombre del canal de: {oldDisplayName} a: {newDisplayName}",
|
||||
"mobile.system_message.update_channel_header_message_and_forget.removed": "{username} eliminó el encabezado del canal (era: {oldHeader})",
|
||||
"mobile.system_message.update_channel_header_message_and_forget.updated_from": "{username} ha actualizado el encabezado del canal de: {oldHeader} a: {newHeader}",
|
||||
"mobile.system_message.update_channel_header_message_and_forget.updated_to": "{username} ha actualizado el encabezado del canal a: {newHeader}",
|
||||
"mobile.system_message.update_channel_purpose_message.removed": "{username} eliminó el propósito del canal (era: {oldPurpose})",
|
||||
"mobile.system_message.update_channel_purpose_message.updated_from": "{username} ha actualizado el propósito del canal de: {oldPurpose} a: {newPurpose}",
|
||||
"mobile.system_message.update_channel_purpose_message.updated_to": "{username} ha actualizado el propósito del canal a: {newPurpose}",
|
||||
"mobile.terms_of_service.alert_cancel": "Cancelar",
|
||||
"mobile.terms_of_service.alert_ok": "Aceptar",
|
||||
"mobile.terms_of_service.alert_retry": "Intentar de nuevo",
|
||||
"mobile.terms_of_service.get_terms_error_description": "Asegúrate de tener una conexión activa a internet e inténtalo de nuevo. Si este problema persiste, ponte en contacto con tu Administrador del Sistema.",
|
||||
"mobile.terms_of_service.get_terms_error_title": "No se pueden cargar los términos de servicio.",
|
||||
"mobile.terms_of_service.terms_rejected": "Debes aceptar los términos de servicio antes de acceder a {siteName}. Por favor, ponte en contacto con el Administrador del Sistema para obtener más detalles.",
|
||||
"mobile.timezone_settings.automatically": "Asignar automáticamente",
|
||||
"mobile.timezone_settings.manual": "Cambiar zona horaria",
|
||||
"mobile.timezone_settings.select": "Seleccione la zona horaria",
|
||||
"mobile.tos_link": "Términos de Servicio",
|
||||
"mobile.user_list.deactivated": "Desactivado",
|
||||
"mobile.user.settings.notifications.email.fifteenMinutes": "Cada 15 minutos",
|
||||
"mobile.video_playback.failed_description": "Ocurrió un error al reproducir el vídeo.\n",
|
||||
@@ -445,11 +504,17 @@
|
||||
"modal.manual_status.auto_responder.message_dnd": "¿Quieres cambiar to estado a \"No Molestar\" e inhabilitar las respuestas automáticas?",
|
||||
"modal.manual_status.auto_responder.message_offline": "¿Quieres cambiar to estado a \"Desconectado\" e inhabilitar las respuestas automáticas?",
|
||||
"modal.manual_status.auto_responder.message_online": "¿Quieres cambiar to estado a \"En línea\" e inhabilitar las respuestas automáticas?",
|
||||
"more_channels.archivedChannels": "Canales Archivados",
|
||||
"more_channels.dropdownTitle": "Mostrar",
|
||||
"more_channels.noMore": "No hay más canales para unirse",
|
||||
"more_channels.publicChannels": "Canales Públicos",
|
||||
"more_channels.showArchivedChannels": "Mostrar: Canales Archivados",
|
||||
"more_channels.showPublicChannels": "Mostrar: Canales Públicos",
|
||||
"more_channels.title": "Más Canales",
|
||||
"msg_typing.areTyping": "{users} y {last} están escribiendo...",
|
||||
"msg_typing.isTyping": "{user} está escribiendo...",
|
||||
"navbar_dropdown.logout": "Cerrar sesión",
|
||||
"navbar.channel_drawer.button": "Canales y equipos",
|
||||
"navbar.channel_drawer.hint": "Abrir la lista de canales y equipos",
|
||||
"navbar.leave": "Abandonar Canal",
|
||||
"password_form.title": "Restablecer Contraseña",
|
||||
"password_send.checkInbox": "Por favor revisa tu bandeja de entrada.",
|
||||
@@ -458,25 +523,26 @@
|
||||
"password_send.link": "Si la cuenta existe, una correo electrónico de reinicio de contraseña será enviado a:",
|
||||
"password_send.reset": "Restablecer mi contraseña",
|
||||
"permalink.error.access": "El Enlace permanente pertenece a un mensaje eliminado o a un canal al cual no tienes acceso.",
|
||||
"permalink.error.link_not_found": "Enlace no encontrado",
|
||||
"post_body.check_for_out_of_channel_groups_mentions.message": "no fueron notificados por esta mención porque no se encuentra en este canal. No pueden ser agregados al canal porque no son miembros de los grupos enlazados. Para agregarlos a este canal, deben ser agregados a alguno de los grupos enlazados.",
|
||||
"post_body.check_for_out_of_channel_mentions.link.and": " y ",
|
||||
"post_body.check_for_out_of_channel_mentions.link.private": "agregarlos a este canal privado",
|
||||
"post_body.check_for_out_of_channel_mentions.link.public": "agregarlos al canal",
|
||||
"post_body.check_for_out_of_channel_mentions.message_last": "? Tendrán acceso al historial de mensajes.",
|
||||
"post_body.check_for_out_of_channel_mentions.message.multiple": "fueron mencionados pero no son parte de este canal. Quieres ",
|
||||
"post_body.check_for_out_of_channel_mentions.message.one": "fue mencionado pero no es parte de este canal. Quieres ",
|
||||
"post_body.check_for_out_of_channel_mentions.message.multiple": "no fueron notificados por esta mención porque no se encuentran en el canal. Quieres ",
|
||||
"post_body.check_for_out_of_channel_mentions.message.one": "no fue notificado por esta mención porque no se encuentra en el canal. Quieres ",
|
||||
"post_body.commentedOn": "Comento en el mensaje de {name}: ",
|
||||
"post_body.deleted": "(mensaje eliminado)",
|
||||
"post_info.auto_responder": "RESPUESTA AUTOMÁTICA",
|
||||
"post_info.bot": "BOT",
|
||||
"post_info.del": "Borrar",
|
||||
"post_info.edit": "Editar",
|
||||
"post_info.message.show_less": "Ver Menos",
|
||||
"post_info.message.show_more": "Ver Más",
|
||||
"post_info.guest": "HUÉSPEDES",
|
||||
"post_info.message.show_less": "Ver menos",
|
||||
"post_info.message.show_more": "Ver más",
|
||||
"post_info.system": "Sistema",
|
||||
"post_message_view.edited": "(editado)",
|
||||
"posts_view.newMsg": "Nuevos Mensajes",
|
||||
"rename_channel.handleHolder": "Debe tener caracteres alfanuméricos y en minúscula",
|
||||
"rename_channel.url": "URL",
|
||||
"rhs_thread.rootPostDeletedMessage.body": "Parte de esta conversación ha sido eliminada debido a la política de retención de datos. No se puede seguir respondiendo a esta conversación.",
|
||||
"search_bar.search": "Buscar",
|
||||
"search_header.results": "Resultados de la Búsqueda",
|
||||
@@ -498,20 +564,22 @@
|
||||
"status_dropdown.set_offline": "Desconectado",
|
||||
"status_dropdown.set_online": "En línea",
|
||||
"status_dropdown.set_ooo": "Fuera de Oficina",
|
||||
"suggestion.mention.all": "PRECAUCIÓN: Esto menciona a todos los usuarios en el canal",
|
||||
"suggestion.mention.channel": "Notifica a todas las personas en el canal",
|
||||
"suggestion.mention.all": "Notifica a todas las personas en este canal",
|
||||
"suggestion.mention.channel": "Notifica a todas las personas en este canal",
|
||||
"suggestion.mention.channels": "Mis Canales",
|
||||
"suggestion.mention.here": "Notifica a todos en el canal que estén en línea",
|
||||
"suggestion.mention.here": "Notifica a todas las personas disponibles en este canal",
|
||||
"suggestion.mention.members": "Miembros del Canal",
|
||||
"suggestion.mention.morechannels": "Otros Canales",
|
||||
"suggestion.mention.nonmembers": "No en el Canal",
|
||||
"suggestion.mention.special": "Menciones especiales",
|
||||
"suggestion.mention.you": "(tú)",
|
||||
"suggestion.search.direct": "Mensajes Directos",
|
||||
"suggestion.search.private": "Canales Privados",
|
||||
"suggestion.search.public": "Canales Públicos",
|
||||
"terms_of_service.agreeButton": "Acepto",
|
||||
"terms_of_service.api_error": "No se puede completar la solicitud. Si el problema persiste, contacta a tu Administrador de Sistema.",
|
||||
"user.settings.display.clockDisplay": "Visualización del Reloj",
|
||||
"user.settings.display.custom_theme": "Tema Personalizado",
|
||||
"user.settings.display.militaryClock": "Reloj de 24 horas (ejemplo: 16:00)",
|
||||
"user.settings.display.normalClock": "Reloj de 12 horas (ejemplo: 4:00 pm)",
|
||||
"user.settings.display.preferTime": "Selecciona como prefieres mostrar la hora.",
|
||||
@@ -538,7 +606,7 @@
|
||||
"user.settings.notifications.email.immediately": "Inmediatamente",
|
||||
"user.settings.notifications.email.never": "Nunca",
|
||||
"user.settings.notifications.email.send": "Enviar notificaciones de correo electrónico",
|
||||
"user.settings.notifications.emailInfo": "El correo electrónico que se envía para notificaciones de menciones y mensajes directos cuando estás ausente o desconectado de {siteName} por más de 5 minutos.",
|
||||
"user.settings.notifications.emailInfo": "El correo electrónico que se envía para notificaciones de menciones y mensajes directos cuando estás ausente o desconectado por más de 5 minutos.",
|
||||
"user.settings.notifications.never": "Nunca",
|
||||
"user.settings.notifications.onlyMentions": "Sólo para menciones y mensajes directos",
|
||||
"user.settings.push_notification.away": "Ausente o desconectado",
|
||||
@@ -547,4 +615,4 @@
|
||||
"user.settings.push_notification.offline": "Desconectado",
|
||||
"user.settings.push_notification.online": "En línea, ausente o desconectado",
|
||||
"web.root.signup_info": "Todas las comunicaciones del equipo en un sólo lugar, con búsquedas y accesible desde cualquier parte"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"about.teamEditionSt": "Toute la communication de votre équipe en un seul endroit, consultable instantanément et accessible de partout.",
|
||||
"about.teamEditiont0": "Édition Team",
|
||||
"about.teamEditiont1": "Édition Entreprise",
|
||||
"about.title": "À propos de Mattermost",
|
||||
"about.title": "A propos de {appTitle}",
|
||||
"announcment_banner.dont_show_again": "Ne plus montrer",
|
||||
"api.channel.add_member.added": "{addedUsername} a été ajouté au canal par {username}.",
|
||||
"archivedChannelMessage": "Vous consultez un **canal archivé**. Vous ne pouvez pas publier de nouveaux messages.",
|
||||
@@ -35,6 +35,9 @@
|
||||
"channel_modal.purposeEx": "Ex. : « Un canal pour rapporter des bogues ou des améliorations »",
|
||||
"channel_notifications.ignoreChannelMentions.settings": "Ignorer @channel, @here et @all",
|
||||
"channel_notifications.muteChannel.settings": "Mettre le canal en sourdine",
|
||||
"channel.channelHasGuests": "The canal dispose d'utilisateurs invités",
|
||||
"channel.hasGuests": "Ce groupe dispose d'utilisateurs invités",
|
||||
"channel.isGuest": "Cet utilisateur est un utilisateur invité",
|
||||
"combined_system_message.added_to_channel.many_expanded": "{users} et {lastUser} ont été **ajoutés au canal** par {actor}.",
|
||||
"combined_system_message.added_to_channel.one": "{firstUser} **ajouté au canal** par {actor}.",
|
||||
"combined_system_message.added_to_channel.one_you": "Vous avez été **ajouté au canal** par {actor}.",
|
||||
@@ -45,19 +48,19 @@
|
||||
"combined_system_message.added_to_team.two": "{firstUser} et {secondUser} ont été **ajoutés à l'équipe** par {actor}.",
|
||||
"combined_system_message.joined_channel.many_expanded": "{users} et {lastUser} ont **rejoint le canal**.",
|
||||
"combined_system_message.joined_channel.one": "{firstUser} a **rejoint le canal**.",
|
||||
"combined_system_message.joined_channel.one_you": "a **rejoint le canal**.",
|
||||
"combined_system_message.joined_channel.one_you": "Vous avez **rejoint le canal**.",
|
||||
"combined_system_message.joined_channel.two": "{firstUser} et {secondUser} ont **rejoint le canal**.",
|
||||
"combined_system_message.joined_team.many_expanded": "{users} et {lastUser} ont **rejoint l'équipe**.",
|
||||
"combined_system_message.joined_team.one": "{firstUser} a **rejoint l'équipe**.",
|
||||
"combined_system_message.joined_team.one_you": "a **rejoint l'équipe**.",
|
||||
"combined_system_message.joined_team.one_you": "Vous avez **rejoint l'équipe**.",
|
||||
"combined_system_message.joined_team.two": "{firstUser} et {secondUser} ont **rejoint l'équipe**.",
|
||||
"combined_system_message.left_channel.many_expanded": "{users} et {lastUser} ont **quitté le canal**.",
|
||||
"combined_system_message.left_channel.one": "{firstUser} a **quitté le canal**.",
|
||||
"combined_system_message.left_channel.one_you": "a **quitté le canal**.",
|
||||
"combined_system_message.left_channel.one_you": "Vous avez **quitté le canal**.",
|
||||
"combined_system_message.left_channel.two": "{firstUser} et {secondUser} ont **quitté le canal**.",
|
||||
"combined_system_message.left_team.many_expanded": "{users} et {lastUser} ont **quitté l'équipe**.",
|
||||
"combined_system_message.left_team.one": "{firstUser} a **quitté l'équipe**.",
|
||||
"combined_system_message.left_team.one_you": "a **quitté l'équipe**.",
|
||||
"combined_system_message.left_team.one_you": "Vous avez **quitté l'équipe**.",
|
||||
"combined_system_message.left_team.two": "{firstUser} et {secondUser} ont **quitté l'équipe**.",
|
||||
"combined_system_message.removed_from_channel.many_expanded": "{users} et {lastUser} ont été **retirés du canal**.",
|
||||
"combined_system_message.removed_from_channel.one": "{firstUser} a été **retiré du canal**.",
|
||||
@@ -70,21 +73,21 @@
|
||||
"combined_system_message.you": "Vous",
|
||||
"create_comment.addComment": "Commenter...",
|
||||
"create_post.deactivated": "Ceci est un canal de messages personnels archivé contenant une discussion avec un utilisateur désactivé.",
|
||||
"create_post.write": "Write to {channelDisplayName}",
|
||||
"create_post.write": "Écrire à {channelDisplayName}",
|
||||
"date_separator.today": "Aujourd'hui",
|
||||
"date_separator.yesterday": "Hier",
|
||||
"edit_post.editPost": "Modifier le message...",
|
||||
"edit_post.save": "Enregistrer",
|
||||
"error.team_not_found.title": "Équipe introuvable",
|
||||
"file_attachment.download": "Télécharger",
|
||||
"file_upload.fileAbove": "Le fichier plus grand que {max}Mo ne peut pas être téléchargé : {filename}",
|
||||
"get_post_link_modal.title": "Copier le lien permanent",
|
||||
"get_post_link_modal.title": "Copier le lien",
|
||||
"integrations.add": "Ajouter",
|
||||
"intro_messages.anyMember": " Tout membre peut rejoindre et lire ce canal.",
|
||||
"intro_messages.beginning": "Début de {name}",
|
||||
"intro_messages.channel": "canal",
|
||||
"intro_messages.creator": "Ceci est le début de {type} {name}, créé par {creator} le {date}.",
|
||||
"intro_messages.group": "canal privé",
|
||||
"intro_messages.creator": "Ceci est le début du canal {name}, créé par {creator} le {date}.",
|
||||
"intro_messages.creatorPrivate": "Ceci est le début du canal privé {name}, créé par {creator} le {date}.",
|
||||
"intro_messages.group_message": "Vous êtes au début de votre historique de messages de groupe avec ces utilisateurs. Les messages privés et les fichiers partagés ici ne sont pas visibles par les autres utilisateurs.",
|
||||
"intro_messages.noCreator": "Ceci est le début de {name} {type}, créé le {date}.",
|
||||
"intro_messages.noCreator": "Ceci est le début du canal {name}, créé le {date}.",
|
||||
"intro_messages.onlyInvited": " Seuls les membres invités peuvent voir ce canal privé.",
|
||||
"last_users_message.added_to_channel.type": "a été **ajouté au canal** par {actor}.",
|
||||
"last_users_message.added_to_team.type": "a été **ajouté à l'équipe** par {actor}.",
|
||||
@@ -97,10 +100,10 @@
|
||||
"last_users_message.removed_from_channel.type": "ont été **retirés de ce canal**.",
|
||||
"last_users_message.removed_from_team.type": "ont été **retirés de cette équipe**.",
|
||||
"login_mfa.enterToken": "Pour terminer le processus de connexion, veuillez spécifier le jeton apparaissant dans l'application d'authentification de votre smartphone.",
|
||||
"login_mfa.token": "Jeton MFA",
|
||||
"login_mfa.token": "Jeton d'authentification multi-facteurs (MFA)",
|
||||
"login_mfa.tokenReq": "Veuillez spécifier un jeton d'authentification multi-facteurs (MFA)",
|
||||
"login.email": "Adresse e-mail",
|
||||
"login.forgot": "Mot de passe perdu",
|
||||
"login.forgot": "J'ai perdu mon mot de passe.",
|
||||
"login.invalidPassword": "Votre mot de passe est incorrect.",
|
||||
"login.ldapUsername": "Nom d’utilisateur AD/LDAP",
|
||||
"login.ldapUsernameLower": "Nom d’utilisateur AD/LDAP",
|
||||
@@ -128,7 +131,6 @@
|
||||
"mobile.account_notifications.threads_mentions": "Mentions dans les fils de discussion",
|
||||
"mobile.account_notifications.threads_start": "Fils de discussion que je démarre",
|
||||
"mobile.account_notifications.threads_start_participate": "Fils de discussion que je démarre ou auxquels je participe",
|
||||
"mobile.account.settings.cancel": "Annuler",
|
||||
"mobile.account.settings.save": "Enregistrer",
|
||||
"mobile.action_menu.select": "Sélectionnez une option",
|
||||
"mobile.advanced_settings.clockDisplay": "Affichage de l'horloge",
|
||||
@@ -137,36 +139,44 @@
|
||||
"mobile.advanced_settings.delete_title": "Supprimer les documents et les données",
|
||||
"mobile.advanced_settings.timezone": "Fuseau horaire",
|
||||
"mobile.advanced_settings.title": "Paramètres avancés",
|
||||
"mobile.android.camera_permission_denied_description": "Pour prendre des photos et des vidéos avec votre appareil photo, veuillez modifier vos paramètres d'autorisation.",
|
||||
"mobile.android.camera_permission_denied_title": "L'accès à la caméra est requis",
|
||||
"mobile.android.permission_denied_dismiss": "Rejeter",
|
||||
"mobile.android.permission_denied_retry": "Définir les permissions",
|
||||
"mobile.android.photos_permission_denied_description": "Pour envoyer des images à partir de votre bibliothèque, veuillez modifier vos paramètres d'autorisation.",
|
||||
"mobile.android.photos_permission_denied_title": "L'accès à la bibliothèque de photos est requis",
|
||||
"mobile.android.storage_permission_denied_description": "Pour envoyer des images à partir de votre appareil Android, veuillez modifier vos permissions.",
|
||||
"mobile.android.storage_permission_denied_title": "L'accès au stockage de fichiers est requis",
|
||||
"mobile.android.videos_permission_denied_description": "Pour envoyer des vidéos à partir de votre bibliothèque, veuillez modifier vos paramètres d'autorisation.",
|
||||
"mobile.android.videos_permission_denied_title": "L'accès à la bibliothèque de vidéos est requis",
|
||||
"mobile.alert_dialog.alertCancel": "Annuler",
|
||||
"mobile.android.photos_permission_denied_description": "Utile pour envoyer des photos à votre instance Mattermost ou les sauvegarder sur votre appareil. Ouvrez les paramètres de votre appareil et accordez à Mattermost les accès de lecture et d'écriture à votre bibliothèque de photos.",
|
||||
"mobile.android.photos_permission_denied_title": "{applicationName} aimerait accéder à vos photos",
|
||||
"mobile.android.videos_permission_denied_description": "Utile pour envoyer des vidéos à votre instance Mattermost ou les sauvegarder sur votre appareil. Ouvrez les paramètres de votre appareil et accordez à Mattermost les accès de lecture et d'écriture à votre bibliothèque de vidéos.",
|
||||
"mobile.android.videos_permission_denied_title": "{applicationName} aimerait accéder à vos vidéos",
|
||||
"mobile.announcement_banner.title": "Annonce",
|
||||
"mobile.authentication_error.message": "Mattermost a rencontré un problème. Veuillez vous authentifier à nouveau pour démarrer une nouvelle session.",
|
||||
"mobile.authentication_error.title": "Erreur d'authentification",
|
||||
"mobile.calendar.dayNames": "Dimanche,Lundi,Mardi,Mercredi,Jeudi,Vendredi,Samedi",
|
||||
"mobile.calendar.dayNamesShort": "Dim,Lun,Mar,Mer,Jeu,Ven,Sam",
|
||||
"mobile.calendar.monthNames": "Janvier,Février,Mars,Avril,Mai,Juin,Juillet,Août,Septembre,Octobre,Novembre,Décembre",
|
||||
"mobile.calendar.monthNamesShort": "Jan,Fév,Mar,Avr,Mai,Juin,Juil,Aou,Sep,Oct,Nov,Déc",
|
||||
"mobile.camera_photo_permission_denied_description": "Utile pour prendre des photos et les envoyer à votre instance Mattermost ou les sauvegarder sur votre appareil. Ouvrez les paramètres de votre appareil et accordez à Mattermost les accès de lecture et d'écriture à votre appareil photo.",
|
||||
"mobile.camera_photo_permission_denied_title": "{applicationName} aimerait accéder à votre appareil photo",
|
||||
"mobile.camera_video_permission_denied_description": "Utile pour prendre des vidéos et les envoyer à votre instance Mattermost ou les sauvegarder sur votre appareil. Ouvrez les paramètres de votre appareil et accordez à Mattermost les accès de lecture et d'écriture à votre appareil photo.",
|
||||
"mobile.camera_video_permission_denied_title": "{applicationName} aimerait accéder à votre appareil photo",
|
||||
"mobile.channel_drawer.search": "Aller à...",
|
||||
"mobile.channel_info.alertMessageConvertChannel": "Lorsque vous convertissez {displayName} en canal privé, l'historique du canal ainsi que ses membres sont préservés. Les fichiers partagés publiquement restent accessibles à toute personne qui dispose du lien. Devenir membre d'un canal privé se fait sur invitation uniquement.\n\nCe changement est permanent et ne peut pas être annulé.\n\nVoulez-vous vraiment convertir {displayName} en canal privé ?",
|
||||
"mobile.channel_info.alertMessageDeleteChannel": "Voulez-vous vraiment archiver le {term} {name} ?",
|
||||
"mobile.channel_info.alertMessageLeaveChannel": "Voulez-vous vraiment quitter le {term} {name} ?",
|
||||
"mobile.channel_info.alertMessageUnarchiveChannel": "Voulez-vous vraiment archiver le {term} {name} ?",
|
||||
"mobile.channel_info.alertNo": "Non",
|
||||
"mobile.channel_info.alertTitleConvertChannel": "Convertir {displayName} en canal privé ?",
|
||||
"mobile.channel_info.alertTitleDeleteChannel": "Archiver {term}",
|
||||
"mobile.channel_info.alertTitleLeaveChannel": "Quitter {term}",
|
||||
"mobile.channel_info.alertTitleUnarchiveChannel": "Archiver {term}",
|
||||
"mobile.channel_info.alertYes": "Oui",
|
||||
"mobile.channel_info.convert": "Convertir en canal privé",
|
||||
"mobile.channel_info.convert_failed": "Impossible de convertir {displayName} en canal privé.",
|
||||
"mobile.channel_info.convert_success": "{displayName} est maintenant un canal privé.",
|
||||
"mobile.channel_info.copy_header": "Copier l'entête",
|
||||
"mobile.channel_info.copy_purpose": "Copier la description",
|
||||
"mobile.channel_info.delete_failed": "Impossible d'archiver le canal {displayName}. Veuillez vérifier votre connexion et essayer à nouveau.",
|
||||
"mobile.channel_info.edit": "Modifier le canal",
|
||||
"mobile.channel_info.privateChannel": "Canal privé",
|
||||
"mobile.channel_info.publicChannel": "Canal public",
|
||||
"mobile.channel_info.unarchive_failed": "Impossible d'archiver le canal {displayName}. Veuillez vérifier votre connexion et essayer à nouveau.",
|
||||
"mobile.channel_list.alertNo": "Non",
|
||||
"mobile.channel_list.alertYes": "Oui",
|
||||
"mobile.channel_list.archived": "ARCHIVÉ",
|
||||
"mobile.channel_list.channels": "CANAUX",
|
||||
"mobile.channel_list.closeDM": "Fermer le message personnel",
|
||||
"mobile.channel_list.closeGM": "Fermer le groupe de message",
|
||||
@@ -174,7 +184,6 @@
|
||||
"mobile.channel_list.not_member": "PAS UN MEMBRE",
|
||||
"mobile.channel_list.unreads": "NON LUS",
|
||||
"mobile.channel_members.add_members_alert": "Vous devez sélectionner au moins un membre à ajouter à ce canal.",
|
||||
"mobile.channel.markAsRead": "Marquer comme lu",
|
||||
"mobile.client_upgrade": "Mettre à jour l'application",
|
||||
"mobile.client_upgrade.can_upgrade_subtitle": "Une nouvelle version est disponible au téléchargement.",
|
||||
"mobile.client_upgrade.can_upgrade_title": "Mise à jour disponible",
|
||||
@@ -183,7 +192,7 @@
|
||||
"mobile.client_upgrade.download_error.message": "Une erreur s'est produite lors du téléchargement de la nouvelle version.",
|
||||
"mobile.client_upgrade.download_error.title": "Impossible d'installer la mise à jour",
|
||||
"mobile.client_upgrade.latest_version": "Votre version : {version}",
|
||||
"mobile.client_upgrade.listener.dismiss_button": "Rejeter",
|
||||
"mobile.client_upgrade.listener.dismiss_button": "Annuler",
|
||||
"mobile.client_upgrade.listener.learn_more_button": "En savoir plus",
|
||||
"mobile.client_upgrade.listener.message": "Une mise jour du client est disponible !",
|
||||
"mobile.client_upgrade.listener.upgrade_button": "Mettre à jour",
|
||||
@@ -204,25 +213,26 @@
|
||||
"mobile.create_channel.public": "Nouveau canal public",
|
||||
"mobile.create_post.read_only": "Ce canal est en lecture seule",
|
||||
"mobile.custom_list.no_results": "Aucun résultat",
|
||||
"mobile.display_settings.sidebar": "Barre latérale",
|
||||
"mobile.display_settings.theme": "Thème",
|
||||
"mobile.document_preview.failed_description": "Une erreur s'est produite lors de l'ouverture du document. Veuillez vous assurer que vous ayez un lecteur de {fileType} installé et réessayez.\n",
|
||||
"mobile.document_preview.failed_title": "L'ouverture du document a échoué",
|
||||
"mobile.downloader.android_complete": "Téléchargement terminé",
|
||||
"mobile.downloader.android_failed": "Échec du téléchargement",
|
||||
"mobile.downloader.android_permission": "Des permissions au dossier de téléchargements sont requises pour pouvoir sauvegarder les fichiers.",
|
||||
"mobile.downloader.android_permission": "Des permissions au dossier de téléchargements sont requises pour pouvoir enregistrer des fichiers.",
|
||||
"mobile.downloader.android_started": "Téléchargement commencé",
|
||||
"mobile.downloader.android_success": "Téléchargement réussi",
|
||||
"mobile.downloader.complete": "Téléchargement terminé",
|
||||
"mobile.downloader.disabled_description": "Le téléchargement de fichiers est désactivé sur ce serveur. Veuillez contacter votre administrateur système pour en savoir plus.\n",
|
||||
"mobile.downloader.disabled_title": "Téléchargement désactivé",
|
||||
"mobile.downloader.downloading": "Téléchargement en cours...",
|
||||
"mobile.downloader.failed_description": "Une erreur s'est produite lors du téléchargement du fichier. Veuillez vérifier votre connexion internet et réessayez.\n",
|
||||
"mobile.downloader.failed_title": "Échec du téléchargement",
|
||||
"mobile.downloader.image_saved": "Image sauvegardée",
|
||||
"mobile.downloader.video_saved": "Vidéo sauvegardée",
|
||||
"mobile.downloader.image_saved": "Image enregistrée",
|
||||
"mobile.downloader.video_saved": "Vidéo enregistrée",
|
||||
"mobile.drawer.teamsTitle": "Équipes",
|
||||
"mobile.edit_channel": "Enregistrer",
|
||||
"mobile.edit_post.title": "Edition du message",
|
||||
"mobile.edit_profile.remove_profile_photo": "Supprimer la photo",
|
||||
"mobile.emoji_picker.activity": "ACTIVITÉ",
|
||||
"mobile.emoji_picker.custom": "PERSONNALISÉ",
|
||||
"mobile.emoji_picker.flags": "DRAPEAUX",
|
||||
@@ -241,17 +251,24 @@
|
||||
"mobile.extension.file_limit": "Le partage est limité à un maximum de 5 fichiers.",
|
||||
"mobile.extension.max_file_size": "Les fichiers partagés dans Mattermost doivent faire moins de {size}.",
|
||||
"mobile.extension.permission": "Mattermost requiert l'accès au stockage de l'appareil pour partager des fichiers.",
|
||||
"mobile.extension.team_required": "Vous devez appartenir à une équipe avant de pouvoir partager des fichiers.",
|
||||
"mobile.extension.title": "Partager dans Mattermost",
|
||||
"mobile.failed_network_action.description": "Il semble y avoir un problème avec votre connexion Internet. Veuillez vous assurer d'avoir une connexion active et réessayez.",
|
||||
"mobile.failed_network_action.retry": "Réessayer",
|
||||
"mobile.failed_network_action.shortDescription": "Assurez-vous d'avoir une connexion active et réessayez.",
|
||||
"mobile.failed_network_action.retry": "Essayer à nouveau",
|
||||
"mobile.failed_network_action.shortDescription": "Messages will load when you have an internet connection.",
|
||||
"mobile.failed_network_action.teams_channel_description": "Channels could not be loaded for {teamName}.",
|
||||
"mobile.failed_network_action.teams_description": "Teams could not be loaded.",
|
||||
"mobile.failed_network_action.teams_title": "Something went wrong",
|
||||
"mobile.failed_network_action.title": "Aucune connexion Internet",
|
||||
"mobile.file_upload.browse": "Parcourir les fichiers",
|
||||
"mobile.file_upload.camera_photo": "Prendre une photo",
|
||||
"mobile.file_upload.camera_video": "Enregistrer une vidéo",
|
||||
"mobile.file_upload.library": "Bibliothèque de photos",
|
||||
"mobile.file_upload.max_warning": "Envois limités à maximum 5 fichiers.",
|
||||
"mobile.file_upload.unsupportedMimeType": "Seules les images BMP, JPG ou PNG peuvent être utilisées comme photos de profil.",
|
||||
"mobile.file_upload.video": "Bibliothèque vidéo",
|
||||
"mobile.files_paste.error_description": "Une erreur s'est produite lors de l'opération de collage du(des) fichier(s). Veuillez réessayer.",
|
||||
"mobile.files_paste.error_dismiss": "Annuler",
|
||||
"mobile.files_paste.error_title": "L'opération de collage a échoué",
|
||||
"mobile.flagged_posts.empty_description": "Marquer un message est un bon moyen d'assurer le suivi. Marquer un message est personnel et ne peut être vu par les autres utilisateurs.",
|
||||
"mobile.flagged_posts.empty_title": "Aucun message marqué d'un indicateur",
|
||||
"mobile.help.title": "Aide",
|
||||
@@ -260,7 +277,8 @@
|
||||
"mobile.intro_messages.default_message": "Il s'agit du premier canal que les utilisateurs voient lorsqu'ils s'inscrivent. Utilisez‑le pour poster des informations que tout le monde devrait connaître.",
|
||||
"mobile.intro_messages.default_welcome": "Bienvenue {name} !",
|
||||
"mobile.intro_messages.DM": "Vous êtes au début de votre historique de messages avec {teammate}. Les messages personnels et les fichiers partagés ici ne sont pas visibles par les autres utilisateurs.",
|
||||
"mobile.ios.photos_permission_denied_description": "Pour sauvegarder des photos et des vidéos dans votre bibliothèque, veuillez modifier vos paramètres d'autorisation.",
|
||||
"mobile.ios.photos_permission_denied_description": "Utile pour envoyer des photos ou vidéos à votre instance Mattermost ou les sauvegarder sur votre appareil. Ouvrez les paramètres de votre appareil et accordez à Mattermost les accès de lecture et d'écriture à votre bibliothèque de photos et vidéos.",
|
||||
"mobile.ios.photos_permission_denied_title": "{applicationName} aimerait accéder à vos photos",
|
||||
"mobile.join_channel.error": "Impossible de joindre le canal {displayName}. Veuillez vérifier votre connexion et essayer à nouveau.",
|
||||
"mobile.loading_channels": "Chargement des canaux...",
|
||||
"mobile.loading_members": "Chargement des membres...",
|
||||
@@ -271,13 +289,17 @@
|
||||
"mobile.managed.blocked_by": "Bloqué par {vendor}",
|
||||
"mobile.managed.exit": "Quitter",
|
||||
"mobile.managed.jailbreak": "Les dispositifs jailbreakés ne sont pas approuvés par {vendor}, veuillez quitter l'application.",
|
||||
"mobile.managed.not_secured.android": "Cet appareil doit être sécurisé avec un verrouillage d'écran pour pouvoir utiliser Mattermost.",
|
||||
"mobile.managed.not_secured.ios": "Cet appareil doit être sécurisé avec un code pour pouvoir utiliser Mattermost.\n\nAllez dans Réglages > Face ID et code.",
|
||||
"mobile.managed.not_secured.ios.touchId": "Cet appareil doit être sécurisé avec un code pour pouvoir utiliser Mattermost.\n\nAllez dans Réglages > Touch ID et code.",
|
||||
"mobile.managed.secured_by": "Sécurisé par {vendor}",
|
||||
"mobile.managed.settings": "Aller dans les paramètres",
|
||||
"mobile.markdown.code.copy_code": "Copier le code",
|
||||
"mobile.markdown.code.plusMoreLines": "+{count, number} other {count, plural, one {ligne} other {lignes}}",
|
||||
"mobile.markdown.image.too_large": "L'image dépasse les dimensions maximales de {maxWidth} par {maxHeight} :",
|
||||
"mobile.markdown.link.copy_url": "Copier l'URL",
|
||||
"mobile.mention.copy_mention": "Copier la mention",
|
||||
"mobile.message_length.message": "Votre message courant est trop long. Nombre actuel de caractères : {max}/{count}",
|
||||
"mobile.message_length.message": "Votre message courant est trop long. Nombre actuel de caractères : {count}/{max}",
|
||||
"mobile.message_length.title": "Longueur de message",
|
||||
"mobile.more_dms.add_more": "Vous pouvez encore ajouter {remaining, number} utilisateurs",
|
||||
"mobile.more_dms.cannot_add_more": "Vous ne pouvez plus ajouter d'utilisateurs",
|
||||
@@ -323,7 +345,7 @@
|
||||
"mobile.notification_settings.modal_cancel": "ANNULER",
|
||||
"mobile.notification_settings.modal_save": "ENREGISTRER",
|
||||
"mobile.notification_settings.ooo_auto_responder": "Réponses automatiques aux messages personnels",
|
||||
"mobile.notification_settings.save_failed_description": "Les paramètres de notification n'ont pas pu être enregistrés à cause d'un problème de connexion, merci d'essayer à nouveau.",
|
||||
"mobile.notification_settings.save_failed_description": "Les paramètres de notification n'ont pas pu être enregistrés à cause d'un problème de connexion, veuillez réessayer.",
|
||||
"mobile.notification_settings.save_failed_title": "Erreur de connexion",
|
||||
"mobile.notification.in": " dans ",
|
||||
"mobile.offlineIndicator.connected": "Connecté",
|
||||
@@ -332,20 +354,28 @@
|
||||
"mobile.open_dm.error": "Impossible d'ouvrir un message personnel avec {displayName}. Veuillez vérifier votre connexion et essayer à nouveau.",
|
||||
"mobile.open_gm.error": "Impossible d'ouvrir un message de groupe avec ces utilisateurs. Veuillez vérifier votre connexion et essayer à nouveau.",
|
||||
"mobile.open_unknown_channel.error": "Impossible de rejoindre le canal. Veuillez réinitialiser le cache et réessayer.",
|
||||
"mobile.permission_denied_dismiss": "Ne pas autoriser",
|
||||
"mobile.permission_denied_retry": "Paramètres",
|
||||
"mobile.photo_library_permission_denied_description": "Pour enregistrer des photos et des vidéos dans votre bibliothèque, veuillez modifier vos paramètres de permissions.",
|
||||
"mobile.photo_library_permission_denied_title": "{applicationName} aimerait accéder à votre bibliothèque de photos",
|
||||
"mobile.pinned_posts.empty_description": "Épingle des éléments importants en maintenant appuyé sur un message, puis en sélectionnant « Épingler au canal ».",
|
||||
"mobile.pinned_posts.empty_title": "Aucun message épinglé",
|
||||
"mobile.post_info.add_reaction": "Ajouter une réaction",
|
||||
"mobile.post_info.copy_text": "Copier le texte",
|
||||
"mobile.post_info.flag": "Marquer avec un indicateur",
|
||||
"mobile.post_info.mark_unread": "Marquer comme non lu",
|
||||
"mobile.post_info.pin": "Épingler au canal",
|
||||
"mobile.post_info.reply": "Répondre",
|
||||
"mobile.post_info.unflag": "Supprimer l'indicateur",
|
||||
"mobile.post_info.unpin": "Désépingler du canal",
|
||||
"mobile.post_pre_header.flagged": "Marqué d'un indicateur",
|
||||
"mobile.post_pre_header.pinned": "Épinglé",
|
||||
"mobile.post_pre_header.pinned_flagged": "Épinglé et marqué d'un indicateur",
|
||||
"mobile.post_textbox.empty.message": "Vous êtes en train d'envoyer un message vide.\nVeuillez-vous assurer d'avoir spécifié un message ou d'avoir joint au moins un fichier.",
|
||||
"mobile.post_textbox.empty.ok": "OK",
|
||||
"mobile.post_textbox.empty.title": "Message vide",
|
||||
"mobile.post_textbox.entire_channel.cancel": "Annuler",
|
||||
"mobile.post_textbox.entire_channel.confirm": "Confirmer",
|
||||
"mobile.post_textbox.entire_channel.message": "En utilisant @all ou @channel, vous êtes sur le point d'envoyer des notifications à {totalMembers, number} {totalMembers, plural, one {utilisateur} other {utilisateurs}}. Voulez-vous vraiment continuer ?",
|
||||
"mobile.post_textbox.entire_channel.message.with_timezones": "En utilisant @all ou @channel, vous êtes sur le point d'envoyer des notifications à {totalMembers, number} {totalMembers, plural, one {utilisateur} other {utilisateurs}} dans {timezones, number} {timezones, plural, one {fuseau horaire} other {fuseaux horaires}}. Voulez-vous vraiment continuer ?",
|
||||
"mobile.post_textbox.entire_channel.title": "Confirmez l'envoi de notifications au canal en entier",
|
||||
"mobile.post_textbox.uploadFailedDesc": "Certains fichiers joints n'ont pas pu être envoyés au serveur. Voulez-vous vraiment envoyer votre message ?",
|
||||
"mobile.post_textbox.uploadFailedTitle": "Erreur de pièces jointes",
|
||||
"mobile.post.cancel": "Annuler",
|
||||
@@ -356,6 +386,11 @@
|
||||
"mobile.post.failed_title": "Impossible d'envoyer votre message",
|
||||
"mobile.post.retry": "Rafraîchir",
|
||||
"mobile.posts_view.moreMsg": "Plus de nouveaux messages au-dessus",
|
||||
"mobile.privacy_link": "Politique de respect de la vie privée",
|
||||
"mobile.push_notification_reply.button": "Envoyer",
|
||||
"mobile.push_notification_reply.placeholder": "Écrire une réponse...",
|
||||
"mobile.push_notification_reply.title": "Répondre",
|
||||
"mobile.reaction_header.all_emojis": "Toutes",
|
||||
"mobile.recent_mentions.empty_description": "Les messages qui contiennent votre nom d'utilisateur et d'autres mots qui déclenchent des mentions apparaissent ici.",
|
||||
"mobile.recent_mentions.empty_title": "Aucune mention récente",
|
||||
"mobile.rename_channel.display_name_maxLength": "Ce champ doit faire moins de {maxLength, number} caractères",
|
||||
@@ -378,6 +413,8 @@
|
||||
"mobile.routes.channelInfo.createdBy": "Créé par {creator} le ",
|
||||
"mobile.routes.channelInfo.delete_channel": "Archiver le canal",
|
||||
"mobile.routes.channelInfo.favorite": "Favoris",
|
||||
"mobile.routes.channelInfo.groupManaged": "Les membres sont gérés par groupes liés.",
|
||||
"mobile.routes.channelInfo.unarchive_channel": "Archiver le canal",
|
||||
"mobile.routes.code": "Code {language}",
|
||||
"mobile.routes.code.noLanguage": "Code",
|
||||
"mobile.routes.edit_profile": "Éditer le profil",
|
||||
@@ -393,6 +430,7 @@
|
||||
"mobile.routes.thread": "Fil de discussion de {channelName}",
|
||||
"mobile.routes.thread_dm": "Fil de discussion de messages personnels",
|
||||
"mobile.routes.user_profile": "Profil",
|
||||
"mobile.routes.user_profile.edit": "Modifier",
|
||||
"mobile.routes.user_profile.local_time": "HEURE LOCALE",
|
||||
"mobile.routes.user_profile.send_message": "Envoyer un message",
|
||||
"mobile.search.after_modifier_description": "pour trouver des messages publiés après une date spécifique",
|
||||
@@ -405,13 +443,20 @@
|
||||
"mobile.search.no_results": "Aucun résultat trouvé",
|
||||
"mobile.search.on_modifier_description": "pour trouver des messages publiés à une date spécifique",
|
||||
"mobile.search.recent_title": "Recherches récentes",
|
||||
"mobile.select_team.guest_cant_join_team": "Votre compte d'utilisateur invité n'a pas d'équipe ou de canal assigné. Veuillez contacter un administrateur.",
|
||||
"mobile.select_team.join_open": "Les autres équipes que vous pouvez rejoindre.",
|
||||
"mobile.select_team.no_teams": "Il n'y a aucune équipe disponible que vous pouvez rejoindre.",
|
||||
"mobile.server_link.error.text": "Le lien n'a pas pu être trouvé sur ce serveur.",
|
||||
"mobile.server_link.error.title": "Erreur de lien",
|
||||
"mobile.server_link.unreachable_channel.error": "Ce lien correspond à un canal supprimé ou appartenant à un canal auquel vous n'avez pas accès.",
|
||||
"mobile.server_link.unreachable_team.error": "Ce lien correspond à une équipe supprimée ou appartenant à une équipe à laquelle vous n'avez pas accès.",
|
||||
"mobile.server_ssl.error.text": "The certificate from {host} is not trusted.\n\nPlease contact your System Administrator to resolve the certificate issues and allow connections to this server.",
|
||||
"mobile.server_ssl.error.title": "Untrusted Certificate",
|
||||
"mobile.server_upgrade.button": "OK",
|
||||
"mobile.server_upgrade.description": "\nUne mise à jour du serveur est requise pour utiliser l'application Mattermost. Veuillez demander à votre administrateur système pour plus de détails.\n",
|
||||
"mobile.server_upgrade.description": "\nUne mise à jour du serveur est requise pour utiliser l'application Mattermost. Veuillez demander à votre administrateur système pour en savoir plus.\n",
|
||||
"mobile.server_upgrade.title": "Mise à jour du serveur requise",
|
||||
"mobile.server_url.invalid_format": "L'URL doit commencer par http:// ou https://",
|
||||
"mobile.session_expired": "La session a expiré. Veuillez vous connecter pour continuer à recevoir les notifications.",
|
||||
"mobile.session_expired": "La session a expiré : Veuillez vous connecter pour continuer à recevoir des notifications. Les sessions pour {siteName} sont configurées pour expirer tous les {daysCount, number} {daysCount, plural, one {jour} other {jours}}.",
|
||||
"mobile.set_status.away": "Absent",
|
||||
"mobile.set_status.dnd": "Ne pas déranger",
|
||||
"mobile.set_status.offline": "Hors ligne",
|
||||
@@ -422,22 +467,36 @@
|
||||
"mobile.share_extension.error_message": "Une erreur s'est produite lors de l'utilisation de l'extension de partage.",
|
||||
"mobile.share_extension.error_title": "Erreur d'extension",
|
||||
"mobile.share_extension.team": "Équipe",
|
||||
"mobile.share_extension.too_long_message": "Nombre de caractères : {count}/{max}",
|
||||
"mobile.share_extension.too_long_title": "Le message est trop long",
|
||||
"mobile.sidebar_settings.permanent": "Barre latérale permanente",
|
||||
"mobile.sidebar_settings.permanent_description": "Conserver la barre latérale ouverte en permanence",
|
||||
"mobile.storage_permission_denied_description": "Utile pour envoyer des fichiers à votre instance Mattermost. Ouvrez les paramètres de votre appareil et accordez à Mattermost les accès de lecture et d'écriture aux fichiers.",
|
||||
"mobile.storage_permission_denied_title": "{applicationName} aimerait accéder à vos fichiers",
|
||||
"mobile.suggestion.members": "Membres",
|
||||
"mobile.system_message.channel_archived_message": "{username} a archivé le canal",
|
||||
"mobile.system_message.channel_unarchived_message": "{username} a archivé le canal",
|
||||
"mobile.system_message.update_channel_displayname_message_and_forget.updated_from": "{username} à modifié le nom d'affichage du canal de : {oldDisplayName} en : {newDisplayName}",
|
||||
"mobile.system_message.update_channel_header_message_and_forget.removed": "{username} a supprimé l'entête du canal (précédemment : {oldHeader})",
|
||||
"mobile.system_message.update_channel_header_message_and_forget.updated_from": "{username} a mis à jour l'entête du canal de : {oldHeader} en : {newHeader}",
|
||||
"mobile.system_message.update_channel_header_message_and_forget.updated_to": "{username} a mis à jour l'entête du canal en : {newHeader}",
|
||||
"mobile.system_message.update_channel_purpose_message.removed": "{username} a supprimé la description du canal (précédemment: {oldPurpose})",
|
||||
"mobile.system_message.update_channel_purpose_message.updated_from": "{username} a mis à jour la description du canal de : {oldPurpose} en : {newPurpose}",
|
||||
"mobile.system_message.update_channel_purpose_message.updated_to": "{username} a mis à jour la description du canal en : {newPurpose}",
|
||||
"mobile.terms_of_service.alert_cancel": "Annuler",
|
||||
"mobile.terms_of_service.alert_ok": "OK",
|
||||
"mobile.terms_of_service.alert_retry": "Réessayer",
|
||||
"mobile.terms_of_service.get_terms_error_description": "Assurez-vous d'avoir une connexion internet active et réessayez. Si ce problème persiste, contactez votre administrateur système.",
|
||||
"mobile.terms_of_service.get_terms_error_title": "Impossible de charger les conditions d'utilisation.",
|
||||
"mobile.terms_of_service.terms_rejected": "Vous devez accepter les conditions d'utilisation avant de pouvoir accéder à {siteName}. Veuillez contacter votre administrateur système pour plus d'informations.",
|
||||
"mobile.terms_of_service.terms_rejected": "Vous devez accepter les conditions d'utilisation avant de pouvoir accéder à {siteName}. Veuillez contacter votre administrateur système pour en savoir plus..",
|
||||
"mobile.timezone_settings.automatically": "Définir automatiquement",
|
||||
"mobile.timezone_settings.manual": "Changer le fuseau horaire",
|
||||
"mobile.timezone_settings.select": "Sélectionner un fuseau horaire",
|
||||
"mobile.tos_link": "Conditions d'utilisation",
|
||||
"mobile.user_list.deactivated": "Désactivé",
|
||||
"mobile.user.settings.notifications.email.fifteenMinutes": "Toutes les 15 minutes",
|
||||
"mobile.video_playback.failed_description": "Une erreur s'est produite lors de la tentative de lecture de la vidéo.\n",
|
||||
"mobile.video_playback.failed_title": "La lecture de la vidéo a échoué",
|
||||
"mobile.video.save_error_message": "Pour enregistrer la vidéo, vous devez d'abord la télécharger.",
|
||||
"mobile.video.save_error_title": "Erreur lors de la sauvegarde de la vidéo",
|
||||
"mobile.video.save_error_title": "Erreur lors de l'enregistrement de la vidéo",
|
||||
"mobile.youtube_playback_error.description": "Une erreur s'est produite lors de la lecture de la vidéo YouTube.\nDétails : {details}",
|
||||
"mobile.youtube_playback_error.title": "Erreur de lecture YouTube",
|
||||
"modal.manual_status.auto_responder.message_": "Voulez-vous changer votre statut sur « {status} » et désactiver les Réponses Automatiques ?",
|
||||
@@ -445,11 +504,17 @@
|
||||
"modal.manual_status.auto_responder.message_dnd": "Voulez-vous changer votre statut sur « Ne pas déranger » et désactiver les Réponses Automatiques ?",
|
||||
"modal.manual_status.auto_responder.message_offline": "Voulez-vous changer votre statut sur « Hors ligne » et désactiver les Réponses Automatiques ?",
|
||||
"modal.manual_status.auto_responder.message_online": "Voulez-vous changer votre statut sur « En ligne » et désactiver les Réponses Automatiques ?",
|
||||
"more_channels.archivedChannels": "Canaux archivés",
|
||||
"more_channels.dropdownTitle": "Afficher",
|
||||
"more_channels.noMore": "Il n'y a plus d'autre canal que vous pouvez rejoindre",
|
||||
"more_channels.publicChannels": "Canaux publics",
|
||||
"more_channels.showArchivedChannels": "Afficher : Canaux archivés",
|
||||
"more_channels.showPublicChannels": "Afficher : Canaux publics",
|
||||
"more_channels.title": "Plus de canaux",
|
||||
"msg_typing.areTyping": "{users} et {last} sont en train d'écrire...",
|
||||
"msg_typing.isTyping": "{user} est en train d'écrire...",
|
||||
"navbar_dropdown.logout": "Se déconnecter",
|
||||
"navbar.channel_drawer.button": "Canaux et équipes",
|
||||
"navbar.channel_drawer.hint": "Ouvre le menu de canaux et d'équipes",
|
||||
"navbar.leave": "Quitter le canal",
|
||||
"password_form.title": "Réinitialisation du mot de passe",
|
||||
"password_send.checkInbox": "Veuillez vérifier votre boîte de réception.",
|
||||
@@ -458,25 +523,26 @@
|
||||
"password_send.link": "Si le compte existe, un e-mail de redéfinition de mot de passe sera envoyé à :",
|
||||
"password_send.reset": "Réinitialiser mon mot de passe",
|
||||
"permalink.error.access": "Ce lien correspond à un message supprimé ou appartenant à un canal auquel vous n'avez pas accès.",
|
||||
"permalink.error.link_not_found": "Lien introuvable",
|
||||
"post_body.check_for_out_of_channel_groups_mentions.message": "n'a pas été notifié par cette mention. L'utilisateur n'est pas dans le canal et ne peut pas être ajouté à ce canal, car il ne fait pas partie des groupes liés. Pour l'ajouter à ce canal, l'utilisateur doit être ajouté aux groupes liés.",
|
||||
"post_body.check_for_out_of_channel_mentions.link.and": " et ",
|
||||
"post_body.check_for_out_of_channel_mentions.link.private": "ajouter à ce canal privé",
|
||||
"post_body.check_for_out_of_channel_mentions.link.public": "ajouter à ce canal",
|
||||
"post_body.check_for_out_of_channel_mentions.message_last": "? Ils auront alors accès à tout l'historique de messages pour ce canal.",
|
||||
"post_body.check_for_out_of_channel_mentions.message.multiple": "ont été mentionnés, mais ne sont pas dans le canal. Voulez-vous ",
|
||||
"post_body.check_for_out_of_channel_mentions.message.one": "a été mentionné, mais n'est pas dans le canal. Voulez-vous ",
|
||||
"post_body.check_for_out_of_channel_mentions.message.multiple": "n'a pas été notifié par cette mention, car l'utilisateur n'est pas dans le canal. Voulez-vous ",
|
||||
"post_body.check_for_out_of_channel_mentions.message.one": "n'ont pas été notifiés par cette mention, car ces utilisateurs ne sont pas dans le canal. Voulez-vous ",
|
||||
"post_body.commentedOn": "a commenté le message de {name} : ",
|
||||
"post_body.deleted": "(message supprimé)",
|
||||
"post_info.auto_responder": "RÉPONSE AUTOMATIQUE",
|
||||
"post_info.bot": "BOT",
|
||||
"post_info.del": "Supprimer",
|
||||
"post_info.edit": "Éditer",
|
||||
"post_info.guest": "INVITÉ",
|
||||
"post_info.message.show_less": "Afficher moins",
|
||||
"post_info.message.show_more": "Afficher plus",
|
||||
"post_info.system": "Système",
|
||||
"post_message_view.edited": "(édité)",
|
||||
"posts_view.newMsg": "Nouveaux messages",
|
||||
"rename_channel.handleHolder": "caractères alphanumériques minuscules",
|
||||
"rename_channel.url": "URL",
|
||||
"rhs_thread.rootPostDeletedMessage.body": "Une partie de ce fil de discussion a été supprimée à cause d'une politique de rétention de données. Vous ne pouvez plus répondre à ce fil.",
|
||||
"search_bar.search": "Rechercher",
|
||||
"search_header.results": "Résultats de la recherche",
|
||||
@@ -498,28 +564,30 @@
|
||||
"status_dropdown.set_offline": "Hors ligne",
|
||||
"status_dropdown.set_online": "En ligne",
|
||||
"status_dropdown.set_ooo": "Absent du bureau",
|
||||
"suggestion.mention.all": "ATTENTION : Ceci mentionne tout le monde dans le canal",
|
||||
"suggestion.mention.channel": "Notifier tout le monde dans le canal",
|
||||
"suggestion.mention.all": "Envoie une notification à tous les membres de ce canal",
|
||||
"suggestion.mention.channel": "Envoie une notification à tous les membres de ce canal",
|
||||
"suggestion.mention.channels": "Mes canaux",
|
||||
"suggestion.mention.here": "Notifier toutes les personnes connectées dans ce canal",
|
||||
"suggestion.mention.here": "Envoie une notification à tous les membres considérés comme en ligne de ce canal",
|
||||
"suggestion.mention.members": "Membres du canal",
|
||||
"suggestion.mention.morechannels": "Autres canaux",
|
||||
"suggestion.mention.nonmembers": "Pas dans le canal",
|
||||
"suggestion.mention.special": "Mentions spéciales",
|
||||
"suggestion.mention.you": "(vous)",
|
||||
"suggestion.search.direct": "Messages personnels",
|
||||
"suggestion.search.private": "Canaux privés",
|
||||
"suggestion.search.public": "Canaux publics",
|
||||
"terms_of_service.agreeButton": "Je suis d'accord",
|
||||
"terms_of_service.api_error": "Impossible de terminer la requête. Si ce problème persiste, contactez votre administrateur système.",
|
||||
"user.settings.display.clockDisplay": "Affichage de l'horloge",
|
||||
"user.settings.display.custom_theme": "Thème personnalisé",
|
||||
"user.settings.display.militaryClock": "Horloge 24 heures (ex. : 16:00)",
|
||||
"user.settings.display.normalClock": "Horloge 12 heures (ex. : 4:00 PM)",
|
||||
"user.settings.display.preferTime": "Choisissez la façon dont vous préférez voir les heures affichées dans l'application.",
|
||||
"user.settings.general.email": "E-mail",
|
||||
"user.settings.general.emailCantUpdate": "L'adresse e-mail ne peut être mise à jour qu'en utilisant un navigateur web ou l'application de bureau.",
|
||||
"user.settings.general.emailGitlabCantUpdate": "La connexion s'effectue par Gitlab. L'adresse e-mail ne peut pas être mise à jour. L'adresse e-mail utilisée pour les notifications par e-mail est {email}.",
|
||||
"user.settings.general.emailGoogleCantUpdate": "La connexion s'effectue par Gitlab. L'adresse e-mail ne peut pas être mise à jour. L'adresse e-mail utilisée pour les notifications par e-mail est {email} .",
|
||||
"user.settings.general.emailHelp2": "L'envoi d'e-mails a été désactivé par votre administrateur système. Aucun e-mail de notification ne peut être envoyé.",
|
||||
"user.settings.general.emailGoogleCantUpdate": "La connexion s'effectue par Google. L'adresse e-mail ne peut pas être mise à jour. L'adresse e-mail utilisée pour les notifications par e-mail est {email} .",
|
||||
"user.settings.general.emailHelp2": "L'envoi d'e-mails a été désactivé par votre administrateur système. Aucune notification par e-mail ne peut être envoyée.",
|
||||
"user.settings.general.emailLdapCantUpdate": "La connexion s'effectue par AD/LDAP. L'adresse e-mail ne peut pas être mise à jour. L'adresse e-mail utilisée pour les notifications par e-mail est {email}.",
|
||||
"user.settings.general.emailOffice365CantUpdate": "La connexion s'effectue par Office 365. L'adresse e-mail ne peut pas être mise à jour. L'adresse e-mail utilisée pour les notifications par e-mail est {email} .",
|
||||
"user.settings.general.emailSamlCantUpdate": "La connexion s'effectue via SAML. L'adresse e-mail ne peut pas être mise à jour. L'adresse e-mail utilisée pour les notifications par e-mail est {email}.",
|
||||
@@ -538,7 +606,7 @@
|
||||
"user.settings.notifications.email.immediately": "Immédiatement",
|
||||
"user.settings.notifications.email.never": "Jamais",
|
||||
"user.settings.notifications.email.send": "Envoyer des notifications de bureau",
|
||||
"user.settings.notifications.emailInfo": "Les e-mails de notification sont envoyés pour les mentions et les messages personnels reçus après que vous soyez passé hors-ligne ou absent de {siteName} pendant plus de 5 minutes.",
|
||||
"user.settings.notifications.emailInfo": "Les notifications par e-mail sont envoyées pour les mentions et les messages personnels reçus lorsque vous êtes hors-ligne ou absent pour plus de 5 minutes.",
|
||||
"user.settings.notifications.never": "Jamais",
|
||||
"user.settings.notifications.onlyMentions": "Seulement pour les mentions et messages personnels",
|
||||
"user.settings.push_notification.away": "Absent ou hors ligne",
|
||||
@@ -547,4 +615,4 @@
|
||||
"user.settings.push_notification.offline": "Hors ligne",
|
||||
"user.settings.push_notification.online": "En ligne, absent(e) ou hors ligne",
|
||||
"web.root.signup_info": "Toute la communication de votre équipe au même endroit, accessible de partout"
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user