Merge beta into master (#1897)
* [FIX] Close SortDropdown on sort select (#1230) * [FIX] Cancel upload and check failed upload (#1232) * [FIX] Slash commands not cleaning is typing and not using state (#1233) * [FIX] Dispatch roomsRequest on app foreground event even if not connected (#1234) * [CHORE] Update react-native-jitsi-meet (#1235) * [FIX] Regex on run slash command (#1223) * Update React Native to 0.61.1 (#1236) * Update React Native to 0.61.1 * Update patch to SSL Pinning * Revert storybook * [CHORE] Update react-native-safe-area-view (#1219) * [FIX] Try/catch JSON.parse XHR response (#1238) * [FIX] Change messagebox icon immediate on change text (#1241) * [FIX] Update last open on message stream received (#1240) * [FIX] Remove animation from RoomsListView.willFocus (#1239) * [FIX] Delete message on thread (#1214) * [REGRESSION] Markdown text (#1242) * [FIX] Jest (#1243) * [FIX] Avatar shown when useRealName is activated (#1162) * Fix avatar when use real name * Wrong indentation * [DOCS] Add SECURITY.md (#1244) * [CHORE] Update react-native-reanimated to 1.3.0 (#1246) * [FIX] Run credentials migration only once (#1245) * [CHORE] Update react-native-jitsi-meet to 2.0.1 (#1249) * [FIX] Messagebox onChangeText issues (#1252) * Stop ongoing debounces on messagebox unmount * Immediately change send icon, but keep debouncing others * Make CustomEmoji stateless function * Fix mentions keyExtractor * [FIX] Room subscription issues (#1255) * [FIX] Reaction press (#1258) * [FIX] Channel avatars not showing after application unloads (#1264) * Revert react-native-safe-area-view (#1265) * [FIX] Remove console on production mode (#1268) * [FIX] Messages preview issues (#1257) * [FIX] Select user from native credentials (#1266) * [FIX] Some issues on preview message (#1271) * [FIX] Audio player track and thumb not rendering on Android (#1273) * [FIX] Record audio message throws exception when FileSystem.getInfoAsync is called (#1272) * [FIX] China shouldn't use CallKit (#1274) * [FIX] Watermelon batches (#1277) * Bump version to 1.20.1 (#1285) * [CHORE] Remove memoize-one (#1284) * [FIX] End Jitsi call on unmount (#1291) * [FIX] Allow self-signed certificates (#1310) * [FIX] Set User-Agent (#1318) * Set User-Agent Fetch & Websocket & XHR * Set User-Agent * Custom User Agent on fetch/websocket * Fix names * Use DeviceInfo * fix server with subpath (#1322) * [FIX] Server with https:\\ instead of https:// (#1320) * [FIX] Server dropdown not closing after changing stack (#1299) * [FIX] Invalid server version (#1319) * [IMPROVEMENT] Respect "Hide counter" preference (#1306) * [FIX] Pass isFocused as a function to Messagebox (#1309) * [CHORE] Remove icons folder (#1290) * [CHORE] Refactor RoomItem touchable (#1331) * [FIX] Unnecessary rerender on RoomItem when status is undefined (#1336) * [UPDATE DEPS] react-navigation and react-navigation-stack (#1337) * [FIX] Avatars not loading on share extension when Accounts_AvatarBlockUnauthenticatedAccess is enabled (#1339) * Bump version to 1.20.2 (#1340) * [FIX] Remove some unnecessary re-renders on Messagebox (#1341) * [REGRESSION] Use LayoutAnimation instead of Transition API (#1338) * [FIX] Remove setState from notifications view causing watermelon object to be updated outside an action (#1342) * [IMPROVEMENT] Save last message as message when subscription is updated (#1344) * [UPDATE DEPS] Update RN to 0.61.3 (#1345) * [DOCS] Update Readme (#1346) * [CHORE] Remove react-native-scrollable-tab-view fork (#1352) * [FIX] URL preview (#1360) * [REGRESSION] Decrease list view memory size (#1361) * [FIX] Paste (#1350) * [CHORE] Update gems (#1365) * Bump version to 1.20.3 (#1366) * [FIX] Use Ruby 2.4 on TestFlight upload (#1368) * [FIX] Parse Urls (#1371) * [FIX] Parse image URL only if it's not empty (#1372) * [FIX] Load messages issues (#1373) * Bump version to 1.21.0 (#1376) * [FIX] Crowd login (#1381) * [FIX] Clicking user avatar in thread previews crashes app (#1363) * [IMPROVEMENT] Error messages on connect (#1379) * [FIX] ProfileView input navigation error when custom fields aren't set (#1383) * [FIX] Batch server deletion on logout (#1382) * Bump app to 1.22.0 (#1387) * [FIX] Server Version (#1392) * Update patch and minor deps (#1386) * [FIX] Crash when open thread (#1395) * Bump version to 1.23.0 (#1394) * [I18N] Update ru.js (#1384) * [FIX] CAS building wrong URL (#1362) * [FIX] Delete messages (#1399) * [FIX] In-app notification showing wrong content on channels (#1400) * Bump version to 1.24.0 (#1404) * [FIX] Prevent server with whitespace (#1402) * [IMPROVEMENT] Keyboard and content type on login (#1403) * [FIX] Messages stop loading (#1410) * [NEW] Tablet support (#1300) * [IMPROVEMENT] Authentication via deep linking (#1418) * [IMPROVEMENT] Markdown performance when identifying emoji only content (#1422) * [FIX] BackHandler remove random failing on development (#1423) * Bump version to 1.25.0 (#1424) * [CHORE] Update CI Xcode Image (#1430) * [FIX] Rooms grouping not working properly (#1435) * [FIX] Take a video (#1437) * [NEW] Themes (#1298) * [FIX] Share extension doesn't reconnect to previous selected server on Android (#1429) * [FIX] Init local settings on notification tap (#1438) * Bump version to 1.26.0 (#1450) * [FIX] Emoji parser not working on Hermes (#1445) * [NEW] Enable Hermes (#1446) * [FIX] Automatic theme repeating (#1457) * [CHORE] Sync Experimental and Official app versions (#1458) * [DOCS] Update readme (#1459) * [FIX] Messages being sent but showing as temp status (#1469) * [FIX] Missing messages after reconnect (#1470) * [FIX] Few fixes on themes (#1477) * [I18N] Missing German translations (#1465) * Missing German translation * adding a missing space behind colon * added a missing space after colon * and another attempt to finally fix this – got confused by all the branches * some smaller fixes for the translation * better wording * fixed another typo * [FIX] Crash while displaying the attached image with http on file name (#1401) * [IMPROVEMENT] Tap app and server version to copy to clipboard (#1425) * [NEW] Reply notification (#1448) * [FIX] Incorrect background color login on iPad (#1480) * [FIX] Prevent multiple tap on send (Share Extension) (#1481) * [NEW] Image Viewer (#1479) * [DOCS] Update Readme (#1485) * [FIX] Jitsi with Hermes Enabled (#1523) * [FIX] Draft messages not working with themed Messagebox (#1525) * [FIX] Go to direct message from members list (#1519) * [FIX] Make SAML wait for idp token instead of creating it on client (#1527) * [FIX] Server Test Push Notification (#1508) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [CHORE] Update to new server response (#1509) * [FIX] Insert messages with blank users (#1529) * Bump version to 4.2.1 (#1530) * [FIX] Error when normalizing empty messages (#1532) * [REGRESSION] CAS (#1570) * Bump version to 4.2.2 (#1571) * [FIX] Add username block condition to prevent error (#1585) * Bump version to 4.2.3 * Bump version to 4.2.4 * Bump version to 4.3.0 (#1630) * [FIX] Channels doesn't load (#1586) * [FIX] Channels doesn't load * [FIX] Update roomsUpdatedAt when subscriptions.length is 0 * [FIX] Remove unnecessary changes * [FIX] Improve the code Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Make SAML to work on Rocket.Chat < 2.3.0 (#1629) * [NEW] Invite links (#1534) * [FIX] Set the http-agent to the form that Rocket.Chat requires for logging (#1482) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] "Following thread" and "Unfollowed Thread" is hardcoded and not translated (#1625) * [FIX] Disable reset button if form didn't changed (#1569) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Header title of RoomInfoView (#1553) * [I18N] Gallery Permissions DE (#1542) * [FIX] Not allow to send messages to archived room (#1623) * [FIX] Profile fields automatically reset (#1502) * [FIX] Show attachment on ThreadMessagesView (#1493) * [NEW] Wordpress auth (#1633) * [CHORE] Add Start Packager script (#1639) * [CHORE] Update RN to 0.61.5 (#1638) * [CHORE] Update RN to 0.61.5 * [CHORE] Update react-native patch Co-authored-by: Djorkaeff Alexandre <djorkaeff.unb@gmail.com> * Bump version to 4.3.1 (#1641) * [FIX] Change force logout rule (#1640) * Bump version to 4.4.0 (#1643) * [IMPROVEMENT] Use MessagingStyle on Android Notification (#1575) * [NEW] Request review (#1627) * [NEW] Pull to refresh RoomView (#1657) * [FIX] Unsubscribe from room (#1655) * [FIX] Server with subdirs (#1646) * [NEW] Clear cache (#1660) * [IMPROVEMENT] Memoize and batch subscriptions updates (#1642) * [FIX] Disallow empty sharing (#1664) * [REGRESSION] Use HTTPS links for sharing and markets protocol for review (#1663) * [FIX] In some cases, share extension doesn't load images (#1649) * [i18n] DE translations for new invite function and some minor fixes (#1631) * [FIX] Remove duplicate jetify step (#1628) minor: also remove 'cd' calls Co-authored-by: Diego Mello <diegolmello@gmail.com> * [REGRESSION] Read messages (#1666) * [i18n] German translations missing (#1670) * [FIX] Notifications crash on older Android Versions (#1672) * [i18n] Added Dutch translation (#1676) * [NEW] Omnichannel Beta (#1674) * [NEW] Confirm logout/clear cache (#1688) * [I18N] Add es-ES language (#1495) * [NEW] UiKit Beta (#1497) * [IMPROVEMENT] Use reselect (#1696) * [FIX] Notification in Android API level less than 24 (#1692) * [IMPROVEMENT] Send tmid on slash commands and media (#1698) * [FIX] Unhandled action on UIKit (#1703) * [NEW] Pull to refresh RoomsList (#1701) * [IMPROVEMENT] Reset app when language is changed (#1702) * [FIX] Small fixes on UIKit (#1709) * [FIX] Spotlight (#1719) * [CHORE] Update react-native-image-crop-picker (#1712) * [FIX] Messages Overlapping (Android) and MessageBox Scroll (iOS) (#1720) * [REGRESSION] Remove @ and # from mention (#1721) * [NEW] Direct message from user info (#1516) * [FIX] Delete slash commands (#1723) * [IMPROVEMENT] Hold URL to copy (#1684) * [FIX] Different sourcemaps generation for Hermes (#1724) * [FIX] Different sourcemaps generation for Hermes * Upload sourcemaps after build * [REVERT] Show emoji keyboard on Android (#1738) * [FIX] Stop logging react-native-image-crop-picker (#1745) * [FIX] Prevent toast ref error (#1744) * [FIX] Prevent reaction map error (#1743) * [FIX] Add missing calls to user info (#1741) * [FIX] Catch room unsubscribe error (#1739) * [i18n] Missing German keys (#1735) * [FIX] Missing i18n on MessagesView title (#1733) * [FIX] UIKit Modal: Weird behavior on Android Tablet (#1742) * [i18n] Missing key on German (#1747) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [i18n] Add Italian (#1736) * [CHORE] Memory leaks investigation (#1675) * [IMPROVEMENT] Alert verify email when enabled (#1725) * [NEW] Jitsi JWT added to URL (#1746) * [FIX] UIKit submit when connection lost (#1748) * Bump version to 4.5.0 (#1761) * [NEW] Default browser (#1752) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] HTTP Basic Auth (#1753) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [IMPROVEMENT] Honor profile fields edit settings (#1687) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [IMPROVEMENT] Room announcements (#1726) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [IMPROVEMENT] Honor Register/Login settings (#1727) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [IMPROVEMENT] Make links clickable on Room Info (#1730) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [NEW] Hide system messages (#1755) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [IMPROVEMENT] Honor "Message_AudioRecorderEnabled" (#1764) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [i18n] Missing de keys (#1765) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Redirect user to SetUsernameView (#1728) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Join Room (#1769) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Accept all media types using * (#1770) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Use RealName when necessary (#1758) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Markdown Line Break (#1783) * [IMPROVEMENT] Remove useMarkdown (#1774) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [IMPROVEMENT] Open browser rather than webview on Create Workspace (#1788) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [IMPROVEMENT] Markdown perf (#1796) * [FIX] Stop video when modal is closed (#1787) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Hide reply notification action when there are missing data (#1771) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [i18n] Added Japanese translation (#1781) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Reset password error message (#1772) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Close tablet modal (#1773) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Setting not present (#1775) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Thread header (#1776) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Keyboard tracking loses input ref (#1784) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [NEW] Mark message as unread (#1785) Co-authored-by: Djorkaeff Alexandre <djorkaeff.unb@gmail.com> * [IMPROVEMENT] Log server version (#1786) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [IMPROVEMENT] Add loading message on long running tasks (#1798) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [CHORE] Switch Apple account on Fastlane (#1810) * [FIX] Watermelon throwing "Cannot update a record with pending updates" (#1754) * [FIX] Detox tests (#1790) * [CHORE] Use markdown preview on RoomView Header (#1807) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] LoginSignup blink services (#1809) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [IMPROVEMENT] Request user presence on demand (#1813) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Remove all invited users when create a channel (#1814) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Pop from room which you have been removed (#1819) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Room Info styles (#1820) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [i18n] Add missing German keys (#1800) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Empty mentions for @all and @here when real name is enabled (#1822) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [TESTS] Markdown added to Storybook (#1812) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [REGRESSION] Room View header title (#1827) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Storybook snapshots (#1831) Co-authored-by: Djorkaeff Alexandre <djorkaeff.unb@gmail.com> * [FIX] Mentions (#1829) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Thread message not found (#1830) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Separate delete and remove channel (#1832) * Rename to delete room * Separate delete and remove channel * handleRemoved -> handleRoomRemoved * [FIX] Navigate to RoomsList & Handle tablet case Co-authored-by: Djorkaeff Alexandre <djorkaeff.unb@gmail.com> * [NEW] Filter system messages per room (#1815) Co-authored-by: Djorkaeff Alexandre <djorkaeff.unb@gmail.com> Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] e2e tests (#1838) * [FIX] Consecutive clear cache calls freezing app (#1851) * Bump version to 4.5.1 (#1853) * [FIX][iOS] Ignore silent mode on audio player (#1862) * [IMPROVEMENT] Create App Group property on Info.plist (#1858) Co-authored-by: Diego Mello <diegolmello@gmail.com> Co-authored-by: Prateek Jain <44807945+Prateek93a@users.noreply.github.com> Co-authored-by: Djorkaeff Alexandre <djorkaeff.unb@gmail.com> Co-authored-by: Lucas Siqueira <lucassiqzro@gmail.com> Co-authored-by: Calebe Rios <calebersmendes@gmail.com> Co-authored-by: Pitstopper <18574776+Pitstopper@users.noreply.github.com> Co-authored-by: phriedrich <info@phriedrich.de> Co-authored-by: Guilherme Siqueira <guilhersiqueira@gmail.com> Co-authored-by: Prateek Jain <prateek93a@gmail.com> Co-authored-by: devyaniChoubey <52153085+devyaniChoubey@users.noreply.github.com> Co-authored-by: Bernard Seow <ssbing99@gmail.com> Co-authored-by: Hiroki Ishiura <ishiura@ja2.so-net.ne.jp> Co-authored-by: Exordian <jakob.englisch@gmail.com> Co-authored-by: Daanchaam <daanhendriks97@gmail.com> Co-authored-by: Youssef Muhamad <emaildeyoussefmuhamad@gmail.com> Co-authored-by: Iván Álvarez <ialvarezpereira@gmail.com> Co-authored-by: Sarthak Pranesh <41206172+sarthakpranesh@users.noreply.github.com> Co-authored-by: Michele Pellegrini <pellettiero@users.noreply.github.com> Co-authored-by: Tanmoy Bhowmik <tanmoy.openroot@gmail.com> Co-authored-by: Hibikine Kage <14365761+hibikine@users.noreply.github.com>
This commit is contained in:
parent
69aff7e56a
commit
96bfe1910e
|
@ -44,7 +44,7 @@ jobs:
|
|||
paths:
|
||||
- ./node_modules
|
||||
|
||||
e2e-test:
|
||||
e2e-build:
|
||||
macos:
|
||||
xcode: "11.2.1"
|
||||
|
||||
|
@ -80,11 +80,73 @@ jobs:
|
|||
yarn global add detox-cli
|
||||
yarn
|
||||
|
||||
- run:
|
||||
name: Rebuild Detox framework cache
|
||||
command: |
|
||||
detox clean-framework-cache
|
||||
detox build-framework-cache
|
||||
|
||||
- run:
|
||||
name: Build
|
||||
command: |
|
||||
detox build --configuration ios.sim.release
|
||||
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths:
|
||||
- ios/build/Build/Products/Release-iphonesimulator/RocketChatRN.app
|
||||
|
||||
- save_cache:
|
||||
name: Save NPM cache
|
||||
key: node-v1-mac-{{ checksum "yarn.lock" }}
|
||||
paths:
|
||||
- node_modules
|
||||
|
||||
e2e-test:
|
||||
macos:
|
||||
xcode: "11.2.1"
|
||||
|
||||
environment:
|
||||
BASH_ENV: "~/.nvm/nvm.sh"
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- attach_workspace:
|
||||
at: .
|
||||
|
||||
- restore_cache:
|
||||
name: Restore NPM cache
|
||||
key: node-v1-mac-{{ checksum "yarn.lock" }}
|
||||
|
||||
- run:
|
||||
name: Install Node 8
|
||||
command: |
|
||||
curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.6/install.sh | bash
|
||||
source ~/.nvm/nvm.sh
|
||||
# https://github.com/creationix/nvm/issues/1394
|
||||
set +e
|
||||
nvm install 8
|
||||
|
||||
- run:
|
||||
name: Install appleSimUtils
|
||||
command: |
|
||||
brew update
|
||||
brew tap wix/brew
|
||||
brew install wix/brew/applesimutils
|
||||
|
||||
- run:
|
||||
name: Install NPM modules
|
||||
command: |
|
||||
yarn global add detox-cli
|
||||
yarn
|
||||
|
||||
- run:
|
||||
name: Rebuild Detox framework cache
|
||||
command: |
|
||||
detox clean-framework-cache
|
||||
detox build-framework-cache
|
||||
|
||||
- run:
|
||||
name: Test
|
||||
command: |
|
||||
|
@ -96,9 +158,6 @@ jobs:
|
|||
paths:
|
||||
- node_modules
|
||||
|
||||
- store_artifacts:
|
||||
path: /tmp/screenshots
|
||||
|
||||
android-build:
|
||||
<<: *defaults
|
||||
docker:
|
||||
|
@ -359,9 +418,12 @@ workflows:
|
|||
type: approval
|
||||
requires:
|
||||
- lint-testunit
|
||||
- e2e-test:
|
||||
- e2e-build:
|
||||
requires:
|
||||
- e2e-hold
|
||||
- e2e-test:
|
||||
requires:
|
||||
- e2e-build
|
||||
|
||||
- ios-build:
|
||||
requires:
|
||||
|
|
|
@ -104,7 +104,7 @@ Readme will guide you on how to config.
|
|||
| Report message | ✅ |
|
||||
| Theming | ✅ |
|
||||
| Settings -> Review the App | ✅ |
|
||||
| Settings -> Default Browser | ❌ |
|
||||
| Settings -> Default Browser | ✅ |
|
||||
| Admin panel | ✅ |
|
||||
| Reply message from notification | ✅ |
|
||||
| Unread counter banner on message list | ✅ |
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
export default {
|
||||
set: () => '',
|
||||
get: () => ''
|
||||
};
|
File diff suppressed because it is too large
Load Diff
|
@ -138,7 +138,7 @@ android {
|
|||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode VERSIONCODE as Integer
|
||||
versionName "4.4.0"
|
||||
versionName "4.5.1"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String]
|
||||
}
|
||||
|
|
|
@ -252,7 +252,9 @@ public class CustomPushNotification extends PushNotification {
|
|||
}
|
||||
|
||||
private void notificationReply(Notification.Builder notification, int notificationId, Bundle bundle) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
||||
String notId = bundle.getString("notId", "1");
|
||||
String ejson = bundle.getString("ejson", "{}");
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N || notId.equals("1") || ejson.equals("{}")) {
|
||||
return;
|
||||
}
|
||||
String label = "Reply";
|
||||
|
|
|
@ -30,7 +30,7 @@ public class Ejson {
|
|||
|
||||
public String serverURL() {
|
||||
String url = this.host;
|
||||
if (url.endsWith("/")) {
|
||||
if (url != null && url.endsWith("/")) {
|
||||
url = url.substring(0, url.length() - 1);
|
||||
}
|
||||
return url;
|
||||
|
|
|
@ -31,7 +31,7 @@ export const ROOMS = createRequestTypes('ROOMS', [
|
|||
'OPEN_SEARCH_HEADER',
|
||||
'CLOSE_SEARCH_HEADER'
|
||||
]);
|
||||
export const ROOM = createRequestTypes('ROOM', ['LEAVE', 'ERASE', 'USER_TYPING']);
|
||||
export const ROOM = createRequestTypes('ROOM', ['LEAVE', 'DELETE_INIT', 'DELETE_FINISH', 'USER_TYPING']);
|
||||
export const APP = createRequestTypes('APP', ['START', 'READY', 'INIT', 'INIT_LOCAL_SETTINGS']);
|
||||
export const MESSAGES = createRequestTypes('MESSAGES', ['REPLY_BROADCAST']);
|
||||
export const CREATE_CHANNEL = createRequestTypes('CREATE_CHANNEL', [...defaultTypes]);
|
||||
|
@ -50,7 +50,6 @@ export const SNIPPETED_MESSAGES = createRequestTypes('SNIPPETED_MESSAGES', ['OPE
|
|||
export const DEEP_LINKING = createRequestTypes('DEEP_LINKING', ['OPEN']);
|
||||
export const SORT_PREFERENCES = createRequestTypes('SORT_PREFERENCES', ['SET_ALL', 'SET']);
|
||||
export const NOTIFICATION = createRequestTypes('NOTIFICATION', ['RECEIVED', 'REMOVE']);
|
||||
export const TOGGLE_MARKDOWN = 'TOGGLE_MARKDOWN';
|
||||
export const TOGGLE_CRASH_REPORT = 'TOGGLE_CRASH_REPORT';
|
||||
export const SET_CUSTOM_EMOJIS = 'SET_CUSTOM_EMOJIS';
|
||||
export const SET_ACTIVE_USERS = 'SET_ACTIVE_USERS';
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import * as types from '../constants/types';
|
||||
import { APP } from './actionsTypes';
|
||||
|
||||
export function appStart(root) {
|
||||
export function appStart(root, text) {
|
||||
return {
|
||||
type: APP.START,
|
||||
root
|
||||
root,
|
||||
text
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -39,12 +40,6 @@ export function addSettings(settings) {
|
|||
payload: settings
|
||||
};
|
||||
}
|
||||
export function setAllSettings(settings) {
|
||||
return {
|
||||
type: types.SET_ALL_SETTINGS,
|
||||
payload: settings
|
||||
};
|
||||
}
|
||||
|
||||
export function login() {
|
||||
return {
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
import * as types from './actionsTypes';
|
||||
|
||||
export function toggleMarkdown(value) {
|
||||
return {
|
||||
type: types.TOGGLE_MARKDOWN,
|
||||
payload: value
|
||||
};
|
||||
}
|
|
@ -8,14 +8,20 @@ export function leaveRoom(rid, t) {
|
|||
};
|
||||
}
|
||||
|
||||
export function eraseRoom(rid, t) {
|
||||
export function deleteRoomInit(rid, t) {
|
||||
return {
|
||||
type: types.ROOM.ERASE,
|
||||
type: types.ROOM.DELETE_INIT,
|
||||
rid,
|
||||
t
|
||||
};
|
||||
}
|
||||
|
||||
export function deleteRoomFinish() {
|
||||
return {
|
||||
type: types.ROOM.DELETE_FINISH
|
||||
};
|
||||
}
|
||||
|
||||
export function userTyping(rid, status = true) {
|
||||
return {
|
||||
type: types.ROOM.USER_TYPING,
|
||||
|
|
|
@ -1,4 +1,22 @@
|
|||
export default {
|
||||
Accounts_AllowEmailChange: {
|
||||
type: 'valueAsBoolean'
|
||||
},
|
||||
Accounts_AllowPasswordChange: {
|
||||
type: 'valueAsBoolean'
|
||||
},
|
||||
Accounts_AllowRealNameChange: {
|
||||
type: 'valueAsBoolean'
|
||||
},
|
||||
Accounts_AllowUserAvatarChange: {
|
||||
type: 'valueAsBoolean'
|
||||
},
|
||||
Accounts_AllowUserProfileChange: {
|
||||
type: 'valueAsBoolean'
|
||||
},
|
||||
Accounts_AllowUsernameChange: {
|
||||
type: 'valueAsBoolean'
|
||||
},
|
||||
Accounts_CustomFields: {
|
||||
type: 'valueAsString'
|
||||
},
|
||||
|
@ -17,12 +35,24 @@ export default {
|
|||
Accounts_PasswordReset: {
|
||||
type: 'valueAsBoolean'
|
||||
},
|
||||
Accounts_RegistrationForm: {
|
||||
type: 'valueAsString'
|
||||
},
|
||||
Accounts_RegistrationForm_LinkReplacementText: {
|
||||
type: 'valueAsString'
|
||||
},
|
||||
Accounts_ShowFormLogin: {
|
||||
type: 'valueAsBoolean'
|
||||
},
|
||||
CROWD_Enable: {
|
||||
type: 'valueAsBoolean'
|
||||
},
|
||||
FEDERATION_Enabled: {
|
||||
type: 'valueAsBoolean'
|
||||
},
|
||||
Hide_System_Messages: {
|
||||
type: 'valueAsArray'
|
||||
},
|
||||
LDAP_Enable: {
|
||||
type: 'valueAsBoolean'
|
||||
},
|
||||
|
@ -59,6 +89,9 @@ export default {
|
|||
Message_AllowStarring: {
|
||||
type: 'valueAsBoolean'
|
||||
},
|
||||
Message_AudioRecorderEnabled: {
|
||||
type: 'valueAsBoolean'
|
||||
},
|
||||
Message_GroupingPeriod: {
|
||||
type: 'valueAsNumber'
|
||||
},
|
||||
|
@ -93,7 +126,7 @@ export default {
|
|||
type: 'valueAsBoolean'
|
||||
},
|
||||
Threads_enabled: {
|
||||
type: null
|
||||
type: 'valueAsBoolean'
|
||||
},
|
||||
FileUpload_MediaTypeWhiteList: {
|
||||
type: 'valueAsString'
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
export const SET_CURRENT_SERVER = 'SET_CURRENT_SERVER';
|
||||
export const SET_ALL_SETTINGS = 'SET_ALL_SETTINGS';
|
||||
export const SET_CUSTOM_EMOJIS = 'SET_CUSTOM_EMOJIS';
|
||||
export const ADD_SETTINGS = 'ADD_SETTINGS';
|
||||
|
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import { View } from 'react-native';
|
||||
import FastImage from 'react-native-fast-image';
|
||||
import { settings as RocketChatSettings } from '@rocket.chat/sdk';
|
||||
import Touch from '../utils/touch';
|
||||
|
||||
const formatUrl = (url, baseUrl, uriSize, avatarAuthURLFragment) => (
|
||||
|
@ -45,6 +46,7 @@ const Avatar = React.memo(({
|
|||
style={avatarStyle}
|
||||
source={{
|
||||
uri,
|
||||
headers: RocketChatSettings.customHeaders,
|
||||
priority: FastImage.priority.high
|
||||
}}
|
||||
/>
|
||||
|
|
|
@ -63,6 +63,12 @@ class MessageActions extends React.Component {
|
|||
this.EDIT_INDEX = this.options.length - 1;
|
||||
}
|
||||
|
||||
// Mark as unread
|
||||
if (message.u && message.u._id !== user.id) {
|
||||
this.options.push(I18n.t('Mark_unread'));
|
||||
this.UNREAD_INDEX = this.options.length - 1;
|
||||
}
|
||||
|
||||
// Permalink
|
||||
this.options.push(I18n.t('Permalink'));
|
||||
this.PERMALINK_INDEX = this.options.length - 1;
|
||||
|
@ -243,6 +249,30 @@ class MessageActions extends React.Component {
|
|||
editInit(message);
|
||||
}
|
||||
|
||||
handleUnread = async() => {
|
||||
const { message, room } = this.props;
|
||||
const { id: messageId, ts } = message;
|
||||
const { rid } = room;
|
||||
try {
|
||||
const db = database.active;
|
||||
const result = await RocketChat.markAsUnread({ messageId });
|
||||
if (result.success) {
|
||||
const subCollection = db.collections.get('subscriptions');
|
||||
const subRecord = await subCollection.find(rid);
|
||||
await db.action(async() => {
|
||||
try {
|
||||
await subRecord.update(sub => sub.lastOpen = ts);
|
||||
} catch {
|
||||
// do nothing
|
||||
}
|
||||
});
|
||||
Navigation.navigate('RoomsListView');
|
||||
}
|
||||
} catch (e) {
|
||||
log(e);
|
||||
}
|
||||
}
|
||||
|
||||
handleCopy = async() => {
|
||||
const { message } = this.props;
|
||||
await Clipboard.setString(message.msg);
|
||||
|
@ -349,6 +379,9 @@ class MessageActions extends React.Component {
|
|||
case this.EDIT_INDEX:
|
||||
this.handleEdit();
|
||||
break;
|
||||
case this.UNREAD_INDEX:
|
||||
this.handleUnread();
|
||||
break;
|
||||
case this.PERMALINK_INDEX:
|
||||
this.handlePermalink();
|
||||
break;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import { FlatList } from 'react-native';
|
||||
import { FlatList, View } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import equal from 'deep-equal';
|
||||
|
||||
|
@ -12,15 +12,16 @@ const Mentions = React.memo(({ mentions, trackingType, theme }) => {
|
|||
return null;
|
||||
}
|
||||
return (
|
||||
<FlatList
|
||||
testID='messagebox-container'
|
||||
style={[styles.mentionList, { backgroundColor: themes[theme].auxiliaryBackground }]}
|
||||
data={mentions}
|
||||
extraData={mentions}
|
||||
renderItem={({ item }) => <MentionItem item={item} trackingType={trackingType} theme={theme} />}
|
||||
keyExtractor={item => item.id || item.username || item.command || item}
|
||||
keyboardShouldPersistTaps='always'
|
||||
/>
|
||||
<View testID='messagebox-container'>
|
||||
<FlatList
|
||||
style={[styles.mentionList, { backgroundColor: themes[theme].auxiliaryBackground }]}
|
||||
data={mentions}
|
||||
extraData={mentions}
|
||||
renderItem={({ item }) => <MentionItem item={item} trackingType={trackingType} theme={theme} />}
|
||||
keyExtractor={item => item.id || item.username || item.command || item}
|
||||
keyboardShouldPersistTaps='always'
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}, (prevProps, nextProps) => {
|
||||
if (prevProps.theme !== nextProps.theme) {
|
||||
|
|
|
@ -42,7 +42,7 @@ const styles = StyleSheet.create({
|
|||
});
|
||||
|
||||
const ReplyPreview = React.memo(({
|
||||
message, Message_TimeFormat, baseUrl, username, useMarkdown, replying, getCustomEmoji, close, theme
|
||||
message, Message_TimeFormat, baseUrl, username, replying, getCustomEmoji, close, theme
|
||||
}) => {
|
||||
if (!replying) {
|
||||
return null;
|
||||
|
@ -67,7 +67,6 @@ const ReplyPreview = React.memo(({
|
|||
username={username}
|
||||
getCustomEmoji={getCustomEmoji}
|
||||
numberOfLines={1}
|
||||
useMarkdown={useMarkdown}
|
||||
preview
|
||||
theme={theme}
|
||||
/>
|
||||
|
@ -79,7 +78,6 @@ const ReplyPreview = React.memo(({
|
|||
|
||||
ReplyPreview.propTypes = {
|
||||
replying: PropTypes.bool,
|
||||
useMarkdown: PropTypes.bool,
|
||||
message: PropTypes.object.isRequired,
|
||||
Message_TimeFormat: PropTypes.string.isRequired,
|
||||
close: PropTypes.func.isRequired,
|
||||
|
@ -90,7 +88,6 @@ ReplyPreview.propTypes = {
|
|||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
useMarkdown: state.markdown.useMarkdown,
|
||||
Message_TimeFormat: state.settings.Message_TimeFormat,
|
||||
baseUrl: state.server.server
|
||||
});
|
||||
|
|
|
@ -4,17 +4,20 @@ import PropTypes from 'prop-types';
|
|||
import { SendButton, AudioButton, FileButton } from './buttons';
|
||||
|
||||
const RightButtons = React.memo(({
|
||||
theme, showSend, submit, recordAudioMessage, showFileActions
|
||||
theme, showSend, submit, recordAudioMessage, recordAudioMessageEnabled, showFileActions
|
||||
}) => {
|
||||
if (showSend) {
|
||||
return <SendButton onPress={submit} theme={theme} />;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<AudioButton onPress={recordAudioMessage} theme={theme} />
|
||||
<FileButton onPress={showFileActions} theme={theme} />
|
||||
</>
|
||||
);
|
||||
if (recordAudioMessageEnabled) {
|
||||
return (
|
||||
<>
|
||||
<AudioButton onPress={recordAudioMessage} theme={theme} />
|
||||
<FileButton onPress={showFileActions} theme={theme} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
return <FileButton onPress={showFileActions} theme={theme} />;
|
||||
});
|
||||
|
||||
RightButtons.propTypes = {
|
||||
|
@ -22,6 +25,7 @@ RightButtons.propTypes = {
|
|||
showSend: PropTypes.bool,
|
||||
submit: PropTypes.func.isRequired,
|
||||
recordAudioMessage: PropTypes.func.isRequired,
|
||||
recordAudioMessageEnabled: PropTypes.bool,
|
||||
showFileActions: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
|
|
|
@ -4,19 +4,23 @@ import PropTypes from 'prop-types';
|
|||
import { SendButton, AudioButton } from './buttons';
|
||||
|
||||
const RightButtons = React.memo(({
|
||||
theme, showSend, submit, recordAudioMessage
|
||||
theme, showSend, submit, recordAudioMessage, recordAudioMessageEnabled
|
||||
}) => {
|
||||
if (showSend) {
|
||||
return <SendButton theme={theme} onPress={submit} />;
|
||||
}
|
||||
return <AudioButton theme={theme} onPress={recordAudioMessage} />;
|
||||
if (recordAudioMessageEnabled) {
|
||||
return <AudioButton theme={theme} onPress={recordAudioMessage} />;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
RightButtons.propTypes = {
|
||||
theme: PropTypes.string,
|
||||
showSend: PropTypes.bool,
|
||||
submit: PropTypes.func.isRequired,
|
||||
recordAudioMessage: PropTypes.func.isRequired
|
||||
recordAudioMessage: PropTypes.func.isRequired,
|
||||
recordAudioMessageEnabled: PropTypes.bool
|
||||
};
|
||||
|
||||
export default RightButtons;
|
||||
|
|
|
@ -85,13 +85,15 @@ class MessageBox extends Component {
|
|||
replyWithMention: PropTypes.bool,
|
||||
FileUpload_MediaTypeWhiteList: PropTypes.string,
|
||||
FileUpload_MaxFileSize: PropTypes.number,
|
||||
Message_AudioRecorderEnabled: PropTypes.bool,
|
||||
getCustomEmoji: PropTypes.func,
|
||||
editCancel: PropTypes.func.isRequired,
|
||||
editRequest: PropTypes.func.isRequired,
|
||||
onSubmit: PropTypes.func.isRequired,
|
||||
typing: PropTypes.func,
|
||||
theme: PropTypes.string,
|
||||
replyCancel: PropTypes.func
|
||||
replyCancel: PropTypes.func,
|
||||
navigation: PropTypes.object
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
|
@ -139,7 +141,7 @@ class MessageBox extends Component {
|
|||
|
||||
async componentDidMount() {
|
||||
const db = database.active;
|
||||
const { rid, tmid } = this.props;
|
||||
const { rid, tmid, navigation } = this.props;
|
||||
let msg;
|
||||
try {
|
||||
const threadsCollection = db.collections.get('threads');
|
||||
|
@ -177,6 +179,12 @@ class MessageBox extends Component {
|
|||
if (isTablet) {
|
||||
EventEmiter.addEventListener(KEY_COMMAND, this.handleCommands);
|
||||
}
|
||||
|
||||
this.didFocusListener = navigation.addListener('didFocus', () => {
|
||||
if (this.tracking && this.tracking.resetTracking) {
|
||||
this.tracking.resetTracking();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
|
@ -257,6 +265,9 @@ class MessageBox extends Component {
|
|||
if (this.getSlashCommands && this.getSlashCommands.stop) {
|
||||
this.getSlashCommands.stop();
|
||||
}
|
||||
if (this.didFocusListener && this.didFocusListener.remove) {
|
||||
this.didFocusListener.remove();
|
||||
}
|
||||
if (isTablet) {
|
||||
EventEmiter.removeListener(KEY_COMMAND, this.handleCommands);
|
||||
}
|
||||
|
@ -781,7 +792,7 @@ class MessageBox extends Component {
|
|||
recording, showEmojiKeyboard, showSend, mentions, trackingType, commandPreview, showCommandPreview
|
||||
} = this.state;
|
||||
const {
|
||||
editing, message, replying, replyCancel, user, getCustomEmoji, theme
|
||||
editing, message, replying, replyCancel, user, getCustomEmoji, theme, Message_AudioRecorderEnabled
|
||||
} = this.props;
|
||||
|
||||
const isAndroidTablet = isTablet && isAndroid ? {
|
||||
|
@ -842,6 +853,7 @@ class MessageBox extends Component {
|
|||
showSend={showSend}
|
||||
submit={this.submit}
|
||||
recordAudioMessage={this.recordAudioMessage}
|
||||
recordAudioMessageEnabled={Message_AudioRecorderEnabled}
|
||||
showFileActions={this.showFileActions}
|
||||
/>
|
||||
</View>
|
||||
|
@ -864,6 +876,7 @@ class MessageBox extends Component {
|
|||
}}
|
||||
>
|
||||
<KeyboardAccessoryView
|
||||
ref={ref => this.tracking = ref}
|
||||
renderContent={this.renderContent}
|
||||
kbInputRef={this.component}
|
||||
kbComponent={showEmojiKeyboard ? 'EmojiKeyboard' : null}
|
||||
|
@ -891,7 +904,8 @@ const mapStateToProps = state => ({
|
|||
threadsEnabled: state.settings.Threads_enabled,
|
||||
user: getUserSelector(state),
|
||||
FileUpload_MediaTypeWhiteList: state.settings.FileUpload_MediaTypeWhiteList,
|
||||
FileUpload_MaxFileSize: state.settings.FileUpload_MaxFileSize
|
||||
FileUpload_MaxFileSize: state.settings.FileUpload_MaxFileSize,
|
||||
Message_AudioRecorderEnabled: state.settings.Message_AudioRecorderEnabled
|
||||
});
|
||||
|
||||
const dispatchToProps = ({
|
||||
|
|
|
@ -20,7 +20,7 @@ const Chip = ({ item, onSelect, theme }) => (
|
|||
>
|
||||
<>
|
||||
{item.imageUrl ? <Image style={styles.chipImage} source={{ uri: item.imageUrl }} /> : null}
|
||||
<Text style={[styles.chipText, { color: themes[theme].titleText }]}>{textParser([item.text])}</Text>
|
||||
<Text numberOfLines={1} style={[styles.chipText, { color: themes[theme].titleText }]}>{textParser([item.text])}</Text>
|
||||
<CustomIcon name='cross' size={16} color={themes[theme].auxiliaryText} />
|
||||
</>
|
||||
</Touchable>
|
||||
|
|
|
@ -41,6 +41,12 @@ export const MultiSelect = React.memo(({
|
|||
const [currentValue, setCurrentValue] = useState('');
|
||||
const [showContent, setShowContent] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (values) {
|
||||
select(values);
|
||||
}
|
||||
}, [values]);
|
||||
|
||||
useEffect(() => {
|
||||
setOpen(showContent);
|
||||
}, [showContent]);
|
||||
|
|
|
@ -34,6 +34,7 @@ export default StyleSheet.create({
|
|||
},
|
||||
item: {
|
||||
height: 48,
|
||||
maxWidth: '85%',
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row'
|
||||
},
|
||||
|
@ -59,7 +60,7 @@ export default StyleSheet.create({
|
|||
chips: {
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
marginRight: 16
|
||||
marginRight: 50
|
||||
},
|
||||
chip: {
|
||||
flexDirection: 'row',
|
||||
|
@ -72,6 +73,7 @@ export default StyleSheet.create({
|
|||
},
|
||||
chipText: {
|
||||
paddingHorizontal: 8,
|
||||
flexShrink: 1,
|
||||
...sharedStyles.textMedium,
|
||||
fontSize: 14
|
||||
},
|
||||
|
|
|
@ -7,7 +7,7 @@ import { themes } from '../../constants/colors';
|
|||
import styles from './styles';
|
||||
|
||||
const AtMention = React.memo(({
|
||||
mention, mentions, username, navToRoomInfo, preview, style = [], theme
|
||||
mention, mentions, username, navToRoomInfo, style = [], useRealName, theme
|
||||
}) => {
|
||||
let mentionStyle = { ...styles.mention, color: themes[theme].buttonText };
|
||||
if (mention === 'all' || mention === 'here') {
|
||||
|
@ -27,22 +27,23 @@ const AtMention = React.memo(({
|
|||
};
|
||||
}
|
||||
|
||||
const user = mentions && mentions.length && mentions.find(m => m.username === mention);
|
||||
|
||||
const handlePress = () => {
|
||||
const index = mentions.findIndex(m => m.username === mention);
|
||||
const navParam = {
|
||||
t: 'd',
|
||||
rid: mentions[index]._id
|
||||
rid: user && user._id
|
||||
};
|
||||
navToRoomInfo(navParam);
|
||||
};
|
||||
|
||||
if (mentions && mentions.length && mentions.findIndex(m => m.username === mention) !== -1) {
|
||||
if (user) {
|
||||
return (
|
||||
<Text
|
||||
style={[preview ? { ...styles.text, color: themes[theme].bodyText } : mentionStyle, ...style]}
|
||||
onPress={preview ? undefined : handlePress}
|
||||
style={[mentionStyle, ...style]}
|
||||
onPress={handlePress}
|
||||
>
|
||||
{mention}
|
||||
{useRealName && user.name ? user.name : user.username}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
@ -59,7 +60,7 @@ AtMention.propTypes = {
|
|||
username: PropTypes.string,
|
||||
navToRoomInfo: PropTypes.func,
|
||||
style: PropTypes.array,
|
||||
preview: PropTypes.bool,
|
||||
useRealName: PropTypes.bool,
|
||||
theme: PropTypes.string,
|
||||
mentions: PropTypes.oneOfType([PropTypes.array, PropTypes.object])
|
||||
};
|
||||
|
|
|
@ -7,7 +7,7 @@ import { themes } from '../../constants/colors';
|
|||
import styles from './styles';
|
||||
|
||||
const Hashtag = React.memo(({
|
||||
hashtag, channels, navToRoomInfo, preview, style = [], theme
|
||||
hashtag, channels, navToRoomInfo, style = [], theme
|
||||
}) => {
|
||||
const handlePress = () => {
|
||||
const index = channels.findIndex(channel => channel.name === hashtag);
|
||||
|
@ -21,8 +21,8 @@ const Hashtag = React.memo(({
|
|||
if (channels && channels.length && channels.findIndex(channel => channel.name === hashtag) !== -1) {
|
||||
return (
|
||||
<Text
|
||||
style={[preview ? { ...styles.text, color: themes[theme].bodyText } : styles.mention, ...style]}
|
||||
onPress={preview ? undefined : handlePress}
|
||||
style={[styles.mention, ...style]}
|
||||
onPress={handlePress}
|
||||
>
|
||||
{hashtag}
|
||||
</Text>
|
||||
|
@ -39,7 +39,6 @@ Hashtag.propTypes = {
|
|||
hashtag: PropTypes.string,
|
||||
navToRoomInfo: PropTypes.func,
|
||||
style: PropTypes.array,
|
||||
preview: PropTypes.bool,
|
||||
theme: PropTypes.string,
|
||||
channels: PropTypes.oneOfType([PropTypes.array, PropTypes.object])
|
||||
};
|
||||
|
|
|
@ -10,7 +10,7 @@ import EventEmitter from '../../utils/events';
|
|||
import I18n from '../../i18n';
|
||||
|
||||
const Link = React.memo(({
|
||||
children, link, preview, theme
|
||||
children, link, theme
|
||||
}) => {
|
||||
const handlePress = () => {
|
||||
if (!link) {
|
||||
|
@ -28,13 +28,9 @@ const Link = React.memo(({
|
|||
// if you have a [](https://rocket.chat) render https://rocket.chat
|
||||
return (
|
||||
<Text
|
||||
onPress={preview ? undefined : handlePress}
|
||||
onLongPress={preview ? undefined : onLongPress}
|
||||
style={
|
||||
!preview
|
||||
? { ...styles.link, color: themes[theme].actionTintColor }
|
||||
: { color: themes[theme].bodyText }
|
||||
}
|
||||
onPress={handlePress}
|
||||
onLongPress={onLongPress}
|
||||
style={{ ...styles.link, color: themes[theme].actionTintColor }}
|
||||
>
|
||||
{ childLength !== 0 ? children : link }
|
||||
</Text>
|
||||
|
@ -44,8 +40,7 @@ const Link = React.memo(({
|
|||
Link.propTypes = {
|
||||
children: PropTypes.node,
|
||||
link: PropTypes.string,
|
||||
theme: PropTypes.string,
|
||||
preview: PropTypes.bool
|
||||
theme: PropTypes.string
|
||||
};
|
||||
|
||||
export default Link;
|
||||
|
|
|
@ -3,6 +3,7 @@ import { Text, Image } from 'react-native';
|
|||
import { Parser, Node } from 'commonmark';
|
||||
import Renderer from 'commonmark-react-renderer';
|
||||
import PropTypes from 'prop-types';
|
||||
import removeMarkdown from 'remove-markdown';
|
||||
|
||||
import shortnameToUnicode from '../../utils/shortnameToUnicode';
|
||||
import I18n from '../../i18n';
|
||||
|
@ -18,6 +19,7 @@ import MarkdownEmoji from './Emoji';
|
|||
import MarkdownTable from './Table';
|
||||
import MarkdownTableRow from './TableRow';
|
||||
import MarkdownTableCell from './TableCell';
|
||||
import mergeTextNodes from './mergeTextNodes';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
|
@ -71,22 +73,23 @@ class Markdown extends PureComponent {
|
|||
tmid: PropTypes.string,
|
||||
isEdited: PropTypes.bool,
|
||||
numberOfLines: PropTypes.number,
|
||||
useMarkdown: PropTypes.bool,
|
||||
customEmojis: PropTypes.bool,
|
||||
useRealName: PropTypes.bool,
|
||||
channels: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
|
||||
mentions: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
|
||||
navToRoomInfo: PropTypes.func,
|
||||
preview: PropTypes.bool,
|
||||
theme: PropTypes.string,
|
||||
testID: PropTypes.string,
|
||||
style: PropTypes.array
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.renderer = this.createRenderer(props.preview);
|
||||
this.renderer = this.createRenderer();
|
||||
}
|
||||
|
||||
createRenderer = (preview = false) => new Renderer({
|
||||
createRenderer = () => new Renderer({
|
||||
renderers: {
|
||||
text: this.renderText,
|
||||
|
||||
|
@ -119,7 +122,7 @@ class Markdown extends PureComponent {
|
|||
table_row: this.renderTableRow,
|
||||
table_cell: this.renderTableCell,
|
||||
|
||||
editedIndicator: preview ? () => null : this.renderEditedIndicator
|
||||
editedIndicator: this.renderEditedIndicator
|
||||
},
|
||||
renderParagraphsInLists: true
|
||||
});
|
||||
|
@ -141,19 +144,16 @@ class Markdown extends PureComponent {
|
|||
|
||||
renderText = ({ context, literal }) => {
|
||||
const {
|
||||
numberOfLines, preview, style = []
|
||||
numberOfLines, style = []
|
||||
} = this.props;
|
||||
const defaultStyle = [
|
||||
this.isMessageContainsOnlyEmoji && !preview ? styles.textBig : {},
|
||||
this.isMessageContainsOnlyEmoji ? styles.textBig : {},
|
||||
...context.map(type => styles[type])
|
||||
];
|
||||
return (
|
||||
<Text
|
||||
style={[
|
||||
styles.text,
|
||||
!preview ? defaultStyle : {},
|
||||
...style
|
||||
]}
|
||||
accessibilityLabel={literal}
|
||||
style={[styles.text, defaultStyle, ...style]}
|
||||
numberOfLines={numberOfLines}
|
||||
>
|
||||
{literal}
|
||||
|
@ -162,18 +162,16 @@ class Markdown extends PureComponent {
|
|||
}
|
||||
|
||||
renderCodeInline = ({ literal }) => {
|
||||
const { preview, theme, style = [] } = this.props;
|
||||
const { theme, style = [] } = this.props;
|
||||
return (
|
||||
<Text
|
||||
style={[
|
||||
!preview
|
||||
? {
|
||||
...styles.codeInline,
|
||||
color: themes[theme].bodyText,
|
||||
backgroundColor: themes[theme].bannerBackground,
|
||||
borderColor: themes[theme].bannerBackground
|
||||
}
|
||||
: { ...styles.text, color: themes[theme].bodyText },
|
||||
{
|
||||
...styles.codeInline,
|
||||
color: themes[theme].bodyText,
|
||||
backgroundColor: themes[theme].bannerBackground,
|
||||
borderColor: themes[theme].bannerBackground
|
||||
},
|
||||
...style
|
||||
]}
|
||||
>
|
||||
|
@ -183,18 +181,16 @@ class Markdown extends PureComponent {
|
|||
};
|
||||
|
||||
renderCodeBlock = ({ literal }) => {
|
||||
const { preview, theme, style = [] } = this.props;
|
||||
const { theme, style = [] } = this.props;
|
||||
return (
|
||||
<Text
|
||||
style={[
|
||||
!preview
|
||||
? {
|
||||
...styles.codeBlock,
|
||||
color: themes[theme].bodyText,
|
||||
backgroundColor: themes[theme].bannerBackground,
|
||||
borderColor: themes[theme].bannerBackground
|
||||
}
|
||||
: { ...styles.text, color: themes[theme].bodyText },
|
||||
{
|
||||
...styles.codeBlock,
|
||||
color: themes[theme].bodyText,
|
||||
backgroundColor: themes[theme].bannerBackground,
|
||||
borderColor: themes[theme].bannerBackground
|
||||
},
|
||||
...style
|
||||
]}
|
||||
>
|
||||
|
@ -221,11 +217,10 @@ class Markdown extends PureComponent {
|
|||
};
|
||||
|
||||
renderLink = ({ children, href }) => {
|
||||
const { preview, theme } = this.props;
|
||||
const { theme } = this.props;
|
||||
return (
|
||||
<MarkdownLink
|
||||
link={href}
|
||||
preview={preview}
|
||||
theme={theme}
|
||||
>
|
||||
{children}
|
||||
|
@ -235,14 +230,13 @@ class Markdown extends PureComponent {
|
|||
|
||||
renderHashtag = ({ hashtag }) => {
|
||||
const {
|
||||
channels, navToRoomInfo, style, preview, theme
|
||||
channels, navToRoomInfo, style, theme
|
||||
} = this.props;
|
||||
return (
|
||||
<MarkdownHashtag
|
||||
hashtag={hashtag}
|
||||
channels={channels}
|
||||
navToRoomInfo={navToRoomInfo}
|
||||
preview={preview}
|
||||
theme={theme}
|
||||
style={style}
|
||||
/>
|
||||
|
@ -251,15 +245,15 @@ class Markdown extends PureComponent {
|
|||
|
||||
renderAtMention = ({ mentionName }) => {
|
||||
const {
|
||||
username, mentions, navToRoomInfo, preview, style, theme
|
||||
username, mentions, navToRoomInfo, useRealName, style, theme
|
||||
} = this.props;
|
||||
return (
|
||||
<MarkdownAtMention
|
||||
mentions={mentions}
|
||||
mention={mentionName}
|
||||
useRealName={useRealName}
|
||||
username={username}
|
||||
navToRoomInfo={navToRoomInfo}
|
||||
preview={preview}
|
||||
theme={theme}
|
||||
style={style}
|
||||
/>
|
||||
|
@ -268,13 +262,13 @@ class Markdown extends PureComponent {
|
|||
|
||||
renderEmoji = ({ emojiName, literal }) => {
|
||||
const {
|
||||
getCustomEmoji, baseUrl, customEmojis = true, preview, style, theme
|
||||
getCustomEmoji, baseUrl, customEmojis = true, style, theme
|
||||
} = this.props;
|
||||
return (
|
||||
<MarkdownEmoji
|
||||
emojiName={emojiName}
|
||||
literal={literal}
|
||||
isMessageContainsOnlyEmoji={this.isMessageContainsOnlyEmoji && !preview}
|
||||
isMessageContainsOnlyEmoji={this.isMessageContainsOnlyEmoji}
|
||||
getCustomEmoji={getCustomEmoji}
|
||||
baseUrl={baseUrl}
|
||||
customEmojis={customEmojis}
|
||||
|
@ -335,10 +329,7 @@ class Markdown extends PureComponent {
|
|||
};
|
||||
|
||||
renderBlockQuote = ({ children }) => {
|
||||
const { preview, theme } = this.props;
|
||||
if (preview) {
|
||||
return children;
|
||||
}
|
||||
const { theme } = this.props;
|
||||
return (
|
||||
<MarkdownBlockQuote theme={theme}>
|
||||
{children}
|
||||
|
@ -367,7 +358,7 @@ class Markdown extends PureComponent {
|
|||
|
||||
render() {
|
||||
const {
|
||||
msg, useMarkdown = true, numberOfLines, preview = false, theme
|
||||
msg, numberOfLines, preview = false, theme, style = [], testID
|
||||
} = this.props;
|
||||
|
||||
if (!msg) {
|
||||
|
@ -379,23 +370,22 @@ class Markdown extends PureComponent {
|
|||
// Ex: '[ ](https://open.rocket.chat/group/test?msg=abcdef) Test'
|
||||
// Return: 'Test'
|
||||
m = m.replace(/^\[([\s]]*)\]\(([^)]*)\)\s/, '').trim();
|
||||
m = shortnameToUnicode(m);
|
||||
|
||||
if (preview) {
|
||||
m = m.split('\n').reduce((lines, line) => `${ lines } ${ line }`, '');
|
||||
const ast = parser.parse(m);
|
||||
return this.renderer.render(ast);
|
||||
m = m.replace(/\n+/g, ' ');
|
||||
m = shortnameToUnicode(m);
|
||||
m = removeMarkdown(m);
|
||||
return (
|
||||
<Text accessibilityLabel={m} style={[styles.text, { color: themes[theme].bodyText }, ...style]} numberOfLines={numberOfLines} testID={testID}>
|
||||
{m}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
if (!useMarkdown && !preview) {
|
||||
return <Text style={[styles.text, { color: themes[theme].bodyText }]} numberOfLines={numberOfLines}>{m}</Text>;
|
||||
}
|
||||
|
||||
const ast = parser.parse(m);
|
||||
let ast = parser.parse(m);
|
||||
ast = mergeTextNodes(ast);
|
||||
this.isMessageContainsOnlyEmoji = isOnlyEmoji(m) && emojiCount(m) <= 3;
|
||||
|
||||
this.editedMessage(ast);
|
||||
|
||||
return this.renderer.render(ast);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
// TODO: should we add this to our commonmark fork instead?
|
||||
// we loop through nodes and try to merge all texts
|
||||
export default function mergeTextNodes(ast) {
|
||||
// https://github.com/commonmark/commonmark.js/blob/master/lib/node.js#L268
|
||||
const walker = ast.walker();
|
||||
let event;
|
||||
// eslint-disable-next-line no-cond-assign
|
||||
while (event = walker.next()) {
|
||||
const { entering, node } = event;
|
||||
const { type } = node;
|
||||
if (entering && type === 'text') {
|
||||
while (node._next && node._next.type === 'text') {
|
||||
const next = node._next;
|
||||
node.literal += next.literal;
|
||||
node._next = next._next;
|
||||
if (node._next) {
|
||||
node._next._prev = node;
|
||||
}
|
||||
if (node._parent._lastChild === next) {
|
||||
node._parent._lastChild = node;
|
||||
}
|
||||
}
|
||||
walker.resumeAt(node, false);
|
||||
}
|
||||
}
|
||||
return ast;
|
||||
}
|
|
@ -8,7 +8,7 @@ import Video from './Video';
|
|||
import Reply from './Reply';
|
||||
|
||||
const Attachments = React.memo(({
|
||||
attachments, timeFormat, user, baseUrl, useMarkdown, showAttachment, getCustomEmoji, theme
|
||||
attachments, timeFormat, user, baseUrl, showAttachment, getCustomEmoji, theme
|
||||
}) => {
|
||||
if (!attachments || attachments.length === 0) {
|
||||
return null;
|
||||
|
@ -16,17 +16,17 @@ const Attachments = React.memo(({
|
|||
|
||||
return attachments.map((file, index) => {
|
||||
if (file.image_url) {
|
||||
return <Image key={file.image_url} file={file} user={user} baseUrl={baseUrl} showAttachment={showAttachment} getCustomEmoji={getCustomEmoji} useMarkdown={useMarkdown} theme={theme} />;
|
||||
return <Image key={file.image_url} file={file} user={user} baseUrl={baseUrl} showAttachment={showAttachment} getCustomEmoji={getCustomEmoji} theme={theme} />;
|
||||
}
|
||||
if (file.audio_url) {
|
||||
return <Audio key={file.audio_url} file={file} user={user} baseUrl={baseUrl} getCustomEmoji={getCustomEmoji} useMarkdown={useMarkdown} theme={theme} />;
|
||||
return <Audio key={file.audio_url} file={file} user={user} baseUrl={baseUrl} getCustomEmoji={getCustomEmoji} theme={theme} />;
|
||||
}
|
||||
if (file.video_url) {
|
||||
return <Video key={file.video_url} file={file} user={user} baseUrl={baseUrl} showAttachment={showAttachment} getCustomEmoji={getCustomEmoji} useMarkdown={useMarkdown} theme={theme} />;
|
||||
return <Video key={file.video_url} file={file} user={user} baseUrl={baseUrl} showAttachment={showAttachment} getCustomEmoji={getCustomEmoji} theme={theme} />;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
return <Reply key={index} index={index} attachment={file} timeFormat={timeFormat} user={user} baseUrl={baseUrl} getCustomEmoji={getCustomEmoji} useMarkdown={useMarkdown} theme={theme} />;
|
||||
return <Reply key={index} index={index} attachment={file} timeFormat={timeFormat} user={user} baseUrl={baseUrl} getCustomEmoji={getCustomEmoji} theme={theme} />;
|
||||
});
|
||||
}, (prevProps, nextProps) => isEqual(prevProps.attachments, nextProps.attachments) && prevProps.theme === nextProps.theme);
|
||||
|
||||
|
@ -35,7 +35,6 @@ Attachments.propTypes = {
|
|||
timeFormat: PropTypes.string,
|
||||
user: PropTypes.object,
|
||||
baseUrl: PropTypes.string,
|
||||
useMarkdown: PropTypes.bool,
|
||||
showAttachment: PropTypes.func,
|
||||
getCustomEmoji: PropTypes.func,
|
||||
theme: PropTypes.string
|
||||
|
|
|
@ -74,7 +74,6 @@ class Audio extends React.Component {
|
|||
file: PropTypes.object.isRequired,
|
||||
baseUrl: PropTypes.string.isRequired,
|
||||
user: PropTypes.object.isRequired,
|
||||
useMarkdown: PropTypes.bool,
|
||||
theme: PropTypes.string,
|
||||
split: PropTypes.bool,
|
||||
getCustomEmoji: PropTypes.func
|
||||
|
@ -157,7 +156,7 @@ class Audio extends React.Component {
|
|||
uri, paused, currentTime, duration
|
||||
} = this.state;
|
||||
const {
|
||||
user, baseUrl, file, getCustomEmoji, useMarkdown, split, theme
|
||||
user, baseUrl, file, getCustomEmoji, split, theme
|
||||
} = this.props;
|
||||
const { description } = file;
|
||||
|
||||
|
@ -182,6 +181,7 @@ class Audio extends React.Component {
|
|||
onEnd={this.onEnd}
|
||||
paused={paused}
|
||||
repeat={false}
|
||||
ignoreSilentSwitch='ignore'
|
||||
/>
|
||||
<Button paused={paused} onPress={this.togglePlayPause} theme={theme} />
|
||||
<Slider
|
||||
|
@ -199,7 +199,7 @@ class Audio extends React.Component {
|
|||
/>
|
||||
<Text style={[styles.duration, { color: themes[theme].auxiliaryText }]}>{this.duration}</Text>
|
||||
</View>
|
||||
<Markdown msg={description} baseUrl={baseUrl} username={user.username} getCustomEmoji={getCustomEmoji} useMarkdown={useMarkdown} theme={theme} />
|
||||
<Markdown msg={description} baseUrl={baseUrl} username={user.username} getCustomEmoji={getCustomEmoji} theme={theme} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ const Broadcast = React.memo(({
|
|||
background={Touchable.Ripple(themes[theme].bannerBackground)}
|
||||
style={[styles.button, { backgroundColor: themes[theme].tintColor }]}
|
||||
hitSlop={BUTTON_HIT_SLOP}
|
||||
testID='message-broadcast-reply'
|
||||
>
|
||||
<>
|
||||
<CustomIcon name='back' size={20} style={styles.buttonIcon} color={themes[theme].buttonText} />
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import { Text, View } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import equal from 'deep-equal';
|
||||
|
||||
import I18n from '../../i18n';
|
||||
import styles from './styles';
|
||||
|
@ -10,7 +11,14 @@ import { themes } from '../../constants/colors';
|
|||
|
||||
const Content = React.memo((props) => {
|
||||
if (props.isInfo) {
|
||||
return <Text style={[styles.textInfo, { color: themes[props.theme].auxiliaryText }]}>{getInfoMessage({ ...props })}</Text>;
|
||||
const infoMessage = getInfoMessage({ ...props });
|
||||
return (
|
||||
<Text
|
||||
style={[styles.textInfo, { color: themes[props.theme].auxiliaryText }]}
|
||||
accessibilityLabel={infoMessage}
|
||||
>{infoMessage}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
let content = null;
|
||||
|
@ -29,9 +37,9 @@ const Content = React.memo((props) => {
|
|||
preview={props.tmid && !props.isThreadRoom}
|
||||
channels={props.channels}
|
||||
mentions={props.mentions}
|
||||
useMarkdown={props.useMarkdown && (!props.tmid || props.isThreadRoom)}
|
||||
navToRoomInfo={props.navToRoomInfo}
|
||||
tmid={props.tmid}
|
||||
useRealName={props.useRealName}
|
||||
theme={props.theme}
|
||||
/>
|
||||
);
|
||||
|
@ -42,7 +50,24 @@ const Content = React.memo((props) => {
|
|||
{content}
|
||||
</View>
|
||||
);
|
||||
}, (prevProps, nextProps) => prevProps.isTemp === nextProps.isTemp && prevProps.msg === nextProps.msg && prevProps.theme === nextProps.theme);
|
||||
}, (prevProps, nextProps) => {
|
||||
if (prevProps.isTemp !== nextProps.isTemp) {
|
||||
return false;
|
||||
}
|
||||
if (prevProps.msg !== nextProps.msg) {
|
||||
return false;
|
||||
}
|
||||
if (prevProps.theme !== nextProps.theme) {
|
||||
return false;
|
||||
}
|
||||
if (!equal(prevProps.mentions, nextProps.mentions)) {
|
||||
return false;
|
||||
}
|
||||
if (!equal(prevProps.channels, nextProps.channels)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
Content.propTypes = {
|
||||
isTemp: PropTypes.bool,
|
||||
|
@ -52,13 +77,13 @@ Content.propTypes = {
|
|||
msg: PropTypes.string,
|
||||
theme: PropTypes.string,
|
||||
isEdited: PropTypes.bool,
|
||||
useMarkdown: PropTypes.bool,
|
||||
baseUrl: PropTypes.string,
|
||||
user: PropTypes.object,
|
||||
getCustomEmoji: PropTypes.func,
|
||||
channels: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
|
||||
mentions: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
|
||||
navToRoomInfo: PropTypes.func
|
||||
navToRoomInfo: PropTypes.func,
|
||||
useRealName: PropTypes.bool
|
||||
};
|
||||
Content.displayName = 'MessageContent';
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ export const MessageImage = React.memo(({ img, theme }) => (
|
|||
));
|
||||
|
||||
const ImageContainer = React.memo(({
|
||||
file, imageUrl, baseUrl, user, useMarkdown, showAttachment, getCustomEmoji, split, theme
|
||||
file, imageUrl, baseUrl, user, showAttachment, getCustomEmoji, split, theme
|
||||
}) => {
|
||||
const img = imageUrl || formatAttachmentUrl(file.image_url, user.id, user.token, baseUrl);
|
||||
if (!img) {
|
||||
|
@ -55,7 +55,7 @@ const ImageContainer = React.memo(({
|
|||
<Button split={split} theme={theme} onPress={onPress}>
|
||||
<View>
|
||||
<MessageImage img={img} theme={theme} />
|
||||
<Markdown msg={file.description} baseUrl={baseUrl} username={user.username} getCustomEmoji={getCustomEmoji} useMarkdown={useMarkdown} theme={theme} />
|
||||
<Markdown msg={file.description} baseUrl={baseUrl} username={user.username} getCustomEmoji={getCustomEmoji} theme={theme} />
|
||||
</View>
|
||||
</Button>
|
||||
);
|
||||
|
@ -73,7 +73,6 @@ ImageContainer.propTypes = {
|
|||
imageUrl: PropTypes.string,
|
||||
baseUrl: PropTypes.string,
|
||||
user: PropTypes.object,
|
||||
useMarkdown: PropTypes.bool,
|
||||
showAttachment: PropTypes.func,
|
||||
theme: PropTypes.string,
|
||||
getCustomEmoji: PropTypes.func,
|
||||
|
|
|
@ -79,7 +79,7 @@ const Title = React.memo(({ attachment, timeFormat, theme }) => {
|
|||
});
|
||||
|
||||
const Description = React.memo(({
|
||||
attachment, baseUrl, user, getCustomEmoji, useMarkdown, theme
|
||||
attachment, baseUrl, user, getCustomEmoji, theme
|
||||
}) => {
|
||||
const text = attachment.text || attachment.title;
|
||||
if (!text) {
|
||||
|
@ -91,7 +91,6 @@ const Description = React.memo(({
|
|||
baseUrl={baseUrl}
|
||||
username={user.username}
|
||||
getCustomEmoji={getCustomEmoji}
|
||||
useMarkdown={useMarkdown}
|
||||
theme={theme}
|
||||
/>
|
||||
);
|
||||
|
@ -125,7 +124,7 @@ const Fields = React.memo(({ attachment, theme }) => {
|
|||
}, (prevProps, nextProps) => isEqual(prevProps.attachment.fields, nextProps.attachment.fields) && prevProps.theme === nextProps.theme);
|
||||
|
||||
const Reply = React.memo(({
|
||||
attachment, timeFormat, baseUrl, user, index, getCustomEmoji, useMarkdown, split, theme
|
||||
attachment, timeFormat, baseUrl, user, index, getCustomEmoji, split, theme
|
||||
}) => {
|
||||
if (!attachment) {
|
||||
return null;
|
||||
|
@ -164,7 +163,6 @@ const Reply = React.memo(({
|
|||
baseUrl={baseUrl}
|
||||
user={user}
|
||||
getCustomEmoji={getCustomEmoji}
|
||||
useMarkdown={useMarkdown}
|
||||
theme={theme}
|
||||
/>
|
||||
<Fields attachment={attachment} theme={theme} />
|
||||
|
@ -179,7 +177,6 @@ Reply.propTypes = {
|
|||
baseUrl: PropTypes.string,
|
||||
user: PropTypes.object,
|
||||
index: PropTypes.number,
|
||||
useMarkdown: PropTypes.bool,
|
||||
theme: PropTypes.string,
|
||||
getCustomEmoji: PropTypes.func,
|
||||
split: PropTypes.bool
|
||||
|
@ -197,7 +194,6 @@ Description.propTypes = {
|
|||
attachment: PropTypes.object,
|
||||
baseUrl: PropTypes.string,
|
||||
user: PropTypes.object,
|
||||
useMarkdown: PropTypes.bool,
|
||||
getCustomEmoji: PropTypes.func,
|
||||
theme: PropTypes.string
|
||||
};
|
||||
|
|
|
@ -27,7 +27,7 @@ const styles = StyleSheet.create({
|
|||
});
|
||||
|
||||
const Video = React.memo(({
|
||||
file, baseUrl, user, useMarkdown, showAttachment, getCustomEmoji, theme
|
||||
file, baseUrl, user, showAttachment, getCustomEmoji, theme
|
||||
}) => {
|
||||
if (!baseUrl) {
|
||||
return null;
|
||||
|
@ -54,7 +54,7 @@ const Video = React.memo(({
|
|||
color={themes[theme].buttonText}
|
||||
/>
|
||||
</Touchable>
|
||||
<Markdown msg={file.description} baseUrl={baseUrl} username={user.username} getCustomEmoji={getCustomEmoji} useMarkdown={useMarkdown} theme={theme} />
|
||||
<Markdown msg={file.description} baseUrl={baseUrl} username={user.username} getCustomEmoji={getCustomEmoji} theme={theme} />
|
||||
</>
|
||||
);
|
||||
}, (prevProps, nextProps) => isEqual(prevProps.file, nextProps.file) && prevProps.theme === nextProps.theme);
|
||||
|
@ -63,7 +63,6 @@ Video.propTypes = {
|
|||
file: PropTypes.object,
|
||||
baseUrl: PropTypes.string,
|
||||
user: PropTypes.object,
|
||||
useMarkdown: PropTypes.bool,
|
||||
showAttachment: PropTypes.func,
|
||||
getCustomEmoji: PropTypes.func,
|
||||
theme: PropTypes.string
|
||||
|
|
|
@ -28,7 +28,6 @@ class MessageContainer extends React.Component {
|
|||
isReadReceiptEnabled: PropTypes.bool,
|
||||
isThreadRoom: PropTypes.bool,
|
||||
useRealName: PropTypes.bool,
|
||||
useMarkdown: PropTypes.bool,
|
||||
autoTranslateRoom: PropTypes.bool,
|
||||
autoTranslateLanguage: PropTypes.string,
|
||||
status: PropTypes.number,
|
||||
|
@ -227,7 +226,7 @@ class MessageContainer extends React.Component {
|
|||
|
||||
render() {
|
||||
const {
|
||||
item, user, style, archived, baseUrl, useRealName, broadcast, fetchThreadName, customThreadTimeFormat, showAttachment, timeFormat, useMarkdown, isReadReceiptEnabled, autoTranslateRoom, autoTranslateLanguage, navToRoomInfo, getCustomEmoji, isThreadRoom, callJitsi, blockAction, rid, theme
|
||||
item, user, style, archived, baseUrl, useRealName, broadcast, fetchThreadName, customThreadTimeFormat, showAttachment, timeFormat, isReadReceiptEnabled, autoTranslateRoom, autoTranslateLanguage, navToRoomInfo, getCustomEmoji, isThreadRoom, callJitsi, blockAction, rid, theme
|
||||
} = this.props;
|
||||
const {
|
||||
id, msg, ts, attachments, urls, reactions, t, avatar, u, alias, editedBy, role, drid, dcount, dlm, tmid, tcount, tlm, tmsg, mentions, channels, unread, blocks, autoTranslate: autoTranslateMessage
|
||||
|
@ -272,7 +271,6 @@ class MessageContainer extends React.Component {
|
|||
tcount={tcount}
|
||||
tlm={tlm}
|
||||
tmsg={tmsg}
|
||||
useMarkdown={useMarkdown}
|
||||
fetchThreadName={fetchThreadName}
|
||||
mentions={mentions}
|
||||
channels={channels}
|
||||
|
|
|
@ -12,6 +12,7 @@ import zhCN from './locales/zh-CN';
|
|||
import ptPT from './locales/pt-PT';
|
||||
import esES from './locales/es-ES';
|
||||
import it from './locales/it';
|
||||
import ja from './locales/ja';
|
||||
|
||||
i18n.translations = {
|
||||
en,
|
||||
|
@ -23,7 +24,8 @@ i18n.translations = {
|
|||
'pt-PT': ptPT,
|
||||
'es-ES': esES,
|
||||
nl,
|
||||
it
|
||||
it,
|
||||
ja
|
||||
};
|
||||
i18n.fallbacks = true;
|
||||
|
||||
|
|
|
@ -112,6 +112,7 @@ export default {
|
|||
Back: 'Zurück',
|
||||
Black: 'Schwarz',
|
||||
Block_user: 'Benutzer blockieren',
|
||||
Browser: 'Browser',
|
||||
Broadcast_channel_Description: 'Nur autorisierte Benutzer können neue Nachrichten schreiben, die anderen Benutzer können jedoch antworten',
|
||||
Broadcast_Channel: 'Broadcastkanal',
|
||||
Busy: 'Beschäftigt',
|
||||
|
@ -129,9 +130,11 @@ export default {
|
|||
Click_to_join: 'Klicken um teilzunehmen!',
|
||||
Close: 'Schließen',
|
||||
Close_emoji_selector: 'Schließen Sie die Emoji-Auswahl',
|
||||
Change_language_loading: 'Ändere Sprache.',
|
||||
Choose: 'Wählen',
|
||||
Choose_from_library: 'Aus der Bibliothek auswählen',
|
||||
Choose_file: 'Datei auswählen',
|
||||
Choose_where_you_want_links_be_opened: 'Entscheide, wie Links geöffnet werden sollen',
|
||||
Code: 'Code',
|
||||
Collaborative: 'Kollaborativ',
|
||||
Confirm: 'Bestätigen',
|
||||
|
@ -148,6 +151,7 @@ export default {
|
|||
Permalink: 'Permalink',
|
||||
Certificate_password: 'Zertifikats-Passwort',
|
||||
Clear_cache: 'Lokalen Server-Cache leeren',
|
||||
Clear_cache_loading: 'Leere Cache.',
|
||||
Whats_the_password_for_your_certificate: 'Wie lautet das Passwort für Ihr Zertifikat?',
|
||||
Create_account: 'Ein Konto erstellen',
|
||||
Create_Channel: 'Kanal erstellen',
|
||||
|
@ -157,10 +161,12 @@ export default {
|
|||
Dark: 'Dunkel',
|
||||
Dark_level: 'Dunkelstufe',
|
||||
Default: 'Standard',
|
||||
Default_browser: 'Standard-Browser',
|
||||
Delete_Room_Warning: 'Durch das Löschen eines Raums werden alle Nachrichten gelöscht, die im Raum gepostet wurden. Das kann nicht rückgängig gemacht werden.',
|
||||
delete: 'löschen',
|
||||
Delete: 'Löschen',
|
||||
DELETE: 'LÖSCHEN',
|
||||
deleting_room: 'lösche Raum',
|
||||
description: 'Beschreibung',
|
||||
Description: 'Beschreibung',
|
||||
DESKTOP_OPTIONS: 'Desktop-Einstellungen',
|
||||
|
@ -180,10 +186,8 @@ export default {
|
|||
EMAIL: 'EMAIL',
|
||||
email: 'Email',
|
||||
Enable_Auto_Translate: 'Automatische Übersetzung aktivieren',
|
||||
Enable_markdown: 'Markdown aktivieren',
|
||||
Enable_notifications: 'Benachrichtigungen aktivieren',
|
||||
Everyone_can_access_this_channel: 'Jeder kann auf diesen Kanal zugreifen',
|
||||
erasing_room: 'lösche Raum',
|
||||
Error_uploading: 'Fehler beim Hochladen',
|
||||
Expiration_Days: 'läuft ab (Tage)',
|
||||
Favorite: 'Favorisieren',
|
||||
|
@ -206,6 +210,7 @@ export default {
|
|||
Has_joined_the_channel: 'Ist dem Kanal beigetreten',
|
||||
Has_joined_the_conversation: 'Hat sich dem Gespräch angeschlossen',
|
||||
Has_left_the_channel: 'Hat den Kanal verlassen',
|
||||
In_app: 'In-App-Browser',
|
||||
IN_APP_AND_DESKTOP: 'IN-APP UND DESKTOP',
|
||||
In_App_and_Desktop_Alert_info: 'Zeigt ein Banner oben am Bildschirm, wenn die App geöffnet ist und eine Benachrichtigung auf dem Desktop.',
|
||||
Invisible: 'Unsichtbar',
|
||||
|
@ -232,6 +237,7 @@ export default {
|
|||
Login: 'Anmeldung',
|
||||
Login_error: 'Ihre Zugangsdaten wurden abgelehnt! Bitte versuchen Sie es erneut.',
|
||||
Login_with: 'Einloggen mit',
|
||||
Logging_out: 'Abmelden.',
|
||||
Logout: 'Abmelden',
|
||||
Max_number_of_uses: 'Maximale Anzahl der Benutzungen',
|
||||
members: 'Mitglieder',
|
||||
|
@ -269,9 +275,6 @@ export default {
|
|||
No_results_found: 'Keine Ergebnisse gefunden',
|
||||
No_starred_messages: 'Keine markierten Nachrichten',
|
||||
No_thread_messages: 'Keine Threadnachrichten',
|
||||
No_announcement_provided: 'Keine Ankündigung erfolgt.',
|
||||
No_description_provided: 'Keine Beschreibung angegeben.',
|
||||
No_topic_provided: 'Kein Thema bereitgestellt',
|
||||
No_Message: 'Keine Nachricht',
|
||||
No_messages_yet: 'Noch keine Nachrichten',
|
||||
No_Reactions: 'Keine Reaktionen',
|
||||
|
@ -298,6 +301,7 @@ export default {
|
|||
pinned: 'angeheftet',
|
||||
Pinned: 'Angeheftet',
|
||||
Please_enter_your_password: 'Bitte geben Sie Ihr Passwort ein',
|
||||
Please_wait: 'Bitte warten.',
|
||||
Preferences: 'Einstellungen',
|
||||
Preferences_saved: 'Einstellungen gespeichert!',
|
||||
Privacy_Policy: ' Datenschutzbestimmungen',
|
||||
|
@ -450,6 +454,8 @@ export default {
|
|||
Username: 'Benutzername',
|
||||
Username_or_email: 'Benutzername oder E-Mail-Adresse',
|
||||
Validating: 'Validierung',
|
||||
Verify_email_title: 'Registrierung erfolgreich!',
|
||||
Verify_email_desc: 'Wir haben dir eine Email geschickt um deine Anmeldung zu bestätigen. Wenn du keine Email erhältst, komme bitte wieder und versuche es noch einmal.',
|
||||
Video_call: 'Videoanruf',
|
||||
View_Original: 'Original anzeigen',
|
||||
Voice_call: 'Sprachanruf',
|
||||
|
@ -496,5 +502,6 @@ export default {
|
|||
New_line: 'Zeilenumbruch',
|
||||
You_will_be_logged_out_of_this_application: 'Du wirst in dieser Anwendung vom Server abgemeldet.',
|
||||
Clear: 'Löschen',
|
||||
This_will_clear_all_your_offline_data: 'Dies wird deine Offline-Daten löschen.'
|
||||
This_will_clear_all_your_offline_data: 'Dies wird deine Offline-Daten löschen.',
|
||||
Mark_unread: 'Als ungelesen markieren'
|
||||
};
|
||||
|
|
|
@ -112,6 +112,7 @@ export default {
|
|||
Back: 'Back',
|
||||
Black: 'Black',
|
||||
Block_user: 'Block user',
|
||||
Browser: 'Browser',
|
||||
Broadcast_channel_Description: 'Only authorized users can write new messages, but the other users will be able to reply',
|
||||
Broadcast_Channel: 'Broadcast Channel',
|
||||
Busy: 'Busy',
|
||||
|
@ -129,9 +130,11 @@ export default {
|
|||
Click_to_join: 'Click to Join!',
|
||||
Close: 'Close',
|
||||
Close_emoji_selector: 'Close emoji selector',
|
||||
Change_language_loading: 'Changing language.',
|
||||
Choose: 'Choose',
|
||||
Choose_from_library: 'Choose from library',
|
||||
Choose_file: 'Choose file',
|
||||
Choose_where_you_want_links_be_opened: 'Choose where you want links be opened',
|
||||
Code: 'Code',
|
||||
Collaborative: 'Collaborative',
|
||||
Confirm: 'Confirm',
|
||||
|
@ -148,6 +151,7 @@ export default {
|
|||
Permalink: 'Permalink',
|
||||
Certificate_password: 'Certificate Password',
|
||||
Clear_cache: 'Clear local server cache',
|
||||
Clear_cache_loading: 'Clearing cache.',
|
||||
Whats_the_password_for_your_certificate: 'What\'s the password for your certificate?',
|
||||
Create_account: 'Create an account',
|
||||
Create_Channel: 'Create Channel',
|
||||
|
@ -157,10 +161,12 @@ export default {
|
|||
Dark: 'Dark',
|
||||
Dark_level: 'Dark Level',
|
||||
Default: 'Default',
|
||||
Default_browser: 'Default browser',
|
||||
Delete_Room_Warning: 'Deleting a room will delete all messages posted within the room. This cannot be undone.',
|
||||
delete: 'delete',
|
||||
Delete: 'Delete',
|
||||
DELETE: 'DELETE',
|
||||
deleting_room: 'deleting room',
|
||||
description: 'description',
|
||||
Description: 'Description',
|
||||
DESKTOP_OPTIONS: 'DESKTOP OPTIONS',
|
||||
|
@ -180,10 +186,8 @@ export default {
|
|||
EMAIL: 'EMAIL',
|
||||
email: 'e-mail',
|
||||
Enable_Auto_Translate: 'Enable Auto-Translate',
|
||||
Enable_markdown: 'Enable markdown',
|
||||
Enable_notifications: 'Enable notifications',
|
||||
Everyone_can_access_this_channel: 'Everyone can access this channel',
|
||||
erasing_room: 'erasing room',
|
||||
Error_uploading: 'Error uploading',
|
||||
Expiration_Days: 'Expiration (Days)',
|
||||
Favorite: 'Favorite',
|
||||
|
@ -206,6 +210,22 @@ export default {
|
|||
Has_joined_the_channel: 'Has joined the channel',
|
||||
Has_joined_the_conversation: 'Has joined the conversation',
|
||||
Has_left_the_channel: 'Has left the channel',
|
||||
Hide_System_Messages: 'Hide System Messages',
|
||||
Hide_type_messages: 'Hide "{{type}}" messages',
|
||||
Message_HideType_uj: 'User Join',
|
||||
Message_HideType_ul: 'User Leave',
|
||||
Message_HideType_ru: 'User Removed',
|
||||
Message_HideType_au: 'User Added',
|
||||
Message_HideType_mute_unmute: 'User Muted / Unmuted',
|
||||
Message_HideType_r: 'Room Name Changed',
|
||||
Message_HideType_ut: 'User Joined Conversation',
|
||||
Message_HideType_wm: 'Welcome',
|
||||
Message_HideType_rm: 'Message Removed',
|
||||
Message_HideType_subscription_role_added: 'Was Set Role',
|
||||
Message_HideType_subscription_role_removed: 'Role No Longer Defined',
|
||||
Message_HideType_room_archived: 'Room Archived',
|
||||
Message_HideType_room_unarchived: 'Room Unarchived',
|
||||
In_app: 'In-app',
|
||||
IN_APP_AND_DESKTOP: 'IN-APP AND DESKTOP',
|
||||
In_App_and_Desktop_Alert_info: 'Displays a banner at the top of the screen when app is open, and displays a notification on desktop',
|
||||
Invisible: 'Invisible',
|
||||
|
@ -232,6 +252,7 @@ export default {
|
|||
Login: 'Login',
|
||||
Login_error: 'Your credentials were rejected! Please try again.',
|
||||
Login_with: 'Login with',
|
||||
Logging_out: 'Logging out.',
|
||||
Logout: 'Logout',
|
||||
Max_number_of_uses: 'Max number of uses',
|
||||
members: 'members',
|
||||
|
@ -269,9 +290,7 @@ export default {
|
|||
No_results_found: 'No results found',
|
||||
No_starred_messages: 'No starred messages',
|
||||
No_thread_messages: 'No thread messages',
|
||||
No_announcement_provided: 'No announcement provided.',
|
||||
No_description_provided: 'No description provided.',
|
||||
No_topic_provided: 'No topic provided.',
|
||||
No_label_provided: 'No {{label}} provided.',
|
||||
No_Message: 'No Message',
|
||||
No_messages_yet: 'No messages yet',
|
||||
No_Reactions: 'No Reactions',
|
||||
|
@ -291,6 +310,7 @@ export default {
|
|||
Only_authorized_users_can_write_new_messages: 'Only authorized users can write new messages',
|
||||
Open_emoji_selector: 'Open emoji selector',
|
||||
Open_Source_Communication: 'Open Source Communication',
|
||||
Overwrites_the_server_configuration_and_use_room_config: 'Overwrites the server configuration and use room config',
|
||||
Password: 'Password',
|
||||
Permalink_copied_to_clipboard: 'Permalink copied to clipboard!',
|
||||
Pin: 'Pin',
|
||||
|
@ -298,6 +318,7 @@ export default {
|
|||
pinned: 'pinned',
|
||||
Pinned: 'Pinned',
|
||||
Please_enter_your_password: 'Please enter your password',
|
||||
Please_wait: 'Please wait.',
|
||||
Preferences: 'Preferences',
|
||||
Preferences_saved: 'Preferences saved!',
|
||||
Privacy_Policy: ' Privacy Policy',
|
||||
|
@ -449,6 +470,7 @@ export default {
|
|||
Username_is_empty: 'Username is empty',
|
||||
Username: 'Username',
|
||||
Username_or_email: 'Username or email',
|
||||
Uses_server_configuration: 'Uses server configuration',
|
||||
Validating: 'Validating',
|
||||
Verify_email_title: 'Registration Succeeded!',
|
||||
Verify_email_desc: 'We have sent you an email to confirm your registration. If you do not receive an email shortly, please come back and try again.',
|
||||
|
@ -469,6 +491,7 @@ export default {
|
|||
You_can_search_using_RegExp_eg: 'You can use RegExp. e.g. `/^text$/i`',
|
||||
You_colon: 'You: ',
|
||||
you_were_mentioned: 'you were mentioned',
|
||||
You_were_removed_from_channel: 'You were removed from {{channel}}',
|
||||
you: 'you',
|
||||
You: 'You',
|
||||
Logged_out_by_server: 'You\'ve been logged out by the server. Please log in again.',
|
||||
|
@ -498,5 +521,6 @@ export default {
|
|||
New_line: 'New line',
|
||||
You_will_be_logged_out_of_this_application: 'You will be logged out of this application.',
|
||||
Clear: 'Clear',
|
||||
This_will_clear_all_your_offline_data: 'This will clear all your offline data.'
|
||||
This_will_clear_all_your_offline_data: 'This will clear all your offline data.',
|
||||
Mark_unread: 'Mark Unread'
|
||||
};
|
||||
|
|
|
@ -158,6 +158,7 @@ export default {
|
|||
delete: 'eliminar',
|
||||
Delete: 'Eliminar',
|
||||
DELETE: 'ELIMINAR',
|
||||
deleting_room: 'eliminando sala',
|
||||
description: 'descripción',
|
||||
Description: 'Descripción',
|
||||
DESKTOP_OPTIONS: 'OPCIONES DE ESCRITORIO',
|
||||
|
@ -176,10 +177,8 @@ export default {
|
|||
EMAIL: 'EMAIL',
|
||||
email: 'e-mail',
|
||||
Enable_Auto_Translate: 'Permitir Auto-Translate',
|
||||
Enable_markdown: 'Permitir markdown',
|
||||
Enable_notifications: 'Permitir notificaciones',
|
||||
Everyone_can_access_this_channel: 'Todos los usuarios pueden acceder a este canal',
|
||||
erasing_room: 'eliminando sala',
|
||||
Error_uploading: 'Error en la subida',
|
||||
Favorite: 'Favorito',
|
||||
Favorites: 'Favoritos',
|
||||
|
@ -256,9 +255,6 @@ export default {
|
|||
No_results_found: 'No hay resultados',
|
||||
No_starred_messages: 'No hay mensajes destacados',
|
||||
No_thread_messages: 'No hay hilots',
|
||||
No_announcement_provided: 'No se ha indicado un anuncio',
|
||||
No_description_provided: 'No se ha indicado descripción',
|
||||
No_topic_provided: 'No se ha indicado asunto.',
|
||||
No_Message: 'Sin mensajes',
|
||||
No_messages_yet: 'No hay todavía mensajes',
|
||||
No_Reactions: 'No hay reacciones',
|
||||
|
|
|
@ -138,6 +138,7 @@ export default {
|
|||
delete: 'supprimez',
|
||||
Delete: 'Supprimez',
|
||||
DELETE: 'SUPPRIMEZ',
|
||||
deleting_room: 'effacement de la salle',
|
||||
description: 'la description',
|
||||
Description: 'La description',
|
||||
Disable_notifications: 'Désactiver les notifications',
|
||||
|
@ -145,7 +146,6 @@ export default {
|
|||
Dont_Have_An_Account: 'Vous n\'avez pas de compte?',
|
||||
Do_you_really_want_to_key_this_room_question_mark: 'Voulez-vous vraiment {{key}} cette salle?',
|
||||
edit: 'modifier',
|
||||
erasing_room: 'effacement de la salle',
|
||||
Edit: 'Modifier',
|
||||
Email_or_password_field_is_empty: 'Le champ e-mail ou mot de passe est vide',
|
||||
Email: 'E-mail',
|
||||
|
@ -215,9 +215,6 @@ export default {
|
|||
No_pinned_messages: 'Aucun message épinglé',
|
||||
No_results_found: 'Aucun résultat trouvé',
|
||||
No_starred_messages: 'Pas de messages suivis',
|
||||
No_announcement_provided: 'Aucune annonce fournie.',
|
||||
No_description_provided: 'Aucune description fournie.',
|
||||
No_topic_provided: 'Aucun sujet fourni.',
|
||||
No_Message: 'Aucun message',
|
||||
No_Reactions: 'Aucune réaction',
|
||||
Not_logged: 'Non connecté',
|
||||
|
|
|
@ -160,6 +160,7 @@ export default {
|
|||
delete: 'elimina',
|
||||
Delete: 'Elimina',
|
||||
DELETE: 'ELIMINA',
|
||||
deleting_room: 'cancellazione stanza',
|
||||
description: 'descrizione',
|
||||
Description: 'Descrizione',
|
||||
DESKTOP_OPTIONS: 'OPZIONI DESKTOP',
|
||||
|
@ -179,10 +180,8 @@ export default {
|
|||
EMAIL: 'E-MAIL',
|
||||
email: 'e-mail',
|
||||
Enable_Auto_Translate: 'Abilita traduzione automatica',
|
||||
Enable_markdown: 'Abilita Markdown',
|
||||
Enable_notifications: 'Abilita notifiche',
|
||||
Everyone_can_access_this_channel: 'Tutti hanno accesso a questo canale',
|
||||
erasing_room: 'cancellazione stanza',
|
||||
Error_uploading: 'Errore nel caricamento di',
|
||||
Expiration_Days: 'Scadenza (giorni)',
|
||||
Favorite: 'Preferito',
|
||||
|
@ -267,9 +266,6 @@ export default {
|
|||
No_results_found: 'Nessun risultato',
|
||||
No_starred_messages: 'Nessun messaggio preferito',
|
||||
No_thread_messages: 'Nessun messaggio thread',
|
||||
No_announcement_provided: 'Nessun annuncio inserito.',
|
||||
No_description_provided: 'Nessuna descrizione inserita.',
|
||||
No_topic_provided: 'Nessun argomento inserito.',
|
||||
No_Message: 'Nessun messaggio',
|
||||
No_messages_yet: 'Non ci sono ancora messaggi',
|
||||
No_Reactions: 'Nessuna reazione',
|
||||
|
|
|
@ -0,0 +1,542 @@
|
|||
export default {
|
||||
'1_person_reacted': '1人がリアクション',
|
||||
'1_user': '1人',
|
||||
'error-action-not-allowed': '{{action}}の権限がありません。',
|
||||
'error-application-not-found': 'アプリケーションがありません。',
|
||||
'error-archived-duplicate-name':
|
||||
'アーカイブ名が重複しています: {{room_name}}',
|
||||
'error-avatar-invalid-url': '画像のURLが正しくありません: {{url}}',
|
||||
'error-avatar-url-handling':
|
||||
'アバターをURL({{url}})から{{username}}に設定中にエラーが発生しました。',
|
||||
'error-cant-invite-for-direct-room': 'ユーザーを直接ルームに招待することができません。',
|
||||
'error-could-not-change-email': 'メールアドレスを変更できません。',
|
||||
'error-could-not-change-name': '名前を変更できません。',
|
||||
'error-could-not-change-username': 'ユーザー名を変更できません。',
|
||||
'error-delete-protected-role': '保護されたロールは削除できません。',
|
||||
'error-department-not-found': 'ロールが存在しません。',
|
||||
'error-direct-message-file-upload-not-allowed':
|
||||
'ダイレクトメッセージでのファイルのアップロードは許可されていません。',
|
||||
'error-duplicate-channel-name': '{{channel_name}}と同名のチャンネルが存在します。',
|
||||
'error-email-domain-blacklisted': 'このドメインのメールアドレスはブラックリストに登録されています。',
|
||||
'error-email-send-failed': '次のメールアドレスの送信に失敗しました: {{message}}',
|
||||
'error-save-image': '画像の保存に失敗しました。',
|
||||
'error-field-unavailable': '{{field}}は既に使用されています。',
|
||||
'error-file-too-large': 'ファイルが大きすぎます。',
|
||||
'error-importer-not-defined':
|
||||
'インポータが正しく定義されていません。Importクラスが見つかりません。',
|
||||
'error-input-is-not-a-valid-field': '{{input}}は{{field}}の入力として正しくありません。',
|
||||
'error-invalid-actionlink': 'アクションリンクが正しくありません。',
|
||||
'error-invalid-arguments': '引数が正しくありません。',
|
||||
'error-invalid-asset': 'アセットが不正です。',
|
||||
'error-invalid-channel': 'チャンネル名が不正です。',
|
||||
'error-invalid-channel-start-with-chars':
|
||||
'不正なチャンネルです。チャンネル名は@か#から開始します。',
|
||||
'error-invalid-custom-field': 'カスタムフィールドが不正です。',
|
||||
'error-invalid-custom-field-name':
|
||||
'カスタムフィールド名が不正です。半角英数字、ハイフン、アンダースコアのみを使用してください。',
|
||||
'error-invalid-date': '不正な日時です',
|
||||
'error-invalid-description': '不正な詳細です',
|
||||
'error-invalid-domain': '不正なドメインです',
|
||||
'error-invalid-email': '不正なメールアドレスです。 {{emai}}',
|
||||
'error-invalid-email-address': '不正なメールアドレスです',
|
||||
'error-invalid-file-height': 'ファイルの高さが不正です',
|
||||
'error-invalid-file-type': 'ファイルの種類が不正です',
|
||||
'error-invalid-file-width': 'ファイルの幅が不正です',
|
||||
'error-invalid-from-address': '不正なアドレスから通知しました',
|
||||
'error-invalid-integration': '不正なインテグレーションです。',
|
||||
'error-invalid-message': '不正なメッセージです。',
|
||||
'error-invalid-method': '不正なメソッドです。',
|
||||
'error-invalid-name': '不正な名前です',
|
||||
'error-invalid-password': '不正なパスワードです',
|
||||
'error-invalid-redirectUri': '不正なリダイレクトURIです',
|
||||
'error-invalid-role': '不正なロールです',
|
||||
'error-invalid-room': '不正なルームです',
|
||||
'error-invalid-room-name': '{{room_name}}は正しいルーム名ではありません。',
|
||||
'error-invalid-room-type': '{{type}}は正しいルームタイプではありません。',
|
||||
'error-invalid-settings': '不正な設定が送信されました',
|
||||
'error-invalid-subscription': '不正な購読です',
|
||||
'error-invalid-token': 'トークンが正しくありません',
|
||||
'error-invalid-triggerWords': 'トリガーワードが正しくありません',
|
||||
'error-invalid-urls': 'URLが正しくありません',
|
||||
'error-invalid-user': 'ユーザーが正しくありません',
|
||||
'error-invalid-username': 'ユーザー名が正しくありません',
|
||||
'error-invalid-webhook-response':
|
||||
'WebhookのURLが200以外のステータスを返しています',
|
||||
'error-message-deleting-blocked': 'メッセージの削除をブロックされています。',
|
||||
'error-message-editing-blocked': 'メッセージの編集をブロックされています。',
|
||||
'error-message-size-exceeded': 'メッセージの大きさが Message_MaxAllowedSize を超えています。',
|
||||
'error-missing-unsubscribe-link': '購読停止リンクを入れてください。',
|
||||
'error-no-tokens-for-this-user': 'このユーザーにはトークンがありません。',
|
||||
'error-not-allowed': '許可されていません。',
|
||||
'error-not-authorized': '有効化されていません。',
|
||||
'error-push-disabled': 'プッシュは無効化されています。',
|
||||
'error-remove-last-owner':
|
||||
'ルームの最後のオーナーです。ルームを退出する前に新しいオーナーを設定してください。',
|
||||
'error-role-in-use': '使用中のロールを削除することはできません。',
|
||||
'error-role-name-required': 'ロール名を入力してください。',
|
||||
'error-the-field-is-required': '{{field}}の入力欄は必須です。',
|
||||
'error-too-many-requests':
|
||||
'エラーです。リクエストが多すぎます。リクエストの頻度を落としてください。{{seconds}} 秒以上待ってから再度お試しください。',
|
||||
'error-user-is-not-activated': 'アカウントが有効化されていません。',
|
||||
'error-user-has-no-roles': 'ロールがありません。',
|
||||
'error-user-limit-exceeded':
|
||||
'#channel_name に招待できるユーザー数の上限を超えています。管理者にお問い合わせください。',
|
||||
'error-user-not-in-room': 'ユーザーがルームにいません。',
|
||||
'error-user-registration-custom-field':
|
||||
'error-user-registration-custom-field',
|
||||
'error-user-registration-disabled': 'ユーザー登録は無効化されています',
|
||||
'error-user-registration-secret':
|
||||
'ユーザーの登録は登録用URLからのみ許可されています',
|
||||
'error-you-are-last-owner':
|
||||
'あなたは最後のオーナーです。ルームを退出する前に別のオーナーを設定してください。',
|
||||
Actions: 'アクション',
|
||||
activity: 'アクティビティ',
|
||||
Activity: 'アクティビティ順',
|
||||
Add_Reaction: 'リアクションを追加',
|
||||
Add_Server: 'サーバーを追加',
|
||||
Add_users: 'ユーザーを追加',
|
||||
Admin_Panel: '管理者パネル',
|
||||
Alert: 'アラート',
|
||||
alert: 'アラート',
|
||||
alerts: 'アラート',
|
||||
All_users_in_the_channel_can_write_new_messages:
|
||||
'すべてのユーザーが新しいメッセージを書き込みできます',
|
||||
All: 'すべての',
|
||||
All_Messages: '全メッセージ',
|
||||
Allow_Reactions: 'リアクションを許可',
|
||||
Alphabetical: 'アルファベット順',
|
||||
and_more: 'さらに表示',
|
||||
and: 'と',
|
||||
announcement: 'アナウンス',
|
||||
Announcement: 'アナウンス',
|
||||
Apply_Your_Certificate: '証明書を適用する',
|
||||
Applying_a_theme_will_change_how_the_app_looks:
|
||||
'テーマを変更すると見た目が変わります',
|
||||
ARCHIVE: 'アーカイブ',
|
||||
archive: 'アーカイブ',
|
||||
are_typing: 'が入力中',
|
||||
Are_you_sure_question_mark: 'よろしいですか?',
|
||||
Are_you_sure_you_want_to_leave_the_room:
|
||||
'{{room}}を退出してもよろしいですか?',
|
||||
Audio: '音声',
|
||||
Authenticating: '認証',
|
||||
Automatic: '自動',
|
||||
Auto_Translate: '自動翻訳',
|
||||
Avatar_changed_successfully: 'アバターを変更しました!',
|
||||
Avatar_Url: 'アバターURL',
|
||||
Away: '退出中',
|
||||
Back: '戻る',
|
||||
Black: 'ブラック',
|
||||
Block_user: 'ブロックしたユーザー',
|
||||
Broadcast_channel_Description:
|
||||
'許可されたユーザーのみが新しいメッセージを書き込めます。他のユーザーは返信することができます',
|
||||
Broadcast_Channel: '配信チャンネル',
|
||||
Busy: '取り込み中',
|
||||
By_proceeding_you_are_agreeing: '続行することにより、私達を承認します',
|
||||
Cancel_editing: '編集をキャンセル',
|
||||
Cancel_recording: '録音をキャンセル',
|
||||
Cancel: 'キャンセル',
|
||||
changing_avatar: 'アバターを変更',
|
||||
creating_channel: 'チャンネルを作成',
|
||||
creating_invite: '招待を作成',
|
||||
Channel_Name: 'チャンネル名',
|
||||
Channels: 'チャンネル',
|
||||
Chats: 'チャット',
|
||||
Call_already_ended: '通話は終了しています。',
|
||||
Click_to_join: 'クリックして参加!',
|
||||
Close: '閉じる',
|
||||
Close_emoji_selector: '絵文字ピッカーを閉じる',
|
||||
Choose: '選択',
|
||||
Choose_from_library: 'ライブラリから選択',
|
||||
Choose_file: 'ファイルを選択',
|
||||
Code: 'コード',
|
||||
Collaborative: 'コラボ',
|
||||
Confirm: '承認',
|
||||
Connect: '接続',
|
||||
Connect_to_a_server: 'サーバーに接続',
|
||||
Connected: '接続しました',
|
||||
connecting_server: 'サーバーに接続中',
|
||||
Connecting: '接続中...',
|
||||
Contact_us: 'お問い合わせ',
|
||||
Contact_your_server_admin: 'サーバー管理者にお問い合わせください。',
|
||||
Continue_with: '次でログイン: ',
|
||||
Copied_to_clipboard: 'クリップボードにコピー!',
|
||||
Copy: 'コピー',
|
||||
Permalink: 'パーマリンク',
|
||||
Certificate_password: 'パスワード証明書',
|
||||
Clear_cache: 'ローカルのサーバーキャッシュをクリア',
|
||||
Whats_the_password_for_your_certificate:
|
||||
'証明書のパスワードはなんですか?',
|
||||
Create_account: 'アカウントを作成',
|
||||
Create_Channel: 'チャンネルを作成',
|
||||
Created_snippet: 'スニペットを作成',
|
||||
Create_a_new_workspace: '新しいワークスペースを作成',
|
||||
Create: '作成',
|
||||
Dark: 'ダーク',
|
||||
Dark_level: 'ダークレベル',
|
||||
Default: 'デフォルト',
|
||||
Default_browser: 'デフォルトのブラウザ',
|
||||
Delete_Room_Warning:
|
||||
'ルームを削除すると、ルームに投稿されたすべてのメッセージが削除されます。この操作は取り消せません。',
|
||||
delete: '削除',
|
||||
Delete: '削除',
|
||||
DELETE: '削除',
|
||||
deleting_room: 'ルームを削除',
|
||||
description: '概要',
|
||||
Description: '概要',
|
||||
DESKTOP_OPTIONS: 'デスクトップオプション',
|
||||
Directory: 'ディレクトリ',
|
||||
Direct_Messages: 'ダイレクトメッセージ',
|
||||
Disable_notifications: '通知を無効化',
|
||||
Discussions: 'ディスカッション',
|
||||
Dont_Have_An_Account: 'アカウントがありませんか?',
|
||||
Do_you_have_a_certificate: '証明書を持っていますか?',
|
||||
Do_you_really_want_to_key_this_room_question_mark:
|
||||
'本当にこのルームを{{key}}しますか?',
|
||||
edit: '編集',
|
||||
edited: '編集済',
|
||||
Edit: '編集',
|
||||
Edit_Invite: '編集に招待',
|
||||
Email_or_password_field_is_empty: 'メールアドレスかパスワードの入力欄が空です',
|
||||
Email: 'メールアドレス',
|
||||
EMAIL: 'メールアドレス',
|
||||
email: 'メールアドレス',
|
||||
Enable_Auto_Translate: '自動翻訳を有効にする',
|
||||
Enable_markdown: 'マークダウンを有効にする',
|
||||
Enable_notifications: '通知を有効にする',
|
||||
Everyone_can_access_this_channel: '全員このチャンネルにアクセスできます',
|
||||
Error_uploading: 'アップロードエラー',
|
||||
Expiration_Days: '期限切れ (日)',
|
||||
Favorite: 'お気に入り',
|
||||
Favorites: 'お気に入り',
|
||||
Files: 'ファイル',
|
||||
File_description: 'ファイルの説明',
|
||||
File_name: 'ファイル名',
|
||||
Finish_recording: '録音停止',
|
||||
Following_thread: 'スレッド更新時に通知',
|
||||
For_your_security_you_must_enter_your_current_password_to_continue:
|
||||
'セキュリティのため、続けるには現在のパスワードを入力してください。',
|
||||
Forgot_my_password: 'パスワードを忘れた',
|
||||
Forgot_password_If_this_email_is_registered:
|
||||
'送信したメールアドレスが登録されていれば、パスワードのリセット方法を送信しました。メールアドレスがすぐに来ない場合はやり直してください。',
|
||||
Forgot_password: 'パスワードを忘れた',
|
||||
Forgot_Password: 'パスワードを忘れた',
|
||||
Full_table: 'クリックしてテーブル全体を見る',
|
||||
Generate_New_Link: '新しいリンクを生成',
|
||||
Group_by_favorites: 'お気に入りをグループ化',
|
||||
Group_by_type: 'タイプ別にグループ化',
|
||||
Hide: '隠す',
|
||||
Has_joined_the_channel: 'はチャンネルに参加しました',
|
||||
Has_joined_the_conversation: 'は会話に参加しました',
|
||||
Has_left_the_channel: 'はチャンネルを退出しました',
|
||||
IN_APP_AND_DESKTOP: 'アプリ内とデスクトップ',
|
||||
In_App_and_Desktop_Alert_info:
|
||||
'アプリを表示中にはバナーを上部に表示し、デスクトップには通知を送ります。',
|
||||
Invisible: '状態を隠す',
|
||||
Invite: '招待',
|
||||
is_a_valid_RocketChat_instance: 'は正しいRocket Chatのインスタンスです',
|
||||
is_not_a_valid_RocketChat_instance: 'はRocket Chatのインスタンスではありません',
|
||||
is_typing: 'が入力中',
|
||||
Invalid_or_expired_invite_token: '招待トークンが無効か、期限が切れています',
|
||||
Invalid_server_version:
|
||||
'接続しようとしているサーバーのバージョン({{currentVersion}})はサポートされていません。\n\nサポートする最低バージョンは {{minVersion}} です',
|
||||
Invite_Link: '招待リンク',
|
||||
Invite_users: 'ユーザーを招待',
|
||||
Join_the_community: 'コミュニティに参加',
|
||||
Join: '参加',
|
||||
Just_invited_people_can_access_this_channel:
|
||||
'招待されたユーザーだけがこのチャンネルに参加できます',
|
||||
Language: '言語',
|
||||
last_message: '最後のメッセージ',
|
||||
Leave_channel: 'チャンネルを退出',
|
||||
leaving_room: 'チャンネルを退出',
|
||||
leave: '退出',
|
||||
Legal: '法的項目',
|
||||
Light: 'ライト',
|
||||
License: 'ライセンス',
|
||||
Livechat: 'ライブチャット',
|
||||
Login: 'ログイン',
|
||||
Login_error: '証明書が承認されませんでした。再度お試しください。',
|
||||
Login_with: '次でログイン: ',
|
||||
Logout: 'ログアウト',
|
||||
Max_number_of_uses: '最大利用数',
|
||||
members: 'メンバー',
|
||||
Members: 'メンバー',
|
||||
Mentioned_Messages: 'メンションされたメッセージ',
|
||||
mentioned: 'メンション',
|
||||
Mentions: 'メンション',
|
||||
Message_accessibility: '{{user}} から {{time}} にメッセージ: {{message}}',
|
||||
Message_actions: 'メッセージアクション',
|
||||
Message_pinned: 'メッセージをピン留め',
|
||||
Message_removed: 'メッセージを除く',
|
||||
message: 'メッセージ',
|
||||
messages: 'メッセージ',
|
||||
Message: 'メッセージ',
|
||||
Messages: 'メッセージ',
|
||||
Message_Reported: 'メッセージを報告しました',
|
||||
Microphone_Permission_Message:
|
||||
'Rocket Chatは音声メッセージを送信するのにマイクのアクセスの許可が必要です。',
|
||||
Microphone_Permission: 'マイクの許可',
|
||||
Mute: 'ミュート',
|
||||
muted: 'ミュートした',
|
||||
My_servers: '自分のサーバー',
|
||||
N_people_reacted: '{{n}}人がリアクションしました',
|
||||
N_users: '{{n}}人',
|
||||
name: 'アルファベット',
|
||||
Name: '名前',
|
||||
Never: 'ずっと受け取らない',
|
||||
New_Message: 'メッセージ',
|
||||
New_Password: '新しいパスワード',
|
||||
New_Server: '新規サーバー',
|
||||
Next: '次へ',
|
||||
No_files: 'ファイルがありません',
|
||||
No_limit: '制限なし',
|
||||
No_mentioned_messages: 'メンションされたメッセージはありません',
|
||||
No_pinned_messages: 'ピン留めされたメッセージはありません',
|
||||
No_results_found: '結果なし',
|
||||
No_starred_messages: 'お気に入りされたメッセージはありません',
|
||||
No_thread_messages: 'スレッドのメッセージはありません',
|
||||
No_Message: 'メッセージなし',
|
||||
No_messages_yet: 'まだメッセージはありません',
|
||||
No_Reactions: 'リアクションなし',
|
||||
No_Read_Receipts: '未読通知はありません',
|
||||
Not_logged: 'ログされていません',
|
||||
Not_RC_Server: 'Rocket Chatサーバーではありません。\n{{contact}}',
|
||||
Nothing: '何もなし',
|
||||
Nothing_to_save: '保存するものはありません!',
|
||||
Notify_active_in_this_room: 'このルームのアクティブなユーザーに通知する',
|
||||
Notify_all_in_this_room: 'このルームのユーザー全員に通知する',
|
||||
Notifications: '通知',
|
||||
Notification_Duration: '通知の期間',
|
||||
Notification_Preferences: '通知設定',
|
||||
Offline: 'オフライン',
|
||||
Oops: 'おっと!',
|
||||
Online: 'オンライン',
|
||||
Only_authorized_users_can_write_new_messages:
|
||||
'承認されたユーザーだけが新しいメッセージを書き込めます',
|
||||
Open_emoji_selector: '絵文字ピッカーを開く',
|
||||
Open_Source_Communication: 'オープンソースコミュニケーション',
|
||||
Password: 'パスワード',
|
||||
Permalink_copied_to_clipboard: 'リンクをクリップボードにコピーしました!',
|
||||
Pin: 'ピン留め',
|
||||
Pinned_Messages: 'ピン留めされたメッセージ',
|
||||
pinned: 'ピン留めされた',
|
||||
Pinned: 'ピン留めされました',
|
||||
Please_enter_your_password: 'パスワードを入力してください',
|
||||
Preferences: '設定',
|
||||
Preferences_saved: '設定が保存されました。',
|
||||
Privacy_Policy: ' プライバシーポリシー',
|
||||
Private_Channel: 'プライベートチャンネル',
|
||||
Private_Groups: 'プライベートグループ',
|
||||
Private: 'プライベート',
|
||||
Processing: '処理中...',
|
||||
Profile_saved_successfully: 'プロフィールが保存されました!',
|
||||
Profile: 'プロフィール',
|
||||
Public_Channel: 'パブリックチャンネル',
|
||||
Public: 'パブリック',
|
||||
PUSH_NOTIFICATIONS: 'プッシュ通知',
|
||||
Push_Notifications_Alert_Info:
|
||||
'通知はアプリを開いていない時に送られます。',
|
||||
Quote: '引用',
|
||||
Reactions_are_disabled: 'リアクションは無効化されています',
|
||||
Reactions_are_enabled: 'リアクションは有効化されています',
|
||||
Reactions: 'リアクション',
|
||||
Read: '読む',
|
||||
Read_Only_Channel: '読み取り専用チャンネル',
|
||||
Read_Only: '読み取り専用',
|
||||
Read_Receipt: 'レシートを見る',
|
||||
Receive_Group_Mentions: 'グループの通知を受け取る',
|
||||
Receive_Group_Mentions_Info: '@all と @here の通知を受け取る',
|
||||
Register: '登録',
|
||||
Repeat_Password: 'パスワードを再入力',
|
||||
Replied_on: '返信:',
|
||||
replies: '返信',
|
||||
reply: '返信',
|
||||
Reply: '返信',
|
||||
Report: '報告',
|
||||
Receive_Notification: '通知を受け取る',
|
||||
Receive_notifications_from: '{{name}}からの通知を受け取る',
|
||||
Resend: '再送信',
|
||||
Reset_password: 'パスワードをリセット',
|
||||
resetting_password: 'パスワードを再設定',
|
||||
RESET: 'リセット',
|
||||
Review_app_title: 'アプリにご満足いただけておりますか?',
|
||||
Review_app_desc: '{{store}}で5段階で評価をお願いします',
|
||||
Review_app_yes: 'はい!',
|
||||
Review_app_no: 'いいえ',
|
||||
Review_app_later: 'あとで',
|
||||
Review_app_unable_store: '{{store}}を開けません。',
|
||||
Review_this_app: 'アプリをレビューする',
|
||||
Roles: 'ロール',
|
||||
Room_actions: 'ルームアクション',
|
||||
Room_changed_announcement:
|
||||
'{{userBy}}がアナウンスを変更しました: {{announcement}}',
|
||||
Room_changed_description:
|
||||
'{{userBy}}が概要を変更しました: {{description}}',
|
||||
Room_changed_privacy: '{{userBy}}がルームタイプを変更しました。: {{type}}',
|
||||
Room_changed_topic: '{{userBy}}がトピックを変更しました: {{topic}}',
|
||||
Room_Files: 'ルームのファイル',
|
||||
Room_Info_Edit: 'ルーム情報を編集',
|
||||
Room_Info: 'ルーム情報',
|
||||
Room_Members: 'ルームのメンバー',
|
||||
Room_name_changed: 'ルーム名が{{userBy}}により変更されました: {{name}}',
|
||||
SAVE: '保存',
|
||||
Save_Changes: '変更を保存',
|
||||
Save: '保存',
|
||||
saving_preferences: '設定を保存中',
|
||||
saving_profile: 'プロフィールを設定中',
|
||||
saving_settings: 'サーバー設定を保存中',
|
||||
saved_to_gallery: 'ギャラリーに保存しました',
|
||||
Search_Messages: 'メッセージを検索',
|
||||
Search: '検索',
|
||||
Search_by: '検索種別: ',
|
||||
Search_global_users: 'グローバルユーザーのための検索',
|
||||
Search_global_users_description:
|
||||
'有効にした場合、他の会社やサーバーの誰もがあなたを検索可能になります。',
|
||||
Seconds: '{{second}} 秒',
|
||||
Select_Avatar: 'アバターを選択',
|
||||
Select_Server: 'サーバーを選択',
|
||||
Select_Users: 'ユーザーを選択',
|
||||
Send: '送信',
|
||||
Send_audio_message: '録音メッセージを送信',
|
||||
Send_crash_report: 'クラッシュレポートを送信',
|
||||
Send_message: 'メッセージを送信',
|
||||
Send_to: '送信先...',
|
||||
Sent_an_attachment: '添付ファイルを送信しました',
|
||||
Server: 'サーバー',
|
||||
Servers: 'サーバー',
|
||||
Server_version: 'サーバーバージョン: {{version}}',
|
||||
Set_username_subtitle:
|
||||
'ユーザー名はメッセージ中であなたにメンションするのに使われます。',
|
||||
Settings: '設定',
|
||||
Settings_succesfully_changed: '設定が更新されました!',
|
||||
Share: 'シェア',
|
||||
Share_Link: 'リンクをシェアする',
|
||||
Share_this_app: 'このアプリをシェアする',
|
||||
Show_more: 'Show more..',
|
||||
Show_Unread_Counter: '未読件数を表示する',
|
||||
Show_Unread_Counter_Info:
|
||||
'未読件数はリスト上で、チャンネルの右側にバッジで表示されます。',
|
||||
Sign_in_your_server: 'サーバーに接続',
|
||||
Sign_Up: '登録',
|
||||
Some_field_is_invalid_or_empty: '不正、または空の入力欄があります。',
|
||||
Sorting_by: '{{key}}順',
|
||||
Sound: '音',
|
||||
Star_room: 'お気に入りルーム',
|
||||
Star: 'お気に入り',
|
||||
Starred_Messages: 'お気に入りされたメッセージ',
|
||||
starred: 'お気に入りされています',
|
||||
Starred: 'お気に入りされています',
|
||||
Start_of_conversation: '会話を開始する',
|
||||
Started_discussion: 'ディスカッションを開始する:',
|
||||
Started_call: '{{userBy}}と通話する',
|
||||
Submit: '送信',
|
||||
Table: '表',
|
||||
Take_a_photo: '写真を撮影',
|
||||
Take_a_video: '動画を撮影',
|
||||
tap_to_change_status: 'タップしてステータスを変更',
|
||||
Tap_to_view_servers_list: 'タップしてサーバーリストを見る',
|
||||
Terms_of_Service: ' 利用規約 ',
|
||||
Theme: 'テーマ',
|
||||
The_URL_is_invalid:
|
||||
'不正なURLか、セキュアな接続を確立できませんでした。\n{{contact}}',
|
||||
There_was_an_error_while_action: '{{action}}の最中にエラーが発生しました!',
|
||||
This_room_is_blocked: 'このルームはブロックされています。',
|
||||
This_room_is_read_only: 'このルームは読み取り専用です。',
|
||||
Thread: 'スレッド',
|
||||
Threads: 'スレッド',
|
||||
Timezone: 'タイムゾーン',
|
||||
To: 'To',
|
||||
topic: 'トピック',
|
||||
Topic: 'トピック',
|
||||
Translate: '翻訳',
|
||||
Try_again: '再度お試しください。',
|
||||
Two_Factor_Authentication: '2段階認証',
|
||||
Type_the_channel_name_here: 'ここにチャンネル名を入力',
|
||||
unarchive: 'アーカイブ解除',
|
||||
UNARCHIVE: 'アーカイブ解除',
|
||||
Unblock_user: 'ブロックを解除',
|
||||
Unfavorite: 'お気に入り解除',
|
||||
Unfollowed_thread: 'スレッド更新時に通知しない',
|
||||
Unmute: 'ミュート解除',
|
||||
unmuted: 'ミュート解除しました',
|
||||
Unpin: 'ピン留めを解除',
|
||||
unread_messages: '未読',
|
||||
Unread: '未読',
|
||||
Unread_on_top: '未読メッセージを上に表示',
|
||||
Unstar: 'お気に入り解除',
|
||||
Updating: '更新中...',
|
||||
Uploading: 'アップロード中',
|
||||
Upload_file_question_mark: 'ファイルをアップロードしますか?',
|
||||
Users: 'ユーザー',
|
||||
User_added_by: '{{userBy}} が {{userAdded}} を追加しました',
|
||||
User_Info: 'ユーザー情報',
|
||||
User_has_been_key: 'ユーザーは{{ key }}!',
|
||||
User_is_no_longer_role_by_: '{{userBy}} は {{user}} のロール {{role}} を削除しました。',
|
||||
User_muted_by: '{{userBy}} は {{userMuted}} をミュートしました。',
|
||||
User_removed_by: '{{userBy}} は {{userRemoved}} を退出させました。',
|
||||
User_sent_an_attachment: '{{user}}は添付ファイルを送信しました',
|
||||
User_unmuted_by: '{{userUnmuted}} は {{userBy}} にミュート解除されました。',
|
||||
User_was_set_role_by_: '{{user}} was set {{role}} by {{userBy}}',
|
||||
Username_is_empty: 'ユーザー名が空です。',
|
||||
Username: 'ユーザー名',
|
||||
Username_or_email: 'ユーザー名かメールアドレス',
|
||||
Validating: '検証中',
|
||||
Video_call: 'ビデオ通話',
|
||||
View_Original: 'オリジナルを見る',
|
||||
Voice_call: '音声通話',
|
||||
Websocket_disabled: 'Websocketはこのサーバーでは無効化されています。\n{{contact}}',
|
||||
Welcome: 'ようこそ',
|
||||
Welcome_to_RocketChat: 'Rocket.Chatへようこそ',
|
||||
Whats_your_2fa: '2段階認証のコードを入力してください',
|
||||
Without_Servers: 'サーバーを除く',
|
||||
Write_External_Permission_Message:
|
||||
'Rocket Chatは画像を保存するためにギャラリーへのアクセスを求めています。',
|
||||
Write_External_Permission: 'ギャラリーへのアクセス許可',
|
||||
Yes_action_it: 'はい、{{action}}します!',
|
||||
Yesterday: '昨日',
|
||||
You_are_in_preview_mode: 'プレビューモードです。',
|
||||
You_are_offline: 'オフラインです。',
|
||||
You_can_search_using_RegExp_eg: '正規表現を利用できます。 例: `/^text$/i`',
|
||||
You_colon: 'あなた: ',
|
||||
you_were_mentioned: 'あなたがメンションしました',
|
||||
you: 'あなた',
|
||||
You: 'あなた',
|
||||
Logged_out_by_server:
|
||||
'サーバーからログアウトします。もう一度ログインしてください。',
|
||||
You_need_to_access_at_least_one_RocketChat_server_to_share_something:
|
||||
'シェアするためには1つ以上のサーバーにアクセスする必要があります。',
|
||||
Your_certificate: 'あなたの認証情報',
|
||||
Your_invite_link_will_expire_after__usesLeft__uses:
|
||||
'招待リンクはあと{{usesLeft}}回で使用できなくなります。',
|
||||
Your_invite_link_will_expire_on__date__or_after__usesLeft__uses:
|
||||
'招待リンクは{{date}}までか、あと{{usesLeft}}回で使用できなくなります。',
|
||||
Your_invite_link_will_expire_on__date__:
|
||||
'招待リンクは{{date}}に使用できなくなります。',
|
||||
Your_invite_link_will_never_expire: '招待リンクはずっと有効です。',
|
||||
Version_no: 'バージョン: {{version}}',
|
||||
You_will_not_be_able_to_recover_this_message:
|
||||
'このメッセージは復元できません!',
|
||||
Change_Language: '言語を変更',
|
||||
Crash_report_disclaimer:
|
||||
'クラッシュレポートには問題を特定し、修正するために必要な情報のみが含まれます。チャット内のコンテンツは送信されません。',
|
||||
Type_message: 'メッセージを入力',
|
||||
Room_search: 'ルームを検索',
|
||||
Room_selection: 'ルームを選択 1...9',
|
||||
Next_room: '次のルーム',
|
||||
Previous_room: '前のルーム',
|
||||
New_room: '新しいルーム',
|
||||
Upload_room: 'ルームにアップロード',
|
||||
Search_messages: 'メッセージを検索',
|
||||
Scroll_messages: 'メッセージをスクロール',
|
||||
Reply_latest: '最新のメッセージにリプライ',
|
||||
Server_selection: 'サーバー選択',
|
||||
Server_selection_numbers: 'サーバー選択 1...9',
|
||||
Add_server: 'サーバーを追加',
|
||||
New_line: '新しい行',
|
||||
You_will_be_logged_out_of_this_application:
|
||||
'アプリからログアウトします。',
|
||||
Clear: 'クリア',
|
||||
This_will_clear_all_your_offline_data:
|
||||
'オフラインデータをすべて削除します。'
|
||||
};
|
|
@ -160,6 +160,7 @@ export default {
|
|||
delete: 'delete',
|
||||
Delete: 'Delete',
|
||||
DELETE: 'DELETE',
|
||||
deleting_room: 'kamer legen',
|
||||
description: 'beschrijving',
|
||||
Description: 'Beschrijving',
|
||||
DESKTOP_OPTIONS: 'DESKTOP OPTIES',
|
||||
|
@ -179,10 +180,8 @@ export default {
|
|||
EMAIL: 'EMAIL',
|
||||
email: 'e-mail',
|
||||
Enable_Auto_Translate: 'Zet Auto-Translate aan',
|
||||
Enable_markdown: 'Zet markdown aan',
|
||||
Enable_notifications: 'Zet notifications aan',
|
||||
Everyone_can_access_this_channel: 'Iedereen kan bij dit kanaal',
|
||||
erasing_room: 'kamer legen',
|
||||
Error_uploading: 'Error tijdens uploaden',
|
||||
Expiration_Days: 'Vervalt in (Dagen)',
|
||||
Favorite: 'Favoriet',
|
||||
|
@ -267,9 +266,6 @@ export default {
|
|||
No_results_found: 'Geen resultaten gevonden',
|
||||
No_starred_messages: 'Geen berichten met ster gemarkeerd',
|
||||
No_thread_messages: 'Geen thread berichten',
|
||||
No_announcement_provided: 'Geen announcement opgegeven.',
|
||||
No_description_provided: 'Geen beschrijving opgegeven.',
|
||||
No_topic_provided: 'Geen onderwerp opgegeven.',
|
||||
No_Message: 'Geen bericht',
|
||||
No_messages_yet: 'Nog geen berichten',
|
||||
No_Reactions: 'Geen reacties',
|
||||
|
|
|
@ -114,6 +114,7 @@ export default {
|
|||
Back: 'Voltar',
|
||||
Black: 'Preto',
|
||||
Block_user: 'Bloquear usuário',
|
||||
Browser: 'Navegador',
|
||||
Broadcast_channel_Description: 'Somente usuários autorizados podem escrever novas mensagens, mas os outros usuários poderão responder',
|
||||
Broadcast_Channel: 'Canal de Transmissão',
|
||||
Busy: 'Ocupado',
|
||||
|
@ -127,13 +128,16 @@ export default {
|
|||
Channel_Name: 'Nome do Canal',
|
||||
Channels: 'Canais',
|
||||
Chats: 'Conversas',
|
||||
Change_language_loading: 'Alterando idioma.',
|
||||
Call_already_ended: 'A chamada já terminou!',
|
||||
Clear_cache_loading: 'Limpando cache.',
|
||||
Click_to_join: 'Clique para participar!',
|
||||
Close: 'Fechar',
|
||||
Close_emoji_selector: 'Fechar seletor de emojis',
|
||||
Choose: 'Escolher',
|
||||
Choose_from_library: 'Escolha da biblioteca',
|
||||
Choose_file: 'Enviar arquivo',
|
||||
Choose_where_you_want_links_be_opened: 'Escolha onde deseja que os links sejam abertos',
|
||||
Code: 'Código',
|
||||
Collaborative: 'Colaborativo',
|
||||
Confirm: 'Confirmar',
|
||||
|
@ -154,10 +158,12 @@ export default {
|
|||
Create: 'Criar',
|
||||
Dark: 'Escuro',
|
||||
Dark_level: 'Nível escuro',
|
||||
Default_browser: 'Navegador padrão',
|
||||
Delete_Room_Warning: 'A exclusão de uma sala irá apagar todas as mensagens postadas na sala. Isso não pode ser desfeito.',
|
||||
delete: 'excluir',
|
||||
Delete: 'Excluir',
|
||||
DELETE: 'EXCLUIR',
|
||||
deleting_room: 'excluindo sala',
|
||||
Direct_Messages: 'Mensagens Diretas',
|
||||
Directory: 'Diretório',
|
||||
description: 'descrição',
|
||||
|
@ -168,13 +174,11 @@ export default {
|
|||
Do_you_really_want_to_key_this_room_question_mark: 'Você quer realmente {{key}} esta sala?',
|
||||
edit: 'editar',
|
||||
edited: 'editado',
|
||||
erasing_room: 'apagando sala',
|
||||
Edit: 'Editar',
|
||||
Edit_Invite: 'Editar convite',
|
||||
Email_or_password_field_is_empty: 'Email ou senha estão vazios',
|
||||
Email: 'Email',
|
||||
email: 'e-mail',
|
||||
Enable_markdown: 'Habilitar markdown',
|
||||
Enable_notifications: 'Habilitar notificações',
|
||||
Everyone_can_access_this_channel: 'Todos podem acessar este canal',
|
||||
Error_uploading: 'Erro subindo',
|
||||
|
@ -197,6 +201,22 @@ export default {
|
|||
Has_joined_the_channel: 'Entrou no canal',
|
||||
Has_joined_the_conversation: 'Entrou na conversa',
|
||||
Has_left_the_channel: 'Saiu da conversa',
|
||||
Hide_System_Messages: 'Esconder mensagens do sistema',
|
||||
Hide_type_messages: 'Esconder mensagens de "{{type}}"',
|
||||
Message_HideType_uj: 'Utilizador Entrou',
|
||||
Message_HideType_ul: 'Utilizador Saiu',
|
||||
Message_HideType_ru: 'Utilizador Removido',
|
||||
Message_HideType_au: 'Utilizador adicionado',
|
||||
Message_HideType_mute_unmute: 'Utilizador Silenciado',
|
||||
Message_HideType_r: 'Nome da sala alterado',
|
||||
Message_HideType_ut: 'Utilizador adicionado ao bate-papo',
|
||||
Message_HideType_wm: 'Bem Vindo',
|
||||
Message_HideType_rm: 'Mensagem Removida',
|
||||
Message_HideType_subscription_role_added: 'Papel atribuído',
|
||||
Message_HideType_subscription_role_removed: 'Papel removido',
|
||||
Message_HideType_room_archived: 'Sala arquivada',
|
||||
Message_HideType_room_unarchived: 'Sala desarquivada',
|
||||
In_app: 'No app',
|
||||
Invisible: 'Invisível',
|
||||
Invite: 'Convidar',
|
||||
is_typing: 'está digitando',
|
||||
|
@ -219,6 +239,7 @@ export default {
|
|||
Login_error: 'Suas credenciais foram rejeitadas. Tente novamente por favor!',
|
||||
Login_with: 'Login with',
|
||||
Logout: 'Sair',
|
||||
Logging_out: 'Saindo.',
|
||||
Max_number_of_uses: 'Número máximo de usos',
|
||||
Members: 'Membros',
|
||||
Mentioned_Messages: 'Mensagens mencionadas',
|
||||
|
@ -251,9 +272,7 @@ export default {
|
|||
No_results_found: 'Nenhum resultado encontrado',
|
||||
No_starred_messages: 'Não há mensagens favoritas',
|
||||
No_thread_messages: 'Não há tópicos',
|
||||
No_announcement_provided: 'Sem anúncio.',
|
||||
No_description_provided: 'Sem descrição.',
|
||||
No_topic_provided: 'Sem tópico.',
|
||||
No_label_provided: 'Sem {{label}}.',
|
||||
No_Message: 'Não há mensagens',
|
||||
No_messages_yet: 'Não há mensagens ainda',
|
||||
No_Reactions: 'Sem reações',
|
||||
|
@ -267,12 +286,14 @@ export default {
|
|||
Only_authorized_users_can_write_new_messages: 'Somente usuários autorizados podem escrever novas mensagens',
|
||||
Open_emoji_selector: 'Abrir seletor de emoji',
|
||||
Open_Source_Communication: 'Comunicação Open Source',
|
||||
Overwrites_the_server_configuration_and_use_room_config: 'Substituir a configuração do servidor e usar a configuração da sala',
|
||||
Password: 'Senha',
|
||||
Permalink_copied_to_clipboard: 'Link-permanente copiado para a área de transferência!',
|
||||
Pin: 'Fixar',
|
||||
Pinned_Messages: 'Mensagens Fixadas',
|
||||
pinned: 'fixada',
|
||||
Pinned: 'Mensagens Fixadas',
|
||||
Please_wait: 'Por favor, aguarde.',
|
||||
Please_enter_your_password: 'Por favor, digite sua senha',
|
||||
Preferences: 'Preferências',
|
||||
Preferences_saved: 'Preferências salvas!',
|
||||
|
@ -404,6 +425,7 @@ export default {
|
|||
Username_is_empty: 'Usuário está vazio',
|
||||
Username: 'Usuário',
|
||||
Username_or_email: 'Usuário ou email',
|
||||
Uses_server_configuration: 'Usar configuração do servidor',
|
||||
Verify_email_title: 'Registrado com sucesso!',
|
||||
Verify_email_desc: 'Nós lhe enviamos um e-mail para confirmar o seu registro. Se você não receber um e-mail em breve, por favor retorne e tente novamente.',
|
||||
Video_call: 'Chamada de vídeo',
|
||||
|
@ -420,6 +442,7 @@ export default {
|
|||
You_can_search_using_RegExp_eg: 'Você pode usar expressões regulares, por exemplo `/^text$/i`',
|
||||
You_colon: 'Você: ',
|
||||
you_were_mentioned: 'você foi mencionado',
|
||||
You_were_removed_from_channel: 'Você foi removido de {{channel}}',
|
||||
you: 'você',
|
||||
You: 'Você',
|
||||
Your_invite_link_will_expire_after__usesLeft__uses: 'Seu link de convite irá vencer depois de {{usesLeft}} usos.',
|
||||
|
@ -446,5 +469,6 @@ export default {
|
|||
New_line: 'Nova linha',
|
||||
You_will_be_logged_out_of_this_application: 'Você sairá deste aplicativo.',
|
||||
Clear: 'Limpar',
|
||||
This_will_clear_all_your_offline_data: 'Isto limpará todos os seus dados offline.'
|
||||
This_will_clear_all_your_offline_data: 'Isto limpará todos os seus dados offline.',
|
||||
Mark_unread: 'Marcar como não Lida'
|
||||
};
|
||||
|
|
|
@ -145,7 +145,7 @@ export default {
|
|||
Dont_Have_An_Account: 'Não tem uma conta?',
|
||||
Do_you_really_want_to_key_this_room_question_mark: 'Você quer mesmo {{key}} esta sala?',
|
||||
edit: 'editar',
|
||||
erasing_room: 'apagando sala',
|
||||
deleting_room: 'apagando sala',
|
||||
Edit: 'Editar',
|
||||
Email_or_password_field_is_empty: 'O campo de e-mail ou palavra-passe está vazio',
|
||||
Email: 'E-mail',
|
||||
|
@ -216,9 +216,6 @@ export default {
|
|||
No_pinned_messages: 'Nenhuma mensagem afixada',
|
||||
No_results_found: 'Nenhum resultado encontrado',
|
||||
No_starred_messages: 'Nenhuma mensagem marcada com estrela',
|
||||
No_announcement_provided: 'Nenhum anúncio fornecido.',
|
||||
No_description_provided: 'Nenhuma descrição fornecida.',
|
||||
No_topic_provided: 'Nenhum tópico fornecido.',
|
||||
No_Message: 'Nenhuma mensagem',
|
||||
No_Reactions: 'Nenhuma reação',
|
||||
Not_logged: 'Não ligado',
|
||||
|
|
|
@ -171,10 +171,9 @@ export default {
|
|||
EMAIL: 'EMAIL',
|
||||
email: 'e-mail',
|
||||
Enable_Auto_Translate: 'Включить автоперевод',
|
||||
Enable_markdown: 'Включить markdown',
|
||||
Enable_notifications: 'Включить уведомления',
|
||||
Everyone_can_access_this_channel: 'Каждый может получить доступ к этому каналу',
|
||||
erasing_room: 'стирание комнаты',
|
||||
deleting_room: 'стирание комнаты',
|
||||
Error_uploading: 'Ошибка при загрузке',
|
||||
Favorite: 'Избранное',
|
||||
Favorites: 'Избранные',
|
||||
|
@ -250,9 +249,6 @@ export default {
|
|||
No_results_found: 'Ничего не найдено',
|
||||
No_starred_messages: 'Нет отмеченных сообщений',
|
||||
No_thread_messages: 'Нет сообщений в теме',
|
||||
No_announcement_provided: 'Нет объявлений.',
|
||||
No_description_provided: 'Нет описания.',
|
||||
No_topic_provided: 'Нет темы.',
|
||||
No_Message: 'Нет сообщения',
|
||||
No_messages_yet: 'Пока нет сообщений',
|
||||
No_Reactions: 'Нет реакций',
|
||||
|
|
|
@ -144,7 +144,7 @@ export default {
|
|||
Dont_Have_An_Account: '还没有账号?',
|
||||
Do_you_really_want_to_key_this_room_question_mark: '你真的想要{{key}}这个房间吗?',
|
||||
edit: '编辑',
|
||||
erasing_room: '正抹去房间',
|
||||
deleting_room: '正抹去房间',
|
||||
Edit: '编辑',
|
||||
Email_or_password_field_is_empty: '邮件或密码字段为空',
|
||||
Email: '邮箱',
|
||||
|
@ -212,9 +212,6 @@ export default {
|
|||
No_pinned_messages: '没有固定的消息',
|
||||
No_snippeted_messages: '没有代码片段的消息',
|
||||
No_starred_messages: '没有加星标的消息',
|
||||
No_announcement_provided: '没有公告.',
|
||||
No_description_provided: '没有描述.',
|
||||
No_topic_provided: '没有话题.',
|
||||
No_Message: '没有消息',
|
||||
No_Reactions: '没有回复',
|
||||
Not_logged: '没有记录',
|
||||
|
|
|
@ -217,6 +217,9 @@ const SettingsStack = createStackNavigator({
|
|||
},
|
||||
ThemeView: {
|
||||
getScreen: () => require('./views/ThemeView').default
|
||||
},
|
||||
DefaultBrowserView: {
|
||||
getScreen: () => require('./views/DefaultBrowserView').default
|
||||
}
|
||||
}, {
|
||||
defaultNavigationOptions: defaultHeader,
|
||||
|
@ -466,7 +469,7 @@ class CustomModalStack extends React.Component {
|
|||
closeModal();
|
||||
return true;
|
||||
}
|
||||
if (state && state.routes[state.index] && state.routes[state.index].routes.length > 1) {
|
||||
if (state && state.routes[state.index] && state.routes[state.index].routes && state.routes[state.index].routes.length > 1) {
|
||||
navigation.goBack();
|
||||
}
|
||||
return false;
|
||||
|
@ -602,9 +605,6 @@ export default class Root extends React.Component {
|
|||
}
|
||||
|
||||
init = async() => {
|
||||
if (isIOS) {
|
||||
await RNUserDefaults.setName('group.ios.chat.rocket');
|
||||
}
|
||||
RNUserDefaults.objectForKey(THEME_PREFERENCES_KEY).then(this.setTheme);
|
||||
const [notification, deepLinking] = await Promise.all([initializePushNotifications(), Linking.getInitialURL()]);
|
||||
const parsedDeepLinkingURL = parseDeepLinking(deepLinking);
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import { Model } from '@nozbe/watermelondb';
|
||||
import { field, date } from '@nozbe/watermelondb/decorators';
|
||||
import { field, date, json } from '@nozbe/watermelondb/decorators';
|
||||
|
||||
import { sanitizer } from '../utils';
|
||||
|
||||
export default class Setting extends Model {
|
||||
static table = 'settings';
|
||||
|
@ -10,5 +12,7 @@ export default class Setting extends Model {
|
|||
|
||||
@field('value_as_number') valueAsNumber;
|
||||
|
||||
@json('value_as_array', sanitizer) valueAsArray;
|
||||
|
||||
@date('_updated_at') _updatedAt;
|
||||
}
|
||||
|
|
|
@ -89,4 +89,6 @@ export default class Subscription extends Model {
|
|||
@children('thread_messages') threadMessages;
|
||||
|
||||
@field('hide_unread_status') hideUnreadStatus;
|
||||
|
||||
@json('sys_mes', sanitizer) sysMes;
|
||||
}
|
||||
|
|
|
@ -40,6 +40,28 @@ export default schemaMigrations({
|
|||
]
|
||||
})
|
||||
]
|
||||
},
|
||||
{
|
||||
toVersion: 5,
|
||||
steps: [
|
||||
addColumns({
|
||||
table: 'settings',
|
||||
columns: [
|
||||
{ name: 'value_as_array', type: 'string', isOptional: true }
|
||||
]
|
||||
})
|
||||
]
|
||||
},
|
||||
{
|
||||
toVersion: 6,
|
||||
steps: [
|
||||
addColumns({
|
||||
table: 'subscriptions',
|
||||
columns: [
|
||||
{ name: 'sys_mes', type: 'string', isOptional: true }
|
||||
]
|
||||
})
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { appSchema, tableSchema } from '@nozbe/watermelondb';
|
||||
|
||||
export default appSchema({
|
||||
version: 4,
|
||||
version: 6,
|
||||
tables: [
|
||||
tableSchema({
|
||||
name: 'subscriptions',
|
||||
|
@ -39,7 +39,8 @@ export default appSchema({
|
|||
{ name: 'jitsi_timeout', type: 'number', isOptional: true },
|
||||
{ name: 'auto_translate', type: 'boolean', isOptional: true },
|
||||
{ name: 'auto_translate_language', type: 'string' },
|
||||
{ name: 'hide_unread_status', type: 'boolean', isOptional: true }
|
||||
{ name: 'hide_unread_status', type: 'boolean', isOptional: true },
|
||||
{ name: 'sys_mes', type: 'string', isOptional: true }
|
||||
]
|
||||
}),
|
||||
tableSchema({
|
||||
|
@ -196,6 +197,7 @@ export default appSchema({
|
|||
{ name: 'value_as_string', type: 'string', isOptional: true },
|
||||
{ name: 'value_as_boolean', type: 'boolean', isOptional: true },
|
||||
{ name: 'value_as_number', type: 'number', isOptional: true },
|
||||
{ name: 'value_as_array', type: 'string', isOptional: true },
|
||||
{ name: '_updated_at', type: 'number', isOptional: true }
|
||||
]
|
||||
}),
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import random from '../../utils/random';
|
||||
import EventEmitter from '../../utils/events';
|
||||
import fetch from '../../utils/fetch';
|
||||
import Navigation from '../Navigation';
|
||||
|
||||
const ACTION_TYPES = {
|
||||
|
|
|
@ -52,6 +52,19 @@ const serverInfoUpdate = async(serverInfo, iconSetting) => {
|
|||
});
|
||||
};
|
||||
|
||||
export async function getLoginSettings({ server }) {
|
||||
try {
|
||||
const settingsParams = JSON.stringify(['Accounts_ShowFormLogin', 'Accounts_RegistrationForm']);
|
||||
const result = await fetch(`${ server }/api/v1/settings.public?query={"_id":{"$in":${ settingsParams }}}`).then(response => response.json());
|
||||
|
||||
if (result.success && result.settings.length) {
|
||||
reduxStore.dispatch(actions.addSettings(this.parseSettings(this._prepareSettings(result.settings))));
|
||||
}
|
||||
} catch (e) {
|
||||
log(e);
|
||||
}
|
||||
}
|
||||
|
||||
export async function setSettings() {
|
||||
const db = database.active;
|
||||
const settingsCollection = db.collections.get('settings');
|
||||
|
@ -61,9 +74,10 @@ export async function setSettings() {
|
|||
valueAsString: item.valueAsString,
|
||||
valueAsBoolean: item.valueAsBoolean,
|
||||
valueAsNumber: item.valueAsNumber,
|
||||
valueAsArray: item.valueAsArray,
|
||||
_updatedAt: item._updatedAt
|
||||
}));
|
||||
reduxStore.dispatch(actions.setAllSettings(RocketChat.parseSettings(parsed.slice(0, parsed.length))));
|
||||
reduxStore.dispatch(actions.addSettings(RocketChat.parseSettings(parsed.slice(0, parsed.length))));
|
||||
}
|
||||
|
||||
export default async function() {
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
import { InteractionManager } from 'react-native';
|
||||
import semver from 'semver';
|
||||
|
||||
import reduxStore from '../createStore';
|
||||
import { setActiveUsers } from '../../actions/activeUsers';
|
||||
|
||||
export function subscribeUsersPresence() {
|
||||
const serverVersion = reduxStore.getState().server.version;
|
||||
|
||||
// if server is lower than 1.1.0
|
||||
if (serverVersion && semver.lt(semver.coerce(serverVersion), '1.1.0')) {
|
||||
if (this.activeUsersSubTimeout) {
|
||||
clearTimeout(this.activeUsersSubTimeout);
|
||||
this.activeUsersSubTimeout = false;
|
||||
}
|
||||
this.activeUsersSubTimeout = setTimeout(() => {
|
||||
this.sdk.subscribe('activeUsers');
|
||||
}, 5000);
|
||||
} else {
|
||||
this.sdk.subscribe('stream-notify-logged', 'user-status');
|
||||
}
|
||||
}
|
||||
|
||||
let ids = [];
|
||||
|
||||
export default async function getUsersPresence() {
|
||||
const serverVersion = reduxStore.getState().server.version;
|
||||
|
||||
// if server is greather than or equal 1.1.0
|
||||
if (serverVersion && !semver.lt(semver.coerce(serverVersion), '1.1.0')) {
|
||||
let params = {};
|
||||
|
||||
// if server is greather than or equal 3.0.0
|
||||
if (serverVersion && !semver.lt(semver.coerce(serverVersion), '3.0.0')) {
|
||||
// if not have any id
|
||||
if (!ids.length) {
|
||||
return;
|
||||
}
|
||||
// Request userPresence on demand
|
||||
params = { ids: ids.join(',') };
|
||||
ids = [];
|
||||
}
|
||||
|
||||
// RC 1.1.0
|
||||
const result = await this.sdk.get('users.presence', params);
|
||||
if (result.success) {
|
||||
const activeUsers = result.users.reduce((ret, item) => {
|
||||
ret[item._id] = item.status;
|
||||
return ret;
|
||||
}, {});
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
reduxStore.dispatch(setActiveUsers(activeUsers));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let usersTimer = null;
|
||||
export function getUserPresence(uid) {
|
||||
const auth = reduxStore.getState().login.isAuthenticated;
|
||||
|
||||
if (!usersTimer) {
|
||||
usersTimer = setTimeout(() => {
|
||||
if (auth && ids.length) {
|
||||
getUsersPresence.call(this);
|
||||
}
|
||||
usersTimer = null;
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
ids.push(uid);
|
||||
}
|
|
@ -32,6 +32,7 @@ export const merge = (subscription, room) => {
|
|||
} else {
|
||||
subscription.muted = [];
|
||||
}
|
||||
subscription.sysMes = room.sysMes;
|
||||
}
|
||||
|
||||
if (!subscription.name) {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
||||
import { settings as RocketChatSettings } from '@rocket.chat/sdk';
|
||||
|
||||
import database from '../database';
|
||||
import log from '../../utils/log';
|
||||
import { headers } from '../../utils/fetch';
|
||||
|
||||
const uploadQueue = {};
|
||||
|
||||
|
@ -75,7 +75,10 @@ export function sendFileMessage(rid, fileInfo, tmid, server, user) {
|
|||
|
||||
xhr.setRequestHeader('X-Auth-Token', token);
|
||||
xhr.setRequestHeader('X-User-Id', id);
|
||||
xhr.setRequestHeader('User-Agent', headers['User-Agent']);
|
||||
const { customHeaders } = RocketChatSettings;
|
||||
Object.keys(customHeaders).forEach((key) => {
|
||||
xhr.setRequestHeader(key, customHeaders[key]);
|
||||
});
|
||||
|
||||
xhr.upload.onprogress = async({ total, loaded }) => {
|
||||
try {
|
||||
|
|
|
@ -5,7 +5,7 @@ import database from '../database';
|
|||
import log from '../../utils/log';
|
||||
import random from '../../utils/random';
|
||||
|
||||
const changeMessageStatus = async(id, tmid, status) => {
|
||||
const changeMessageStatus = async(id, tmid, status, message) => {
|
||||
const db = database.active;
|
||||
const msgCollection = db.collections.get('messages');
|
||||
const threadMessagesCollection = db.collections.get('thread_messages');
|
||||
|
@ -14,6 +14,10 @@ const changeMessageStatus = async(id, tmid, status) => {
|
|||
successBatch.push(
|
||||
messageRecord.prepareUpdate((m) => {
|
||||
m.status = status;
|
||||
if (message) {
|
||||
m.mentions = message.mentions;
|
||||
m.channels = message.channels;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -22,6 +26,10 @@ const changeMessageStatus = async(id, tmid, status) => {
|
|||
successBatch.push(
|
||||
threadMessageRecord.prepareUpdate((tm) => {
|
||||
tm.status = status;
|
||||
if (message) {
|
||||
tm.mentions = message.mentions;
|
||||
tm.channels = message.channels;
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
@ -42,15 +50,18 @@ export async function sendMessageCall(message) {
|
|||
try {
|
||||
const sdk = this.shareSDK || this.sdk;
|
||||
// RC 0.60.0
|
||||
await sdk.post('chat.sendMessage', {
|
||||
const result = await sdk.post('chat.sendMessage', {
|
||||
message: {
|
||||
_id, rid, msg, tmid
|
||||
}
|
||||
});
|
||||
await changeMessageStatus(_id, tmid, messagesStatus.SENT);
|
||||
} catch (e) {
|
||||
await changeMessageStatus(_id, tmid, messagesStatus.ERROR);
|
||||
if (result.success) {
|
||||
return changeMessageStatus(_id, tmid, messagesStatus.SENT, result.message);
|
||||
}
|
||||
} catch {
|
||||
// do nothing
|
||||
}
|
||||
return changeMessageStatus(_id, tmid, messagesStatus.ERROR);
|
||||
}
|
||||
|
||||
export default async function(rid, msg, tmid, user) {
|
||||
|
@ -133,7 +144,7 @@ export default async function(rid, msg, tmid, user) {
|
|||
_id: user.id || '1',
|
||||
username: user.username
|
||||
};
|
||||
if (tmid) {
|
||||
if (tmid && tMessageRecord) {
|
||||
m.tmid = tmid;
|
||||
m.tlm = messageDate;
|
||||
m.tmsg = tMessageRecord.msg;
|
||||
|
|
|
@ -11,10 +11,17 @@ import { addUserTyping, removeUserTyping, clearUserTyping } from '../../../actio
|
|||
import debounce from '../../../utils/debounce';
|
||||
import RocketChat from '../../rocketchat';
|
||||
|
||||
const WINDOW_TIME = 1000;
|
||||
|
||||
export default class RoomSubscription {
|
||||
constructor(rid) {
|
||||
this.rid = rid;
|
||||
this.isAlive = true;
|
||||
this.timer = null;
|
||||
this.queue = {};
|
||||
this.messagesBatch = {};
|
||||
this.threadsBatch = {};
|
||||
this.threadMessagesBatch = {};
|
||||
}
|
||||
|
||||
subscribe = async() => {
|
||||
|
@ -49,6 +56,9 @@ export default class RoomSubscription {
|
|||
this.removeListener(this.notifyRoomListener);
|
||||
this.removeListener(this.messageReceivedListener);
|
||||
reduxStore.dispatch(clearUserTyping());
|
||||
if (this.timer) {
|
||||
clearTimeout(this.timer);
|
||||
}
|
||||
}
|
||||
|
||||
removeListener = async(promise) => {
|
||||
|
@ -131,15 +141,13 @@ export default class RoomSubscription {
|
|||
RocketChat.readMessages(this.rid, lastOpen);
|
||||
}, 300);
|
||||
|
||||
handleMessageReceived = protectedFunction((ddpMessage) => {
|
||||
const message = buildMessage(EJSON.fromJSONValue(ddpMessage.fields.args[0]));
|
||||
const lastOpen = new Date();
|
||||
if (this.rid !== message.rid) {
|
||||
return;
|
||||
}
|
||||
InteractionManager.runAfterInteractions(async() => {
|
||||
updateMessage = message => (
|
||||
new Promise(async(resolve) => {
|
||||
if (this.rid !== message.rid) {
|
||||
return;
|
||||
}
|
||||
|
||||
const db = database.active;
|
||||
const batch = [];
|
||||
const msgCollection = db.collections.get('messages');
|
||||
const threadsCollection = db.collections.get('threads');
|
||||
const threadMessagesCollection = db.collections.get('thread_messages');
|
||||
|
@ -154,22 +162,17 @@ export default class RoomSubscription {
|
|||
// Do nothing
|
||||
}
|
||||
if (messageRecord) {
|
||||
try {
|
||||
const update = messageRecord.prepareUpdate((m) => {
|
||||
Object.assign(m, message);
|
||||
});
|
||||
batch.push(update);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
const update = messageRecord.prepareUpdate((m) => {
|
||||
Object.assign(m, message);
|
||||
});
|
||||
this._messagesBatch[message._id] = update;
|
||||
} else {
|
||||
batch.push(
|
||||
msgCollection.prepareCreate(protectedFunction((m) => {
|
||||
m._raw = sanitizedRaw({ id: message._id }, msgCollection.schema);
|
||||
m.subscription.id = this.rid;
|
||||
Object.assign(m, message);
|
||||
}))
|
||||
);
|
||||
const create = msgCollection.prepareCreate(protectedFunction((m) => {
|
||||
m._raw = sanitizedRaw({ id: message._id }, msgCollection.schema);
|
||||
m.subscription.id = this.rid;
|
||||
Object.assign(m, message);
|
||||
}));
|
||||
this._messagesBatch[message._id] = create;
|
||||
}
|
||||
|
||||
// Create or update thread
|
||||
|
@ -181,19 +184,17 @@ export default class RoomSubscription {
|
|||
}
|
||||
|
||||
if (threadRecord) {
|
||||
batch.push(
|
||||
threadRecord.prepareUpdate(protectedFunction((t) => {
|
||||
Object.assign(t, message);
|
||||
}))
|
||||
);
|
||||
const updateThread = threadRecord.prepareUpdate(protectedFunction((t) => {
|
||||
Object.assign(t, message);
|
||||
}));
|
||||
this._threadsBatch[message._id] = updateThread;
|
||||
} else {
|
||||
batch.push(
|
||||
threadsCollection.prepareCreate(protectedFunction((t) => {
|
||||
t._raw = sanitizedRaw({ id: message._id }, threadsCollection.schema);
|
||||
t.subscription.id = this.rid;
|
||||
Object.assign(t, message);
|
||||
}))
|
||||
);
|
||||
const createThread = threadsCollection.prepareCreate(protectedFunction((t) => {
|
||||
t._raw = sanitizedRaw({ id: message._id }, threadsCollection.schema);
|
||||
t.subscription.id = this.rid;
|
||||
Object.assign(t, message);
|
||||
}));
|
||||
this._threadsBatch[message._id] = createThread;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -206,35 +207,75 @@ export default class RoomSubscription {
|
|||
}
|
||||
|
||||
if (threadMessageRecord) {
|
||||
batch.push(
|
||||
threadMessageRecord.prepareUpdate(protectedFunction((tm) => {
|
||||
Object.assign(tm, message);
|
||||
tm.rid = message.tmid;
|
||||
delete tm.tmid;
|
||||
}))
|
||||
);
|
||||
const updateThreadMessage = threadMessageRecord.prepareUpdate(protectedFunction((tm) => {
|
||||
Object.assign(tm, message);
|
||||
tm.rid = message.tmid;
|
||||
delete tm.tmid;
|
||||
}));
|
||||
this._threadMessagesBatch[message._id] = updateThreadMessage;
|
||||
} else {
|
||||
batch.push(
|
||||
threadMessagesCollection.prepareCreate(protectedFunction((tm) => {
|
||||
tm._raw = sanitizedRaw({ id: message._id }, threadMessagesCollection.schema);
|
||||
Object.assign(tm, message);
|
||||
tm.subscription.id = this.rid;
|
||||
tm.rid = message.tmid;
|
||||
delete tm.tmid;
|
||||
}))
|
||||
);
|
||||
const createThreadMessage = threadMessagesCollection.prepareCreate(protectedFunction((tm) => {
|
||||
tm._raw = sanitizedRaw({ id: message._id }, threadMessagesCollection.schema);
|
||||
Object.assign(tm, message);
|
||||
tm.subscription.id = this.rid;
|
||||
tm.rid = message.tmid;
|
||||
delete tm.tmid;
|
||||
}));
|
||||
this._threadMessagesBatch[message._id] = createThreadMessage;
|
||||
}
|
||||
}
|
||||
|
||||
this.read(lastOpen);
|
||||
return resolve();
|
||||
})
|
||||
)
|
||||
|
||||
try {
|
||||
await db.action(async() => {
|
||||
await db.batch(...batch);
|
||||
});
|
||||
} catch (e) {
|
||||
log(e);
|
||||
}
|
||||
});
|
||||
});
|
||||
handleMessageReceived = (ddpMessage) => {
|
||||
if (!this.timer) {
|
||||
this.timer = setTimeout(async() => {
|
||||
// copy variables values to local and clean them
|
||||
const _lastOpen = this.lastOpen;
|
||||
const _queue = Object.keys(this.queue).map(key => this.queue[key]);
|
||||
this._messagesBatch = this.messagesBatch;
|
||||
this._threadsBatch = this.threadsBatch;
|
||||
this._threadMessagesBatch = this.threadMessagesBatch;
|
||||
this.queue = {};
|
||||
this.messagesBatch = {};
|
||||
this.threadsBatch = {};
|
||||
this.threadMessagesBatch = {};
|
||||
this.timer = null;
|
||||
|
||||
for (let i = 0; i < _queue.length; i += 1) {
|
||||
try {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await this.updateMessage(_queue[i]);
|
||||
} catch (e) {
|
||||
log(e);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const db = database.active;
|
||||
await db.action(async() => {
|
||||
await db.batch(
|
||||
...Object.values(this._messagesBatch),
|
||||
...Object.values(this._threadsBatch),
|
||||
...Object.values(this._threadMessagesBatch)
|
||||
);
|
||||
});
|
||||
|
||||
this.read(_lastOpen);
|
||||
} catch (e) {
|
||||
log(e);
|
||||
}
|
||||
|
||||
// Clean local variables
|
||||
this._messagesBatch = {};
|
||||
this._threadsBatch = {};
|
||||
this._threadMessagesBatch = {};
|
||||
}, WINDOW_TIME);
|
||||
}
|
||||
this.lastOpen = new Date();
|
||||
const message = buildMessage(EJSON.fromJSONValue(ddpMessage.fields.args[0]));
|
||||
this.queue[message._id] = message;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -13,6 +13,8 @@ import { notificationReceived } from '../../../actions/notification';
|
|||
import { handlePayloadUserInteraction } from '../actions';
|
||||
import buildMessage from '../helpers/buildMessage';
|
||||
import RocketChat from '../../rocketchat';
|
||||
import EventEmmiter from '../../../utils/events';
|
||||
import { deleteRoomFinish } from '../../../actions/room';
|
||||
|
||||
const removeListener = listener => listener.stop();
|
||||
|
||||
|
@ -238,6 +240,15 @@ export default function subscribeRooms() {
|
|||
...threadMessagesToDelete
|
||||
);
|
||||
});
|
||||
|
||||
const roomState = store.getState().room;
|
||||
// Delete and remove events come from this stream
|
||||
// Here we identify which one was triggered
|
||||
if (data.rid === roomState.rid && roomState.isDeleting) {
|
||||
store.dispatch(deleteRoomFinish());
|
||||
} else {
|
||||
EventEmmiter.emit('ROOM_REMOVED', { rid: data.rid });
|
||||
}
|
||||
} catch (e) {
|
||||
log(e);
|
||||
}
|
||||
|
@ -283,10 +294,9 @@ export default function subscribeRooms() {
|
|||
const [notification] = ddpMessage.fields.args;
|
||||
try {
|
||||
const { payload: { rid } } = notification;
|
||||
const subCollection = db.collections.get('subscriptions');
|
||||
const sub = await subCollection.find(rid);
|
||||
notification.title = RocketChat.getRoomTitle(sub);
|
||||
notification.avatar = RocketChat.getRoomAvatar(sub);
|
||||
const room = await RocketChat.getRoom(rid);
|
||||
notification.title = RocketChat.getRoomTitle(room);
|
||||
notification.avatar = RocketChat.getRoomAvatar(room);
|
||||
} catch (e) {
|
||||
// do nothing
|
||||
}
|
||||
|
|
|
@ -74,17 +74,29 @@ export default function updateMessages({ rid, update = [], remove = [] }) {
|
|||
// Update
|
||||
msgsToUpdate = msgsToUpdate.map((message) => {
|
||||
const newMessage = update.find(m => m._id === message.id);
|
||||
if (message._hasPendingUpdate) {
|
||||
console.log(message);
|
||||
return;
|
||||
}
|
||||
return message.prepareUpdate(protectedFunction((m) => {
|
||||
Object.assign(m, newMessage);
|
||||
}));
|
||||
});
|
||||
threadsToUpdate = threadsToUpdate.map((thread) => {
|
||||
if (thread._hasPendingUpdate) {
|
||||
console.log(thread);
|
||||
return;
|
||||
}
|
||||
const newThread = allThreads.find(t => t._id === thread.id);
|
||||
return thread.prepareUpdate(protectedFunction((t) => {
|
||||
Object.assign(t, newThread);
|
||||
}));
|
||||
});
|
||||
threadMessagesToUpdate = threadMessagesToUpdate.map((threadMessage) => {
|
||||
if (threadMessage._hasPendingUpdate) {
|
||||
console.log(threadMessage);
|
||||
return;
|
||||
}
|
||||
const newThreadMessage = allThreadMessages.find(t => t._id === threadMessage.id);
|
||||
return threadMessage.prepareUpdate(protectedFunction((tm) => {
|
||||
Object.assign(tm, newThreadMessage);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { AsyncStorage, InteractionManager } from 'react-native';
|
||||
import semver from 'semver';
|
||||
import { Rocketchat as RocketchatClient, settings as RocketChatSettings } from '@rocket.chat/sdk';
|
||||
import { Rocketchat as RocketchatClient } from '@rocket.chat/sdk';
|
||||
import RNUserDefaults from 'rn-user-defaults';
|
||||
import { Q } from '@nozbe/watermelondb';
|
||||
import * as FileSystem from 'expo-file-system';
|
||||
|
@ -12,7 +12,7 @@ import database from './database';
|
|||
import log from '../utils/log';
|
||||
import { isIOS, getBundleId } from '../utils/deviceInfo';
|
||||
import { extractHostname } from '../utils/server';
|
||||
import fetch, { headers } from '../utils/fetch';
|
||||
import fetch, { BASIC_AUTH_KEY } from '../utils/fetch';
|
||||
|
||||
import { setUser, setLoginServices, loginRequest } from '../actions/login';
|
||||
import { disconnect, connectSuccess, connectRequest } from '../actions/connect';
|
||||
|
@ -21,10 +21,11 @@ import {
|
|||
} from '../actions/share';
|
||||
|
||||
import subscribeRooms from './methods/subscriptions/rooms';
|
||||
import getUsersPresence, { getUserPresence, subscribeUsersPresence } from './methods/getUsersPresence';
|
||||
|
||||
import protectedFunction from './methods/helpers/protectedFunction';
|
||||
import readMessages from './methods/readMessages';
|
||||
import getSettings, { setSettings } from './methods/getSettings';
|
||||
import getSettings, { getLoginSettings, setSettings } from './methods/getSettings';
|
||||
|
||||
import getRooms from './methods/getRooms';
|
||||
import getPermissions from './methods/getPermissions';
|
||||
|
@ -50,7 +51,6 @@ import I18n from '../i18n';
|
|||
|
||||
const TOKEN_KEY = 'reactnativemeteor_usertoken';
|
||||
const SORT_PREFS_KEY = 'RC_SORT_PREFS_KEY';
|
||||
export const MARKDOWN_KEY = 'RC_MARKDOWN_KEY';
|
||||
export const THEME_PREFERENCES_KEY = 'RC_THEME_PREFERENCES_KEY';
|
||||
export const CRASH_REPORT_KEY = 'RC_CRASH_REPORT_KEY';
|
||||
const returnAnArray = obj => obj || [];
|
||||
|
@ -58,8 +58,6 @@ const MIN_ROCKETCHAT_VERSION = '0.70.0';
|
|||
|
||||
const STATUSES = ['offline', 'online', 'away', 'busy'];
|
||||
|
||||
RocketChatSettings.customHeaders = headers;
|
||||
|
||||
const RocketChat = {
|
||||
TOKEN_KEY,
|
||||
callJitsi,
|
||||
|
@ -446,6 +444,7 @@ const RocketChat = {
|
|||
await RNUserDefaults.clear('currentServer');
|
||||
await RNUserDefaults.clear(TOKEN_KEY);
|
||||
await RNUserDefaults.clear(`${ TOKEN_KEY }-${ server }`);
|
||||
await RNUserDefaults.clear(`${ BASIC_AUTH_KEY }-${ server }`);
|
||||
|
||||
try {
|
||||
const db = database.active;
|
||||
|
@ -621,6 +620,7 @@ const RocketChat = {
|
|||
cancelUpload,
|
||||
isUploadActive,
|
||||
getSettings,
|
||||
getLoginSettings,
|
||||
setSettings,
|
||||
getPermissions,
|
||||
getCustomEmojis,
|
||||
|
@ -628,7 +628,11 @@ const RocketChat = {
|
|||
getSlashCommands,
|
||||
getRoles,
|
||||
parseSettings: settings => settings.reduce((ret, item) => {
|
||||
ret[item._id] = item[defaultSettings[item._id].type];
|
||||
ret[item._id] = defaultSettings[item._id] && item[defaultSettings[item._id].type];
|
||||
if (item._id === 'Hide_System_Messages') {
|
||||
ret[item._id] = ret[item._id]
|
||||
.reduce((array, value) => [...array, ...value === 'mute_unmute' ? ['user-muted', 'user-unmuted'] : [value]], []);
|
||||
}
|
||||
return ret;
|
||||
}, {}),
|
||||
_prepareSettings(settings) {
|
||||
|
@ -646,6 +650,9 @@ const RocketChat = {
|
|||
// RC 0.49.0
|
||||
return this.sdk.post('chat.update', { roomId: rid, msgId: id, text: msg });
|
||||
},
|
||||
markAsUnread({ messageId }) {
|
||||
return this.sdk.post('subscriptions.unread', { firstUnreadMessage: { _id: messageId } });
|
||||
},
|
||||
toggleStarMessage(messageId, starred) {
|
||||
if (starred) {
|
||||
// RC 0.59.0
|
||||
|
@ -780,7 +787,7 @@ const RocketChat = {
|
|||
// RC 0.48.0
|
||||
return this.sdk.post(`${ this.roomTypeToApiType(t) }.leave`, { roomId });
|
||||
},
|
||||
eraseRoom(roomId, t) {
|
||||
deleteRoom(roomId, t) {
|
||||
// RC 0.49.0
|
||||
return this.sdk.post(`${ this.roomTypeToApiType(t) }.delete`, { roomId });
|
||||
},
|
||||
|
@ -880,13 +887,6 @@ const RocketChat = {
|
|||
// RC 0.51.0
|
||||
return this.sdk.methodCall('setAvatarFromService', data, contentType, service);
|
||||
},
|
||||
async getUseMarkdown() {
|
||||
const useMarkdown = await AsyncStorage.getItem(MARKDOWN_KEY);
|
||||
if (useMarkdown === null) {
|
||||
return true;
|
||||
}
|
||||
return JSON.parse(useMarkdown);
|
||||
},
|
||||
async getAllowCrashReport() {
|
||||
const allowCrashReport = await AsyncStorage.getItem(CRASH_REPORT_KEY);
|
||||
if (allowCrashReport === null) {
|
||||
|
@ -912,7 +912,7 @@ const RocketChat = {
|
|||
let loginServices = [];
|
||||
const loginServicesResult = await fetch(`${ server }/api/v1/settings.oauth`).then(response => response.json());
|
||||
|
||||
if (loginServicesResult.success && loginServicesResult.services.length > 0) {
|
||||
if (loginServicesResult.success && loginServicesResult.services) {
|
||||
const { services } = loginServicesResult;
|
||||
loginServices = services;
|
||||
|
||||
|
@ -1066,42 +1066,9 @@ const RocketChat = {
|
|||
this.activeUsers[ddpMessage.id] = ddpMessage.fields.status;
|
||||
}
|
||||
},
|
||||
getUserPresence() {
|
||||
return new Promise(async(resolve) => {
|
||||
const serverVersion = reduxStore.getState().server.version;
|
||||
|
||||
// if server is lower than 1.1.0
|
||||
if (serverVersion && semver.lt(semver.coerce(serverVersion), '1.1.0')) {
|
||||
if (this.activeUsersSubTimeout) {
|
||||
clearTimeout(this.activeUsersSubTimeout);
|
||||
this.activeUsersSubTimeout = false;
|
||||
}
|
||||
this.activeUsersSubTimeout = setTimeout(() => {
|
||||
this.sdk.subscribe('activeUsers');
|
||||
}, 5000);
|
||||
return resolve();
|
||||
} else {
|
||||
const params = {};
|
||||
// if (this.lastUserPresenceFetch) {
|
||||
// params.from = this.lastUserPresenceFetch.toISOString();
|
||||
// }
|
||||
|
||||
// RC 1.1.0
|
||||
const result = await this.sdk.get('users.presence', params);
|
||||
if (result.success) {
|
||||
const activeUsers = result.users.reduce((ret, item) => {
|
||||
ret[item._id] = item.status;
|
||||
return ret;
|
||||
}, {});
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
reduxStore.dispatch(setActiveUsers(activeUsers));
|
||||
});
|
||||
this.sdk.subscribe('stream-notify-logged', 'user-status');
|
||||
return resolve();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
getUsersPresence,
|
||||
getUserPresence,
|
||||
subscribeUsersPresence,
|
||||
getDirectory({
|
||||
query, count, offset, sort
|
||||
}) {
|
||||
|
|
|
@ -6,10 +6,9 @@ import I18n from '../../i18n';
|
|||
import styles from './styles';
|
||||
import Markdown from '../../containers/markdown';
|
||||
import { themes } from '../../constants/colors';
|
||||
import shortnameToUnicode from '../../utils/shortnameToUnicode';
|
||||
|
||||
const formatMsg = ({
|
||||
lastMessage, type, showLastMessage, username
|
||||
lastMessage, type, showLastMessage, username, useRealName
|
||||
}) => {
|
||||
if (!showLastMessage) {
|
||||
return '';
|
||||
|
@ -33,27 +32,25 @@ const formatMsg = ({
|
|||
if (isLastMessageSentByMe) {
|
||||
prefix = I18n.t('You_colon');
|
||||
} else if (type !== 'd') {
|
||||
prefix = `${ lastMessage.u.username }: `;
|
||||
const { u: { name } } = lastMessage;
|
||||
prefix = `${ useRealName ? name : lastMessage.u.username }: `;
|
||||
}
|
||||
|
||||
let msg = `${ prefix }${ lastMessage.msg.replace(/[\n\t\r]/igm, '') }`;
|
||||
if (msg) {
|
||||
msg = shortnameToUnicode(msg);
|
||||
}
|
||||
return msg;
|
||||
return `${ prefix }${ lastMessage.msg }`;
|
||||
};
|
||||
|
||||
const arePropsEqual = (oldProps, newProps) => _.isEqual(oldProps, newProps);
|
||||
|
||||
const LastMessage = React.memo(({
|
||||
lastMessage, type, showLastMessage, username, alert, theme
|
||||
lastMessage, type, showLastMessage, username, alert, useRealName, theme
|
||||
}) => (
|
||||
<Markdown
|
||||
msg={formatMsg({
|
||||
lastMessage, type, showLastMessage, username
|
||||
lastMessage, type, showLastMessage, username, useRealName
|
||||
})}
|
||||
style={[styles.markdownText, { color: alert ? themes[theme].bodyText : themes[theme].auxiliaryText }]}
|
||||
customEmojis={false}
|
||||
useRealName={useRealName}
|
||||
numberOfLines={2}
|
||||
preview
|
||||
theme={theme}
|
||||
|
@ -66,6 +63,7 @@ LastMessage.propTypes = {
|
|||
type: PropTypes.string,
|
||||
showLastMessage: PropTypes.bool,
|
||||
username: PropTypes.string,
|
||||
useRealName: PropTypes.bool,
|
||||
alert: PropTypes.bool
|
||||
};
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { View, Text } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
@ -20,6 +20,7 @@ const attrs = [
|
|||
'unread',
|
||||
'userMentions',
|
||||
'showLastMessage',
|
||||
'useRealName',
|
||||
'alert',
|
||||
'type',
|
||||
'width',
|
||||
|
@ -39,8 +40,15 @@ const arePropsEqual = (oldProps, newProps) => {
|
|||
};
|
||||
|
||||
const RoomItem = React.memo(({
|
||||
onPress, width, favorite, toggleFav, isRead, rid, toggleRead, hideChannel, testID, unread, userMentions, name, _updatedAt, alert, type, avatarSize, baseUrl, userId, username, token, id, prid, showLastMessage, hideUnreadStatus, lastMessage, status, avatar, theme
|
||||
onPress, width, favorite, toggleFav, isRead, rid, toggleRead, hideChannel, testID, unread, userMentions, name, _updatedAt, alert, type, avatarSize, baseUrl, userId, username, token, id, prid, showLastMessage, hideUnreadStatus, lastMessage, status, avatar, useRealName, getUserPresence, theme
|
||||
}) => {
|
||||
useEffect(() => {
|
||||
if (type === 'd' && rid) {
|
||||
const uid = rid.replace(userId, '');
|
||||
getUserPresence(uid);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const date = formatDate(_updatedAt);
|
||||
|
||||
let accessibilityLabel = name;
|
||||
|
@ -144,6 +152,7 @@ const RoomItem = React.memo(({
|
|||
showLastMessage={showLastMessage}
|
||||
username={username}
|
||||
alert={alert && !hideUnreadStatus}
|
||||
useRealName={useRealName}
|
||||
theme={theme}
|
||||
/>
|
||||
<UnreadBadge
|
||||
|
@ -187,12 +196,15 @@ RoomItem.propTypes = {
|
|||
hideChannel: PropTypes.func,
|
||||
avatar: PropTypes.bool,
|
||||
hideUnreadStatus: PropTypes.bool,
|
||||
useRealName: PropTypes.bool,
|
||||
getUserPresence: PropTypes.func,
|
||||
theme: PropTypes.string
|
||||
};
|
||||
|
||||
RoomItem.defaultProps = {
|
||||
avatarSize: 48,
|
||||
status: 'offline'
|
||||
status: 'offline',
|
||||
getUserPresence: () => {}
|
||||
};
|
||||
|
||||
const mapStateToProps = (state, ownProps) => ({
|
||||
|
|
|
@ -44,7 +44,9 @@ const UserItem = ({
|
|||
}) => {
|
||||
const longPress = ({ nativeEvent }) => {
|
||||
if (nativeEvent.state === State.ACTIVE) {
|
||||
onLongPress();
|
||||
if (onLongPress) {
|
||||
onLongPress();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ import { combineReducers } from 'redux';
|
|||
import settings from './reducers';
|
||||
import login from './login';
|
||||
import meteor from './connect';
|
||||
import room from './room';
|
||||
import rooms from './rooms';
|
||||
import server from './server';
|
||||
import selectedUsers from './selectedUsers';
|
||||
|
@ -9,7 +10,6 @@ import createChannel from './createChannel';
|
|||
import app from './app';
|
||||
import sortPreferences from './sortPreferences';
|
||||
import notification from './notification';
|
||||
import markdown from './markdown';
|
||||
import share from './share';
|
||||
import crashReport from './crashReport';
|
||||
import customEmojis from './customEmojis';
|
||||
|
@ -25,10 +25,10 @@ export default combineReducers({
|
|||
selectedUsers,
|
||||
createChannel,
|
||||
app,
|
||||
room,
|
||||
rooms,
|
||||
sortPreferences,
|
||||
notification,
|
||||
markdown,
|
||||
share,
|
||||
crashReport,
|
||||
customEmojis,
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
import { TOGGLE_MARKDOWN } from '../actions/actionsTypes';
|
||||
|
||||
const initialState = {
|
||||
useMarkdown: true
|
||||
};
|
||||
|
||||
|
||||
export default (state = initialState, action) => {
|
||||
switch (action.type) {
|
||||
case TOGGLE_MARKDOWN:
|
||||
return {
|
||||
useMarkdown: action.payload
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
|
@ -2,11 +2,6 @@ import * as types from '../constants/types';
|
|||
import initialState from './initialState';
|
||||
|
||||
export default function settings(state = initialState.settings, action) {
|
||||
if (action.type === types.SET_ALL_SETTINGS) {
|
||||
return {
|
||||
...action.payload
|
||||
};
|
||||
}
|
||||
if (action.type === types.ADD_SETTINGS) {
|
||||
return {
|
||||
...state,
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
import { ROOM } from '../actions/actionsTypes';
|
||||
|
||||
const initialState = {
|
||||
rid: null,
|
||||
isDeleting: false
|
||||
};
|
||||
|
||||
export default function(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case ROOM.DELETE_INIT:
|
||||
return {
|
||||
...state,
|
||||
rid: action.rid,
|
||||
isDeleting: true
|
||||
};
|
||||
case ROOM.DELETE_FINISH:
|
||||
return {
|
||||
...state,
|
||||
isDeleting: false
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
|
@ -1,10 +1,12 @@
|
|||
import {
|
||||
select, put, call, take, takeLatest
|
||||
} from 'redux-saga/effects';
|
||||
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
||||
|
||||
import { CREATE_CHANNEL, LOGIN } from '../actions/actionsTypes';
|
||||
import { createChannelSuccess, createChannelFailure } from '../actions/createChannel';
|
||||
import RocketChat from '../lib/rocketchat';
|
||||
import database from '../lib/database';
|
||||
|
||||
const create = function* create(data) {
|
||||
return yield RocketChat.createChannel(data);
|
||||
|
@ -16,8 +18,22 @@ const handleRequest = function* handleRequest({ data }) {
|
|||
if (!auth) {
|
||||
yield take(LOGIN.SUCCESS);
|
||||
}
|
||||
const result = yield call(create, data);
|
||||
yield put(createChannelSuccess(result));
|
||||
const sub = yield call(create, data);
|
||||
|
||||
try {
|
||||
const db = database.active;
|
||||
const subCollection = db.collections.get('subscriptions');
|
||||
yield db.action(async() => {
|
||||
await subCollection.create((s) => {
|
||||
s._raw = sanitizedRaw({ id: sub.rid }, subCollection.schema);
|
||||
Object.assign(s, sub);
|
||||
});
|
||||
});
|
||||
} catch {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
yield put(createChannelSuccess(sub));
|
||||
} catch (err) {
|
||||
yield put(createChannelFailure(err));
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ import RNBootSplash from 'react-native-bootsplash';
|
|||
import * as actions from '../actions';
|
||||
import { selectServerRequest } from '../actions/server';
|
||||
import { setAllPreferences } from '../actions/sortPreferences';
|
||||
import { toggleMarkdown } from '../actions/markdown';
|
||||
import { toggleCrashReport } from '../actions/crashReport';
|
||||
import { APP } from '../actions/actionsTypes';
|
||||
import RocketChat from '../lib/rocketchat';
|
||||
|
@ -24,9 +23,6 @@ export const initLocalSettings = function* initLocalSettings() {
|
|||
const sortPreferences = yield RocketChat.getSortPreferences();
|
||||
yield put(setAllPreferences(sortPreferences));
|
||||
|
||||
const useMarkdown = yield RocketChat.getUseMarkdown();
|
||||
yield put(toggleMarkdown(useMarkdown));
|
||||
|
||||
const allowCrashReport = yield RocketChat.getAllowCrashReport();
|
||||
yield put(toggleCrashReport(allowCrashReport));
|
||||
};
|
||||
|
@ -114,15 +110,15 @@ const restore = function* restore() {
|
|||
}
|
||||
};
|
||||
|
||||
const start = function* start({ root }) {
|
||||
const start = function* start({ root, text }) {
|
||||
if (root === 'inside') {
|
||||
yield Navigation.navigate('InsideStack');
|
||||
} else if (root === 'setUsername') {
|
||||
yield Navigation.navigate('SetUsernameView');
|
||||
yield Navigation.navigate('SetUsernameStack');
|
||||
} else if (root === 'outside') {
|
||||
yield Navigation.navigate('OutsideStack');
|
||||
} else if (root === 'loading') {
|
||||
yield Navigation.navigate('AuthLoading');
|
||||
yield Navigation.navigate('AuthLoading', { text });
|
||||
}
|
||||
RNBootSplash.hide();
|
||||
};
|
||||
|
|
|
@ -35,7 +35,13 @@ const handleLoginRequest = function* handleLoginRequest({ credentials, logoutOnE
|
|||
} else {
|
||||
result = yield call(loginWithPasswordCall, credentials);
|
||||
}
|
||||
return yield put(loginSuccess(result));
|
||||
if (!result.username) {
|
||||
yield put(serverFinishAdd());
|
||||
yield put(setUser(result));
|
||||
yield put(appStart('setUsername'));
|
||||
} else {
|
||||
yield put(loginSuccess(result));
|
||||
}
|
||||
} catch (e) {
|
||||
if (logoutOnError && (e.data && e.data.message && /you've been logged out by the server/i.test(e.data.message))) {
|
||||
yield put(logout(true));
|
||||
|
@ -65,8 +71,9 @@ const registerPushToken = function* registerPushToken() {
|
|||
yield RocketChat.registerPushToken();
|
||||
};
|
||||
|
||||
const fetchUserPresence = function* fetchUserPresence() {
|
||||
yield RocketChat.getUserPresence();
|
||||
const fetchUsersPresence = function* fetchUserPresence() {
|
||||
yield RocketChat.getUsersPresence();
|
||||
yield RocketChat.subscribeUsersPresence();
|
||||
};
|
||||
|
||||
const handleLoginSuccess = function* handleLoginSuccess({ user }) {
|
||||
|
@ -81,7 +88,7 @@ const handleLoginSuccess = function* handleLoginSuccess({ user }) {
|
|||
yield fork(fetchRoles);
|
||||
yield fork(fetchSlashCommands);
|
||||
yield fork(registerPushToken);
|
||||
yield fork(fetchUserPresence);
|
||||
yield fork(fetchUsersPresence);
|
||||
|
||||
I18n.locale = user.language;
|
||||
moment.locale(toMomentLocale(user.language));
|
||||
|
@ -117,9 +124,7 @@ const handleLoginSuccess = function* handleLoginSuccess({ user }) {
|
|||
EventEmitter.emit('connected');
|
||||
|
||||
let currentRoot;
|
||||
if (!user.username) {
|
||||
yield put(appStart('setUsername'));
|
||||
} else if (adding) {
|
||||
if (adding) {
|
||||
yield put(serverFinishAdd());
|
||||
yield put(appStart('inside'));
|
||||
} else {
|
||||
|
@ -143,7 +148,7 @@ const handleLoginSuccess = function* handleLoginSuccess({ user }) {
|
|||
};
|
||||
|
||||
const handleLogout = function* handleLogout({ forcedByServer }) {
|
||||
yield put(appStart('loading'));
|
||||
yield put(appStart('loading', I18n.t('Logging_out')));
|
||||
const server = yield select(getServer);
|
||||
if (server) {
|
||||
try {
|
||||
|
@ -162,10 +167,12 @@ const handleLogout = function* handleLogout({ forcedByServer }) {
|
|||
|
||||
// see if there're other logged in servers and selects first one
|
||||
if (servers.length > 0) {
|
||||
const newServer = servers[0].id;
|
||||
const token = yield RNUserDefaults.get(`${ RocketChat.TOKEN_KEY }-${ newServer }`);
|
||||
if (token) {
|
||||
return yield put(selectServerRequest(newServer));
|
||||
for (let i = 0; i < servers.length; i += 1) {
|
||||
const newServer = servers[i].id;
|
||||
const token = yield RNUserDefaults.get(`${ RocketChat.TOKEN_KEY }-${ newServer }`);
|
||||
if (token) {
|
||||
return yield put(selectServerRequest(newServer));
|
||||
}
|
||||
}
|
||||
}
|
||||
// if there's no servers, go outside
|
||||
|
@ -193,7 +200,6 @@ const root = function* root() {
|
|||
while (true) {
|
||||
const params = yield take(types.LOGIN.SUCCESS);
|
||||
const loginSuccessTask = yield fork(handleLoginSuccess, params);
|
||||
// yield take(types.SERVER.SELECT_REQUEST);
|
||||
yield race({
|
||||
selectRequest: take(types.SERVER.SELECT_REQUEST),
|
||||
timeout: delay(2000)
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import { Alert } from 'react-native';
|
||||
import {
|
||||
takeLatest, take, select, delay
|
||||
takeLatest, take, select, delay, race, put
|
||||
} from 'redux-saga/effects';
|
||||
|
||||
import Navigation from '../lib/Navigation';
|
||||
import * as types from '../actions/actionsTypes';
|
||||
import { deleteRoomFinish } from '../actions/room';
|
||||
import RocketChat from '../lib/rocketchat';
|
||||
import log from '../utils/log';
|
||||
import I18n from '../i18n';
|
||||
|
@ -42,20 +43,28 @@ const handleLeaveRoom = function* handleLeaveRoom({ rid, t }) {
|
|||
}
|
||||
};
|
||||
|
||||
const handleEraseRoom = function* handleEraseRoom({ rid, t }) {
|
||||
const handleDeleteRoom = function* handleDeleteRoom({ rid, t }) {
|
||||
try {
|
||||
const result = yield RocketChat.eraseRoom(rid, t);
|
||||
const result = yield RocketChat.deleteRoom(rid, t);
|
||||
if (result.success) {
|
||||
yield Navigation.navigate('RoomsListView');
|
||||
}
|
||||
// types.ROOM.DELETE_FINISH is triggered by `subscriptions-changed` with `removed` arg
|
||||
const { timeout } = yield race({
|
||||
deleteFinished: take(types.ROOM.DELETE_FINISH),
|
||||
timeout: delay(3000)
|
||||
});
|
||||
if (timeout) {
|
||||
put(deleteRoomFinish());
|
||||
}
|
||||
} catch (e) {
|
||||
Alert.alert(I18n.t('Oops'), I18n.t('There_was_an_error_while_action', { action: I18n.t('erasing_room') }));
|
||||
Alert.alert(I18n.t('Oops'), I18n.t('There_was_an_error_while_action', { action: I18n.t('deleting_room') }));
|
||||
}
|
||||
};
|
||||
|
||||
const root = function* root() {
|
||||
yield takeLatest(types.ROOM.USER_TYPING, watchUserTyping);
|
||||
yield takeLatest(types.ROOM.LEAVE, handleLeaveRoom);
|
||||
yield takeLatest(types.ROOM.ERASE, handleEraseRoom);
|
||||
yield takeLatest(types.ROOM.DELETE_INIT, handleDeleteRoom);
|
||||
};
|
||||
export default root;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {
|
||||
put, take, takeLatest, fork, cancel, race
|
||||
put, take, takeLatest, fork, cancel, race, select
|
||||
} from 'redux-saga/effects';
|
||||
import { Alert } from 'react-native';
|
||||
import RNUserDefaults from 'rn-user-defaults';
|
||||
|
@ -15,10 +15,11 @@ import {
|
|||
import { setUser } from '../actions/login';
|
||||
import RocketChat from '../lib/rocketchat';
|
||||
import database from '../lib/database';
|
||||
import log from '../utils/log';
|
||||
import log, { logServerVersion } from '../utils/log';
|
||||
import { extractHostname } from '../utils/server';
|
||||
import I18n from '../i18n';
|
||||
import { SERVERS, TOKEN, SERVER_URL } from '../constants/userDefaults';
|
||||
import { BASIC_AUTH_KEY, setBasicAuth } from '../utils/fetch';
|
||||
|
||||
const getServerInfo = function* getServerInfo({ server, raiseError = true }) {
|
||||
try {
|
||||
|
@ -89,6 +90,9 @@ const handleSelectServer = function* handleSelectServer({ server, version, fetch
|
|||
}
|
||||
}
|
||||
|
||||
const basicAuth = yield RNUserDefaults.get(`${ BASIC_AUTH_KEY }-${ server }`);
|
||||
setBasicAuth(basicAuth);
|
||||
|
||||
if (user) {
|
||||
yield RocketChat.connect({ server, user, logoutOnError: true });
|
||||
yield put(setUser(user));
|
||||
|
@ -109,7 +113,11 @@ const handleSelectServer = function* handleSelectServer({ server, version, fetch
|
|||
}
|
||||
|
||||
// Return server version even when offline
|
||||
yield put(selectServerSuccess(server, (serverInfo && serverInfo.version) || version));
|
||||
const serverVersion = (serverInfo && serverInfo.version) || version;
|
||||
|
||||
// we'll set serverVersion as metadata for bugsnag
|
||||
logServerVersion(serverVersion);
|
||||
yield put(selectServerSuccess(server, serverVersion));
|
||||
} catch (e) {
|
||||
yield put(selectServerFailure());
|
||||
log(e);
|
||||
|
@ -126,7 +134,11 @@ const handleServerRequest = function* handleServerRequest({ server, certificate
|
|||
|
||||
if (serverInfo) {
|
||||
const loginServicesLength = yield RocketChat.getLoginServices(server);
|
||||
if (loginServicesLength === 0) {
|
||||
yield RocketChat.getLoginSettings({ server });
|
||||
|
||||
const showFormLogin = yield select(state => state.settings.Accounts_ShowFormLogin);
|
||||
|
||||
if (!loginServicesLength && showFormLogin) {
|
||||
Navigation.navigate('LoginView');
|
||||
} else {
|
||||
Navigation.navigate('LoginSignupView');
|
||||
|
|
|
@ -15,7 +15,7 @@ import {
|
|||
import Navigation from './lib/ShareNavigation';
|
||||
import store from './lib/createStore';
|
||||
import sharedStyles from './views/Styles';
|
||||
import { isNotch, isIOS, supportSystemTheme } from './utils/deviceInfo';
|
||||
import { isNotch, supportSystemTheme } from './utils/deviceInfo';
|
||||
import { defaultHeader, onNavigationStateChange, cardStyle } from './utils/navigation';
|
||||
import RocketChat, { THEME_PREFERENCES_KEY } from './lib/rocketchat';
|
||||
import { ThemeContext } from './theme';
|
||||
|
@ -77,9 +77,6 @@ class Root extends React.Component {
|
|||
}
|
||||
|
||||
init = async() => {
|
||||
if (isIOS) {
|
||||
await RNUserDefaults.setName('group.ios.chat.rocket');
|
||||
}
|
||||
RNUserDefaults.objectForKey(THEME_PREFERENCES_KEY).then(this.setTheme);
|
||||
const currentServer = await RNUserDefaults.get('currentServer');
|
||||
const token = await RNUserDefaults.get(RocketChat.TOKEN_KEY);
|
||||
|
|
|
@ -129,6 +129,17 @@ export const initTabletNav = (setState) => {
|
|||
return null;
|
||||
}
|
||||
|
||||
if (routeName === 'RoomsListView') {
|
||||
const resetAction = StackActions.reset({
|
||||
index: 0,
|
||||
actions: [NavigationActions.navigate({ routeName: 'RoomView', params: {} })]
|
||||
});
|
||||
roomRef.dispatch(resetAction);
|
||||
notificationRef.dispatch(resetAction);
|
||||
setState({ showModal: false });
|
||||
return null;
|
||||
}
|
||||
|
||||
if (routeName === 'NewMessageView') {
|
||||
modalRef.dispatch(NavigationActions.navigate({ routeName, params }));
|
||||
setState({ showModal: true });
|
||||
|
|
|
@ -1,13 +1,29 @@
|
|||
import { Platform } from 'react-native';
|
||||
import DeviceInfo from 'react-native-device-info';
|
||||
import { settings as RocketChatSettings } from '@rocket.chat/sdk';
|
||||
|
||||
// this form is required by Rocket.Chat's parser in "app/statistics/server/lib/UAParserCustom.js"
|
||||
export const headers = { 'User-Agent': `RC Mobile; ${ Platform.OS } ${ DeviceInfo.getSystemVersion() }; v${ DeviceInfo.getVersion() } (${ DeviceInfo.getBuildNumber() })` };
|
||||
export const headers = {
|
||||
'User-Agent': `RC Mobile; ${ Platform.OS } ${ DeviceInfo.getSystemVersion() }; v${ DeviceInfo.getVersion() } (${ DeviceInfo.getBuildNumber() })`
|
||||
};
|
||||
|
||||
let _basicAuth;
|
||||
export const setBasicAuth = (basicAuth) => {
|
||||
_basicAuth = basicAuth;
|
||||
if (basicAuth) {
|
||||
RocketChatSettings.customHeaders = { ...RocketChatSettings.customHeaders, Authorization: `Basic ${ _basicAuth }` };
|
||||
} else {
|
||||
RocketChatSettings.customHeaders = headers;
|
||||
}
|
||||
};
|
||||
export const BASIC_AUTH_KEY = 'BASIC_AUTH_KEY';
|
||||
|
||||
RocketChatSettings.customHeaders = headers;
|
||||
|
||||
export default (url, options = {}) => {
|
||||
let customOptions = { ...options, headers };
|
||||
let customOptions = { ...options, headers: RocketChatSettings.customHeaders };
|
||||
if (options && options.headers) {
|
||||
customOptions = { ...customOptions, headers: { ...options.headers, ...headers } };
|
||||
customOptions = { ...customOptions, headers: { ...options.headers, ...customOptions.headers } };
|
||||
}
|
||||
return fetch(url, customOptions);
|
||||
};
|
||||
|
|
|
@ -5,6 +5,19 @@ import { isIOS } from './deviceInfo';
|
|||
|
||||
export const animateNextTransition = debounce(() => {
|
||||
if (isIOS) {
|
||||
LayoutAnimation.easeInEaseOut();
|
||||
LayoutAnimation.configureNext({
|
||||
duration: 200,
|
||||
create: {
|
||||
type: LayoutAnimation.Types.easeInEaseOut,
|
||||
property: LayoutAnimation.Properties.opacity
|
||||
},
|
||||
update: {
|
||||
type: LayoutAnimation.Types.easeInEaseOut
|
||||
},
|
||||
delete: {
|
||||
type: LayoutAnimation.Types.easeInEaseOut,
|
||||
property: LayoutAnimation.Properties.opacity
|
||||
}
|
||||
});
|
||||
}
|
||||
}, 200, true);
|
||||
|
|
|
@ -8,9 +8,23 @@ export const { analytics } = firebase;
|
|||
export const loggerConfig = bugsnag.config;
|
||||
export const { leaveBreadcrumb } = bugsnag;
|
||||
|
||||
let metadata = {};
|
||||
|
||||
export const logServerVersion = (serverVersion) => {
|
||||
metadata = {
|
||||
serverVersion
|
||||
};
|
||||
};
|
||||
|
||||
export default (e) => {
|
||||
if (e instanceof Error && !__DEV__) {
|
||||
bugsnag.notify(e);
|
||||
bugsnag.notify(e, (report) => {
|
||||
report.metadata = {
|
||||
details: {
|
||||
...metadata
|
||||
}
|
||||
};
|
||||
});
|
||||
} else {
|
||||
console.log(e);
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ export const canUploadFile = (file, serverInfo) => {
|
|||
return { success: false, error: 'error-file-too-large' };
|
||||
}
|
||||
// if white list is empty, all media types are enabled
|
||||
if (!FileUpload_MediaTypeWhiteList) {
|
||||
if (!FileUpload_MediaTypeWhiteList || FileUpload_MediaTypeWhiteList === '*') {
|
||||
return { success: true };
|
||||
}
|
||||
const allowedMime = FileUpload_MediaTypeWhiteList.split(',');
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
export const MessageTypeValues = [
|
||||
{
|
||||
value: 'uj',
|
||||
text: 'Message_HideType_uj'
|
||||
}, {
|
||||
value: 'ul',
|
||||
text: 'Message_HideType_ul'
|
||||
}, {
|
||||
value: 'ru',
|
||||
text: 'Message_HideType_ru'
|
||||
}, {
|
||||
value: 'au',
|
||||
text: 'Message_HideType_au'
|
||||
}, {
|
||||
value: 'mute_unmute',
|
||||
text: 'Message_HideType_mute_unmute'
|
||||
}, {
|
||||
value: 'r',
|
||||
text: 'Message_HideType_r'
|
||||
}, {
|
||||
value: 'ut',
|
||||
text: 'Message_HideType_ut'
|
||||
}, {
|
||||
value: 'wm',
|
||||
text: 'Message_HideType_wm'
|
||||
}, {
|
||||
value: 'rm',
|
||||
text: 'Message_HideType_rm'
|
||||
}, {
|
||||
value: 'subscription_role_added',
|
||||
text: 'Message_HideType_subscription_role_added'
|
||||
}, {
|
||||
value: 'subscription_role_removed',
|
||||
text: 'Message_HideType_subscription_role_removed'
|
||||
}, {
|
||||
value: 'room_archived',
|
||||
text: 'Message_HideType_room_archived'
|
||||
}, {
|
||||
value: 'room_unarchived',
|
||||
text: 'Message_HideType_room_unarchived'
|
||||
}
|
||||
];
|
|
@ -1,12 +1,62 @@
|
|||
import { Linking } from 'react-native';
|
||||
import * as WebBrowser from 'expo-web-browser';
|
||||
import RNUserDefaults from 'rn-user-defaults';
|
||||
import parse from 'url-parse';
|
||||
|
||||
import { themes } from '../constants/colors';
|
||||
|
||||
const openLink = (url, theme = 'light') => WebBrowser.openBrowserAsync(url, {
|
||||
toolbarColor: themes[theme].headerBackground,
|
||||
controlsColor: themes[theme].headerTintColor,
|
||||
collapseToolbar: true,
|
||||
showTitle: true
|
||||
});
|
||||
export const DEFAULT_BROWSER_KEY = 'DEFAULT_BROWSER_KEY';
|
||||
|
||||
const scheme = {
|
||||
chrome: 'googlechrome:',
|
||||
chromeSecure: 'googlechromes:',
|
||||
firefox: 'firefox:',
|
||||
brave: 'brave:'
|
||||
};
|
||||
|
||||
const appSchemeURL = (url, browser) => {
|
||||
let schemeUrl = url;
|
||||
const parsedUrl = parse(url, true);
|
||||
const { protocol } = parsedUrl;
|
||||
const isSecure = ['https:'].includes(protocol);
|
||||
|
||||
if (browser === 'googlechrome') {
|
||||
if (!isSecure) {
|
||||
schemeUrl = url.replace(protocol, scheme.chrome);
|
||||
} else {
|
||||
schemeUrl = url.replace(protocol, scheme.chromeSecure);
|
||||
}
|
||||
} else if (browser === 'firefox') {
|
||||
schemeUrl = `${ scheme.firefox }//open-url?url=${ url }`;
|
||||
} else if (browser === 'brave') {
|
||||
schemeUrl = `${ scheme.brave }//open-url?url=${ url }`;
|
||||
}
|
||||
|
||||
return schemeUrl;
|
||||
};
|
||||
|
||||
const openLink = async(url, theme = 'light') => {
|
||||
try {
|
||||
const browser = await RNUserDefaults.get(DEFAULT_BROWSER_KEY);
|
||||
|
||||
if (browser) {
|
||||
const schemeUrl = appSchemeURL(url, browser.replace(':', ''));
|
||||
await Linking.openURL(schemeUrl);
|
||||
} else {
|
||||
await WebBrowser.openBrowserAsync(url, {
|
||||
toolbarColor: themes[theme].headerBackground,
|
||||
controlsColor: themes[theme].headerTintColor,
|
||||
collapseToolbar: true,
|
||||
showTitle: true
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
try {
|
||||
await Linking.openURL(url);
|
||||
} catch {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default openLink;
|
||||
|
|
|
@ -65,8 +65,22 @@ class AttachmentView extends React.Component {
|
|||
componentDidMount() {
|
||||
const { navigation } = this.props;
|
||||
navigation.setParams({ handleSave: this.handleSave });
|
||||
|
||||
this.willBlurListener = navigation.addListener('willBlur', () => {
|
||||
if (this.videoRef && this.videoRef.stopAsync) {
|
||||
this.videoRef.stopAsync();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.willBlurListener && this.willBlurListener.remove) {
|
||||
this.willBlurListener.remove();
|
||||
}
|
||||
}
|
||||
|
||||
getVideoRef = ref => this.videoRef = ref;
|
||||
|
||||
handleSave = async() => {
|
||||
const { attachment } = this.state;
|
||||
const { user, baseUrl } = this.props;
|
||||
|
@ -117,6 +131,7 @@ class AttachmentView extends React.Component {
|
|||
useNativeControls
|
||||
onLoad={() => this.setState({ loading: false })}
|
||||
onError={console.log}
|
||||
ref={this.getVideoRef}
|
||||
/>
|
||||
);
|
||||
|
||||
|
|
|
@ -1,10 +1,40 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
View, Text, StyleSheet, ActivityIndicator
|
||||
} from 'react-native';
|
||||
|
||||
import I18n from '../i18n';
|
||||
import StatusBar from '../containers/StatusBar';
|
||||
import { withTheme } from '../theme';
|
||||
import { themes } from '../constants/colors';
|
||||
|
||||
export default React.memo(withTheme(({ theme }) => (
|
||||
<>
|
||||
<StatusBar theme={theme} />
|
||||
</>
|
||||
)));
|
||||
import sharedStyles from './Styles';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center'
|
||||
},
|
||||
text: {
|
||||
fontSize: 16,
|
||||
paddingTop: 10,
|
||||
...sharedStyles.textRegular,
|
||||
...sharedStyles.textAlignCenter
|
||||
}
|
||||
});
|
||||
|
||||
export default React.memo(withTheme(({ theme, navigation }) => {
|
||||
const text = navigation.getParam('text');
|
||||
return (
|
||||
<View style={[styles.container, { backgroundColor: themes[theme].backgroundColor }]}>
|
||||
<StatusBar theme={theme} />
|
||||
{text && (
|
||||
<>
|
||||
<ActivityIndicator color={themes[theme].auxiliaryText} size='large' />
|
||||
<Text style={[styles.text, { color: themes[theme].bodyText }]}>{`${ text }\n${ I18n.t('Please_wait') }`}</Text>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}));
|
||||
|
|
|
@ -208,10 +208,7 @@ class CreateChannelView extends React.Component {
|
|||
}
|
||||
|
||||
removeUser = (user) => {
|
||||
const { users, removeUser } = this.props;
|
||||
if (users.length === 1) {
|
||||
return;
|
||||
}
|
||||
const { removeUser } = this.props;
|
||||
removeUser(user);
|
||||
}
|
||||
|
||||
|
@ -285,6 +282,7 @@ class CreateChannelView extends React.Component {
|
|||
username={item.name}
|
||||
onPress={() => this.removeUser(item)}
|
||||
testID={`create-channel-view-item-${ item.name }`}
|
||||
icon='check'
|
||||
baseUrl={baseUrl}
|
||||
user={user}
|
||||
theme={theme}
|
||||
|
|
|
@ -0,0 +1,191 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
StyleSheet, FlatList, View, Text, Linking
|
||||
} from 'react-native';
|
||||
import { SafeAreaView } from 'react-navigation';
|
||||
import RNUserDefaults from 'rn-user-defaults';
|
||||
|
||||
import I18n from '../i18n';
|
||||
import { themedHeader } from '../utils/navigation';
|
||||
import { withTheme } from '../theme';
|
||||
import { themes } from '../constants/colors';
|
||||
import sharedStyles from './Styles';
|
||||
import StatusBar from '../containers/StatusBar';
|
||||
import Separator from '../containers/Separator';
|
||||
import ListItem from '../containers/ListItem';
|
||||
import { CustomIcon } from '../lib/Icons';
|
||||
import { DEFAULT_BROWSER_KEY } from '../utils/openLink';
|
||||
import { isIOS } from '../utils/deviceInfo';
|
||||
|
||||
const DEFAULT_BROWSERS = [
|
||||
{
|
||||
title: I18n.t('In_app'),
|
||||
value: 'inApp'
|
||||
},
|
||||
{
|
||||
title: isIOS ? 'Safari' : I18n.t('Browser'),
|
||||
value: 'systemDefault:'
|
||||
}
|
||||
];
|
||||
|
||||
const BROWSERS = [
|
||||
{
|
||||
title: 'Chrome',
|
||||
value: 'googlechrome:'
|
||||
},
|
||||
{
|
||||
title: 'Firefox',
|
||||
value: 'firefox:'
|
||||
},
|
||||
{
|
||||
title: 'Brave',
|
||||
value: 'brave:'
|
||||
}
|
||||
];
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
list: {
|
||||
paddingBottom: 18
|
||||
},
|
||||
info: {
|
||||
paddingTop: 25,
|
||||
paddingBottom: 18,
|
||||
paddingHorizontal: 16
|
||||
},
|
||||
infoText: {
|
||||
fontSize: 16,
|
||||
...sharedStyles.textRegular
|
||||
}
|
||||
});
|
||||
|
||||
class DefaultBrowserView extends React.Component {
|
||||
static navigationOptions = ({ screenProps }) => ({
|
||||
title: I18n.t('Default_browser'),
|
||||
...themedHeader(screenProps.theme)
|
||||
})
|
||||
|
||||
static propTypes = {
|
||||
theme: PropTypes.string
|
||||
}
|
||||
|
||||
state = {
|
||||
browser: null,
|
||||
supported: []
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
if (isIOS) {
|
||||
this.init();
|
||||
}
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
this.mounted = true;
|
||||
try {
|
||||
const browser = await RNUserDefaults.get(DEFAULT_BROWSER_KEY);
|
||||
this.setState({ browser });
|
||||
} catch {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
init = () => {
|
||||
BROWSERS.forEach((browser) => {
|
||||
const { value } = browser;
|
||||
Linking.canOpenURL(value).then((installed) => {
|
||||
if (installed) {
|
||||
if (this.mounted) {
|
||||
this.setState(({ supported }) => ({ supported: [...supported, browser] }));
|
||||
} else {
|
||||
const { supported } = this.state;
|
||||
this.state.supported = [...supported, browser];
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
isSelected = (value) => {
|
||||
const { browser } = this.state;
|
||||
if (!browser && value === 'inApp') {
|
||||
return true;
|
||||
}
|
||||
return browser === value;
|
||||
}
|
||||
|
||||
changeDefaultBrowser = async(newBrowser) => {
|
||||
try {
|
||||
const browser = newBrowser !== 'inApp' ? newBrowser : null;
|
||||
await RNUserDefaults.set(DEFAULT_BROWSER_KEY, browser);
|
||||
this.setState({ browser });
|
||||
} catch {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
renderSeparator = () => {
|
||||
const { theme } = this.props;
|
||||
return <Separator theme={theme} />;
|
||||
}
|
||||
|
||||
renderIcon = () => {
|
||||
const { theme } = this.props;
|
||||
return <CustomIcon name='check' size={20} color={themes[theme].tintColor} />;
|
||||
}
|
||||
|
||||
renderItem = ({ item }) => {
|
||||
const { theme } = this.props;
|
||||
const { title, value } = item;
|
||||
return (
|
||||
<ListItem
|
||||
title={title}
|
||||
onPress={() => this.changeDefaultBrowser(value)}
|
||||
testID={`default-browser-view-${ title }`}
|
||||
right={this.isSelected(value) ? this.renderIcon : null}
|
||||
theme={theme}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderHeader = () => {
|
||||
const { theme } = this.props;
|
||||
return (
|
||||
<>
|
||||
<View style={styles.info}>
|
||||
<Text style={[styles.infoText, { color: themes[theme].infoText }]}>{I18n.t('Choose_where_you_want_links_be_opened')}</Text>
|
||||
</View>
|
||||
{this.renderSeparator()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { supported } = this.state;
|
||||
const { theme } = this.props;
|
||||
return (
|
||||
<SafeAreaView
|
||||
style={[sharedStyles.container, { backgroundColor: themes[theme].auxiliaryBackground }]}
|
||||
forceInset={{ vertical: 'never' }}
|
||||
testID='default-browser-view'
|
||||
>
|
||||
<StatusBar theme={theme} />
|
||||
<FlatList
|
||||
data={DEFAULT_BROWSERS.concat(supported)}
|
||||
keyExtractor={item => item.value}
|
||||
contentContainerStyle={[
|
||||
styles.list,
|
||||
{ borderColor: themes[theme].separatorColor }
|
||||
]}
|
||||
renderItem={this.renderItem}
|
||||
ListHeaderComponent={this.renderHeader}
|
||||
ListFooterComponent={this.renderSeparator}
|
||||
ItemSeparatorComponent={this.renderSeparator}
|
||||
/>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withTheme(DefaultBrowserView);
|
|
@ -77,7 +77,7 @@ class ForgotPasswordView extends React.Component {
|
|||
showErrorAlert(I18n.t('Forgot_password_If_this_email_is_registered'), I18n.t('Alert'));
|
||||
}
|
||||
} catch (e) {
|
||||
const msg = (e.data && e.data.error) || I18n.t('There_was_an_error_while_action', I18n.t('resetting_password'));
|
||||
const msg = (e.data && e.data.error) || I18n.t('There_was_an_error_while_action', { action: I18n.t('resetting_password') });
|
||||
showErrorAlert(msg, I18n.t('Alert'));
|
||||
}
|
||||
this.setState({ isFetching: false });
|
||||
|
|
|
@ -6,7 +6,6 @@ import { SafeAreaView } from 'react-navigation';
|
|||
|
||||
import RocketChat from '../../lib/rocketchat';
|
||||
import I18n from '../../i18n';
|
||||
import Loading from '../../containers/Loading';
|
||||
import { showErrorAlert } from '../../utils/info';
|
||||
import log from '../../utils/log';
|
||||
import { setUser as setUserAction } from '../../actions/login';
|
||||
|
@ -53,6 +52,9 @@ const LANGUAGES = [
|
|||
}, {
|
||||
label: 'Italiano',
|
||||
value: 'it'
|
||||
}, {
|
||||
label: '日本語',
|
||||
value: 'ja'
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -72,13 +74,12 @@ class LanguageView extends React.Component {
|
|||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
language: props.user ? props.user.language : 'en',
|
||||
saving: false
|
||||
language: props.user ? props.user.language : 'en'
|
||||
};
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
const { language, saving } = this.state;
|
||||
const { language } = this.state;
|
||||
const { user, theme } = this.props;
|
||||
if (nextProps.theme !== theme) {
|
||||
return true;
|
||||
|
@ -86,9 +87,6 @@ class LanguageView extends React.Component {
|
|||
if (nextState.language !== language) {
|
||||
return true;
|
||||
}
|
||||
if (nextState.saving !== saving) {
|
||||
return true;
|
||||
}
|
||||
if (nextProps.user.language !== user.language) {
|
||||
return true;
|
||||
}
|
||||
|
@ -105,9 +103,18 @@ class LanguageView extends React.Component {
|
|||
return;
|
||||
}
|
||||
|
||||
this.setState({ saving: true });
|
||||
const { appStart } = this.props;
|
||||
|
||||
const { user, setUser, appStart } = this.props;
|
||||
await appStart('loading', I18n.t('Change_language_loading'));
|
||||
|
||||
// shows loading for at least 300ms
|
||||
await Promise.all([this.changeLanguage(language), new Promise(resolve => setTimeout(resolve, 300))]);
|
||||
|
||||
await appStart('inside');
|
||||
}
|
||||
|
||||
changeLanguage = async(language) => {
|
||||
const { user, setUser } = this.props;
|
||||
|
||||
const params = {};
|
||||
|
||||
|
@ -132,15 +139,10 @@ class LanguageView extends React.Component {
|
|||
// do nothing
|
||||
}
|
||||
});
|
||||
|
||||
await appStart('loading');
|
||||
await appStart('inside');
|
||||
} catch (e) {
|
||||
showErrorAlert(I18n.t('There_was_an_error_while_action', { action: I18n.t('saving_preferences') }));
|
||||
log(e);
|
||||
}
|
||||
|
||||
this.setState({ saving: false });
|
||||
}
|
||||
|
||||
renderSeparator = () => {
|
||||
|
@ -171,7 +173,6 @@ class LanguageView extends React.Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { saving } = this.state;
|
||||
const { theme } = this.props;
|
||||
return (
|
||||
<SafeAreaView
|
||||
|
@ -193,7 +194,6 @@ class LanguageView extends React.Component {
|
|||
renderItem={this.renderItem}
|
||||
ItemSeparatorComponent={this.renderSeparator}
|
||||
/>
|
||||
<Loading visible={saving} />
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
@ -205,7 +205,7 @@ const mapStateToProps = state => ({
|
|||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
setUser: params => dispatch(setUserAction(params)),
|
||||
appStart: params => dispatch(appStartAction(params))
|
||||
appStart: (...params) => dispatch(appStartAction(...params))
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(LanguageView));
|
||||
|
|
|
@ -58,6 +58,11 @@ const styles = StyleSheet.create({
|
|||
serviceName: {
|
||||
...sharedStyles.textBold
|
||||
},
|
||||
registerDisabled: {
|
||||
...sharedStyles.textRegular,
|
||||
...sharedStyles.textAlignCenter,
|
||||
fontSize: 16
|
||||
},
|
||||
servicesTogglerContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
|
@ -108,6 +113,9 @@ class LoginSignupView extends React.Component {
|
|||
Gitlab_URL: PropTypes.string,
|
||||
CAS_enabled: PropTypes.bool,
|
||||
CAS_login_url: PropTypes.string,
|
||||
Accounts_ShowFormLogin: PropTypes.bool,
|
||||
Accounts_RegistrationForm: PropTypes.string,
|
||||
Accounts_RegistrationForm_LinkReplacementText: PropTypes.string,
|
||||
theme: PropTypes.string
|
||||
}
|
||||
|
||||
|
@ -124,7 +132,7 @@ class LoginSignupView extends React.Component {
|
|||
shouldComponentUpdate(nextProps, nextState) {
|
||||
const { collapsed, servicesHeight } = this.state;
|
||||
const {
|
||||
server, Site_Name, services, theme
|
||||
server, Site_Name, services, Accounts_ShowFormLogin, Accounts_RegistrationForm, Accounts_RegistrationForm_LinkReplacementText, theme
|
||||
} = this.props;
|
||||
if (nextState.collapsed !== collapsed) {
|
||||
return true;
|
||||
|
@ -141,6 +149,15 @@ class LoginSignupView extends React.Component {
|
|||
if (nextProps.theme !== theme) {
|
||||
return true;
|
||||
}
|
||||
if (nextProps.Accounts_ShowFormLogin !== Accounts_ShowFormLogin) {
|
||||
return true;
|
||||
}
|
||||
if (nextProps.Accounts_RegistrationForm !== Accounts_RegistrationForm) {
|
||||
return true;
|
||||
}
|
||||
if (nextProps.Accounts_RegistrationForm_LinkReplacementText !== Accounts_RegistrationForm_LinkReplacementText) {
|
||||
return true;
|
||||
}
|
||||
if (!equal(nextProps.services, services)) {
|
||||
return true;
|
||||
}
|
||||
|
@ -333,10 +350,12 @@ class LoginSignupView extends React.Component {
|
|||
|
||||
renderServicesSeparator = () => {
|
||||
const { collapsed } = this.state;
|
||||
const { services, theme } = this.props;
|
||||
const {
|
||||
services, theme, Accounts_ShowFormLogin, Accounts_RegistrationForm
|
||||
} = this.props;
|
||||
const { length } = Object.values(services);
|
||||
|
||||
if (length > 3) {
|
||||
if (length > 3 && Accounts_ShowFormLogin && Accounts_RegistrationForm) {
|
||||
return (
|
||||
<View style={styles.servicesTogglerContainer}>
|
||||
<View style={[styles.separatorLine, styles.separatorLineLeft, { backgroundColor: themes[theme].auxiliaryText }]} />
|
||||
|
@ -358,6 +377,7 @@ class LoginSignupView extends React.Component {
|
|||
let { name } = service;
|
||||
name = name === 'meteor-developer' ? 'meteor' : name;
|
||||
const icon = `icon_${ name }`;
|
||||
const isSaml = service.service === 'saml';
|
||||
let onPress = () => {};
|
||||
|
||||
switch (service.authType) {
|
||||
|
@ -383,8 +403,8 @@ class LoginSignupView extends React.Component {
|
|||
name = name.charAt(0).toUpperCase() + name.slice(1);
|
||||
const { CAS_enabled, theme } = this.props;
|
||||
let buttonText;
|
||||
if (service.service === 'saml' || (service.service === 'cas' && CAS_enabled)) {
|
||||
buttonText = <Text style={styles.serviceName}>{name}</Text>;
|
||||
if (isSaml || (service.service === 'cas' && CAS_enabled)) {
|
||||
buttonText = <Text style={[styles.serviceName, isSaml && { color: service.buttonLabelColor }]}>{name}</Text>;
|
||||
} else {
|
||||
buttonText = (
|
||||
<>
|
||||
|
@ -396,7 +416,7 @@ class LoginSignupView extends React.Component {
|
|||
<Touch
|
||||
key={service.name}
|
||||
onPress={onPress}
|
||||
style={styles.serviceButton}
|
||||
style={[styles.serviceButton, isSaml && { backgroundColor: service.buttonColor }]}
|
||||
theme={theme}
|
||||
>
|
||||
<View style={[styles.serviceButtonContainer, { borderColor: themes[theme].borderColor }]}>
|
||||
|
@ -409,15 +429,14 @@ class LoginSignupView extends React.Component {
|
|||
|
||||
renderServices = () => {
|
||||
const { servicesHeight } = this.state;
|
||||
const { services } = this.props;
|
||||
const { services, Accounts_ShowFormLogin, Accounts_RegistrationForm } = this.props;
|
||||
const { length } = Object.values(services);
|
||||
const style = {
|
||||
overflow: 'hidden',
|
||||
height: servicesHeight
|
||||
};
|
||||
|
||||
|
||||
if (length > 3) {
|
||||
if (length > 3 && Accounts_ShowFormLogin && Accounts_RegistrationForm) {
|
||||
return (
|
||||
<Animated.View style={style}>
|
||||
{Object.values(services).map(service => this.renderItem(service))}
|
||||
|
@ -431,6 +450,38 @@ class LoginSignupView extends React.Component {
|
|||
);
|
||||
}
|
||||
|
||||
renderLogin = () => {
|
||||
const { Accounts_ShowFormLogin, theme } = this.props;
|
||||
if (!Accounts_ShowFormLogin) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Button
|
||||
title={<Text>{I18n.t('Login_with')} <Text style={{ ...sharedStyles.textBold }}>{I18n.t('email')}</Text></Text>}
|
||||
type='primary'
|
||||
onPress={() => this.login()}
|
||||
theme={theme}
|
||||
testID='welcome-view-login'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderRegister = () => {
|
||||
const { Accounts_RegistrationForm, Accounts_RegistrationForm_LinkReplacementText, theme } = this.props;
|
||||
if (Accounts_RegistrationForm !== 'Public') {
|
||||
return <Text style={[styles.registerDisabled, { color: themes[theme].auxiliaryText }]}>{Accounts_RegistrationForm_LinkReplacementText}</Text>;
|
||||
}
|
||||
return (
|
||||
<Button
|
||||
title={I18n.t('Create_account')}
|
||||
type='secondary'
|
||||
onPress={() => this.register()}
|
||||
theme={theme}
|
||||
testID='welcome-view-register'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { theme } = this.props;
|
||||
return (
|
||||
|
@ -452,20 +503,8 @@ class LoginSignupView extends React.Component {
|
|||
<StatusBar theme={theme} />
|
||||
{this.renderServices()}
|
||||
{this.renderServicesSeparator()}
|
||||
<Button
|
||||
title={<Text>{I18n.t('Login_with')} <Text style={{ ...sharedStyles.textBold }}>{I18n.t('email')}</Text></Text>}
|
||||
type='primary'
|
||||
onPress={() => this.login()}
|
||||
theme={theme}
|
||||
testID='welcome-view-login'
|
||||
/>
|
||||
<Button
|
||||
title={I18n.t('Create_account')}
|
||||
type='secondary'
|
||||
onPress={() => this.register()}
|
||||
theme={theme}
|
||||
testID='welcome-view-register'
|
||||
/>
|
||||
{this.renderLogin()}
|
||||
{this.renderRegister()}
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
);
|
||||
|
@ -478,6 +517,9 @@ const mapStateToProps = state => ({
|
|||
Gitlab_URL: state.settings.API_Gitlab_URL,
|
||||
CAS_enabled: state.settings.CAS_enabled,
|
||||
CAS_login_url: state.settings.CAS_login_url,
|
||||
Accounts_ShowFormLogin: state.settings.Accounts_ShowFormLogin,
|
||||
Accounts_RegistrationForm: state.settings.Accounts_RegistrationForm,
|
||||
Accounts_RegistrationForm_LinkReplacementText: state.settings.Accounts_RegistrationForm_LinkReplacementText,
|
||||
services: state.login.services
|
||||
});
|
||||
|
||||
|
|
|
@ -29,6 +29,11 @@ const styles = StyleSheet.create({
|
|||
alignItems: 'center',
|
||||
marginTop: 10
|
||||
},
|
||||
registerDisabled: {
|
||||
...sharedStyles.textRegular,
|
||||
...sharedStyles.textAlignCenter,
|
||||
fontSize: 13
|
||||
},
|
||||
dontHaveAccount: {
|
||||
...sharedStyles.textRegular,
|
||||
fontSize: 13
|
||||
|
@ -61,6 +66,8 @@ class LoginView extends React.Component {
|
|||
Accounts_EmailOrUsernamePlaceholder: PropTypes.string,
|
||||
Accounts_PasswordPlaceholder: PropTypes.string,
|
||||
Accounts_PasswordReset: PropTypes.bool,
|
||||
Accounts_RegistrationForm: PropTypes.string,
|
||||
Accounts_RegistrationForm_LinkReplacementText: PropTypes.string,
|
||||
isFetching: PropTypes.bool,
|
||||
failure: PropTypes.bool,
|
||||
theme: PropTypes.string
|
||||
|
@ -101,7 +108,7 @@ class LoginView extends React.Component {
|
|||
user, password, code, showTOTP
|
||||
} = this.state;
|
||||
const {
|
||||
isFetching, failure, error, Site_Name, Accounts_EmailOrUsernamePlaceholder, Accounts_PasswordPlaceholder, theme
|
||||
isFetching, failure, error, Site_Name, Accounts_EmailOrUsernamePlaceholder, Accounts_PasswordPlaceholder, Accounts_RegistrationForm, Accounts_RegistrationForm_LinkReplacementText, theme
|
||||
} = this.props;
|
||||
if (nextState.user !== user) {
|
||||
return true;
|
||||
|
@ -133,6 +140,12 @@ class LoginView extends React.Component {
|
|||
if (nextProps.Accounts_PasswordPlaceholder !== Accounts_PasswordPlaceholder) {
|
||||
return true;
|
||||
}
|
||||
if (nextProps.Accounts_RegistrationForm !== Accounts_RegistrationForm) {
|
||||
return true;
|
||||
}
|
||||
if (nextProps.Accounts_RegistrationForm_LinkReplacementText !== Accounts_RegistrationForm_LinkReplacementText) {
|
||||
return true;
|
||||
}
|
||||
if (!equal(nextProps.error, error)) {
|
||||
return true;
|
||||
}
|
||||
|
@ -225,7 +238,7 @@ class LoginView extends React.Component {
|
|||
|
||||
renderUserForm = () => {
|
||||
const {
|
||||
Accounts_EmailOrUsernamePlaceholder, Accounts_PasswordPlaceholder, Accounts_PasswordReset, isFetching, theme
|
||||
Accounts_EmailOrUsernamePlaceholder, Accounts_PasswordPlaceholder, Accounts_PasswordReset, Accounts_RegistrationForm, Accounts_RegistrationForm_LinkReplacementText, isFetching, theme
|
||||
} = this.props;
|
||||
return (
|
||||
<SafeAreaView
|
||||
|
@ -283,15 +296,17 @@ class LoginView extends React.Component {
|
|||
theme={theme}
|
||||
/>
|
||||
)}
|
||||
<View style={styles.bottomContainer}>
|
||||
<Text style={[styles.dontHaveAccount, { color: themes[theme].auxiliaryText }]}>{I18n.t('Dont_Have_An_Account')}</Text>
|
||||
<Text
|
||||
style={[styles.createAccount, { color: themes[theme].actionTintColor }]}
|
||||
onPress={this.register}
|
||||
testID='login-view-register'
|
||||
>{I18n.t('Create_account')}
|
||||
</Text>
|
||||
</View>
|
||||
{Accounts_RegistrationForm === 'Public' ? (
|
||||
<View style={styles.bottomContainer}>
|
||||
<Text style={[styles.dontHaveAccount, { color: themes[theme].auxiliaryText }]}>{I18n.t('Dont_Have_An_Account')}</Text>
|
||||
<Text
|
||||
style={[styles.createAccount, { color: themes[theme].actionTintColor }]}
|
||||
onPress={this.register}
|
||||
testID='login-view-register'
|
||||
>{I18n.t('Create_account')}
|
||||
</Text>
|
||||
</View>
|
||||
) : (<Text style={[styles.registerDisabled, { color: themes[theme].auxiliaryText }]}>{Accounts_RegistrationForm_LinkReplacementText}</Text>)}
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
@ -323,6 +338,8 @@ const mapStateToProps = state => ({
|
|||
Site_Name: state.settings.Site_Name,
|
||||
Accounts_EmailOrUsernamePlaceholder: state.settings.Accounts_EmailOrUsernamePlaceholder,
|
||||
Accounts_PasswordPlaceholder: state.settings.Accounts_PasswordPlaceholder,
|
||||
Accounts_RegistrationForm: state.settings.Accounts_RegistrationForm,
|
||||
Accounts_RegistrationForm_LinkReplacementText: state.settings.Accounts_RegistrationForm_LinkReplacementText,
|
||||
Accounts_PasswordReset: state.settings.Accounts_PasswordReset
|
||||
});
|
||||
|
||||
|
|
|
@ -9,6 +9,9 @@ import * as FileSystem from 'expo-file-system';
|
|||
import DocumentPicker from 'react-native-document-picker';
|
||||
import ActionSheet from 'react-native-action-sheet';
|
||||
import isEqual from 'deep-equal';
|
||||
import RNUserDefaults from 'rn-user-defaults';
|
||||
import { encode } from 'base-64';
|
||||
import parse from 'url-parse';
|
||||
|
||||
import { serverRequest } from '../actions/server';
|
||||
import sharedStyles from './Styles';
|
||||
|
@ -25,6 +28,7 @@ import { themes } from '../constants/colors';
|
|||
import log from '../utils/log';
|
||||
import { animateNextTransition } from '../utils/layoutAnimation';
|
||||
import { withTheme } from '../theme';
|
||||
import { setBasicAuth, BASIC_AUTH_KEY } from '../utils/fetch';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
image: {
|
||||
|
@ -148,7 +152,22 @@ class NewServerView extends React.Component {
|
|||
|
||||
if (text) {
|
||||
Keyboard.dismiss();
|
||||
connectServer(this.completeUrl(text), cert);
|
||||
const server = this.completeUrl(text);
|
||||
await this.basicAuth(server, text);
|
||||
connectServer(server, cert);
|
||||
}
|
||||
}
|
||||
|
||||
basicAuth = async(server, text) => {
|
||||
try {
|
||||
const parsedUrl = parse(text, true);
|
||||
if (parsedUrl.auth.length) {
|
||||
const credentials = encode(parsedUrl.auth);
|
||||
await RNUserDefaults.set(`${ BASIC_AUTH_KEY }-${ server }`, credentials);
|
||||
setBasicAuth(credentials);
|
||||
}
|
||||
} catch {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -177,6 +196,11 @@ class NewServerView extends React.Component {
|
|||
}
|
||||
|
||||
completeUrl = (url) => {
|
||||
const parsedUrl = parse(url, true);
|
||||
if (parsedUrl.auth.length) {
|
||||
url = parsedUrl.host;
|
||||
}
|
||||
|
||||
url = url && url.replace(/\s/g, '');
|
||||
|
||||
if (/^(\w|[0-9-_]){3,}$/.test(url)
|
||||
|
|
|
@ -242,7 +242,6 @@ class NotificationPreferencesView extends React.Component {
|
|||
{...scrollPersistTaps}
|
||||
style={{ backgroundColor: themes[theme].auxiliaryBackground }}
|
||||
contentContainerStyle={styles.contentContainer}
|
||||
showsVerticalScrollIndicator={false}
|
||||
testID='notification-preference-view-list'
|
||||
>
|
||||
<Separator theme={theme} />
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
View, Text, Image, TouchableOpacity, BackHandler
|
||||
View, Text, Image, TouchableOpacity, BackHandler, Linking
|
||||
} from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
|
@ -10,7 +10,6 @@ import Orientation from 'react-native-orientation-locker';
|
|||
import { selectServerRequest, serverInitAdd, serverFinishAdd } from '../../actions/server';
|
||||
import { appStart as appStartAction } from '../../actions';
|
||||
import I18n from '../../i18n';
|
||||
import openLink from '../../utils/openLink';
|
||||
import Button from './Button';
|
||||
import styles from './styles';
|
||||
import { isIOS, isNotch, isTablet } from '../../utils/deviceInfo';
|
||||
|
@ -105,9 +104,12 @@ class OnboardingView extends React.Component {
|
|||
this.newServer('https://open.rocket.chat');
|
||||
}
|
||||
|
||||
createWorkspace = () => {
|
||||
const { theme } = this.props;
|
||||
openLink('https://cloud.rocket.chat/trial', theme);
|
||||
createWorkspace = async() => {
|
||||
try {
|
||||
await Linking.openURL('https://cloud.rocket.chat/trial');
|
||||
} catch {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
renderClose = () => {
|
||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import { View, ScrollView, Keyboard } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import Dialog from 'react-native-dialog';
|
||||
import prompt from 'react-native-prompt-android';
|
||||
import SHA256 from 'js-sha256';
|
||||
import ImagePicker from 'react-native-image-crop-picker';
|
||||
import RNPickerSelect from 'react-native-picker-select';
|
||||
|
@ -50,13 +50,17 @@ class ProfileView extends React.Component {
|
|||
static propTypes = {
|
||||
baseUrl: PropTypes.string,
|
||||
user: PropTypes.object,
|
||||
Accounts_AllowEmailChange: PropTypes.bool,
|
||||
Accounts_AllowPasswordChange: PropTypes.bool,
|
||||
Accounts_AllowRealNameChange: PropTypes.bool,
|
||||
Accounts_AllowUserAvatarChange: PropTypes.bool,
|
||||
Accounts_AllowUsernameChange: PropTypes.bool,
|
||||
Accounts_CustomFields: PropTypes.string,
|
||||
setUser: PropTypes.func,
|
||||
theme: PropTypes.string
|
||||
}
|
||||
|
||||
state = {
|
||||
showPasswordAlert: false,
|
||||
saving: false,
|
||||
name: null,
|
||||
username: null,
|
||||
|
@ -98,6 +102,12 @@ class ProfileView extends React.Component {
|
|||
}
|
||||
|
||||
setAvatar = (avatar) => {
|
||||
const { Accounts_AllowUserAvatarChange } = this.props;
|
||||
|
||||
if (!Accounts_AllowUserAvatarChange) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({ avatar });
|
||||
}
|
||||
|
||||
|
@ -144,19 +154,11 @@ class ProfileView extends React.Component {
|
|||
);
|
||||
}
|
||||
|
||||
closePasswordAlert = () => {
|
||||
this.setState({ showPasswordAlert: false });
|
||||
}
|
||||
|
||||
handleError = (e, func, action) => {
|
||||
if (e.data && e.data.errorType === 'error-too-many-requests') {
|
||||
return showErrorAlert(e.data.error);
|
||||
}
|
||||
showErrorAlert(
|
||||
I18n.t('There_was_an_error_while_action', { action: I18n.t(action) }),
|
||||
'',
|
||||
() => this.setState({ showPasswordAlert: false })
|
||||
);
|
||||
showErrorAlert(I18n.t('There_was_an_error_while_action', { action: I18n.t(action) }));
|
||||
}
|
||||
|
||||
submit = async() => {
|
||||
|
@ -201,7 +203,26 @@ class ProfileView extends React.Component {
|
|||
|
||||
const requirePassword = !!params.email || newPassword;
|
||||
if (requirePassword && !params.currentPassword) {
|
||||
return this.setState({ showPasswordAlert: true, saving: false });
|
||||
this.setState({ saving: false });
|
||||
prompt(
|
||||
I18n.t('Please_enter_your_password'),
|
||||
I18n.t('For_your_security_you_must_enter_your_current_password_to_continue'),
|
||||
[
|
||||
{ text: I18n.t('Cancel'), onPress: () => {}, style: 'cancel' },
|
||||
{
|
||||
text: I18n.t('Save'),
|
||||
onPress: (p) => {
|
||||
this.setState({ currentPassword: p });
|
||||
this.submit();
|
||||
}
|
||||
}
|
||||
],
|
||||
{
|
||||
type: 'secure-text',
|
||||
cancelable: false
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
|
@ -222,7 +243,7 @@ class ProfileView extends React.Component {
|
|||
} else {
|
||||
setUser({ ...params });
|
||||
}
|
||||
this.setState({ saving: false, showPasswordAlert: false });
|
||||
this.setState({ saving: false });
|
||||
EventEmitter.emit(LISTENER, { message: I18n.t('Profile_saved_successfully') });
|
||||
this.init();
|
||||
}
|
||||
|
@ -233,6 +254,12 @@ class ProfileView extends React.Component {
|
|||
}
|
||||
|
||||
resetAvatar = async() => {
|
||||
const { Accounts_AllowUserAvatarChange } = this.props;
|
||||
|
||||
if (!Accounts_AllowUserAvatarChange) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const { user } = this.props;
|
||||
await RocketChat.resetAvatar(user.id);
|
||||
|
@ -244,6 +271,12 @@ class ProfileView extends React.Component {
|
|||
}
|
||||
|
||||
pickImage = async() => {
|
||||
const { Accounts_AllowUserAvatarChange } = this.props;
|
||||
|
||||
if (!Accounts_AllowUserAvatarChange) {
|
||||
return;
|
||||
}
|
||||
|
||||
const options = {
|
||||
cropping: true,
|
||||
compressImageQuality: 0.8,
|
||||
|
@ -280,18 +313,25 @@ class ProfileView extends React.Component {
|
|||
|
||||
renderAvatarButtons = () => {
|
||||
const { avatarUrl, avatarSuggestions } = this.state;
|
||||
const { user, baseUrl, theme } = this.props;
|
||||
const {
|
||||
user,
|
||||
baseUrl,
|
||||
theme,
|
||||
Accounts_AllowUserAvatarChange
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<View style={styles.avatarButtons}>
|
||||
{this.renderAvatarButton({
|
||||
child: <Avatar text={`@${ user.username }`} size={50} baseUrl={baseUrl} userId={user.id} token={user.token} />,
|
||||
onPress: () => this.resetAvatar(),
|
||||
disabled: !Accounts_AllowUserAvatarChange,
|
||||
key: 'profile-view-reset-avatar'
|
||||
})}
|
||||
{this.renderAvatarButton({
|
||||
child: <CustomIcon name='upload' size={30} color={themes[theme].bodyText} />,
|
||||
onPress: () => this.pickImage(),
|
||||
disabled: !Accounts_AllowUserAvatarChange,
|
||||
key: 'profile-view-upload-avatar'
|
||||
})}
|
||||
{this.renderAvatarButton({
|
||||
|
@ -303,6 +343,7 @@ class ProfileView extends React.Component {
|
|||
{Object.keys(avatarSuggestions).map((service) => {
|
||||
const { url, blob, contentType } = avatarSuggestions[service];
|
||||
return this.renderAvatarButton({
|
||||
disabled: !Accounts_AllowUserAvatarChange,
|
||||
key: `profile-view-avatar-${ service }`,
|
||||
child: <Avatar avatar={url} size={50} baseUrl={baseUrl} userId={user.id} token={user.token} />,
|
||||
onPress: () => this.setAvatar({
|
||||
|
@ -378,10 +419,18 @@ class ProfileView extends React.Component {
|
|||
|
||||
render() {
|
||||
const {
|
||||
name, username, email, newPassword, avatarUrl, customFields, avatar, saving, showPasswordAlert
|
||||
name, username, email, newPassword, avatarUrl, customFields, avatar, saving
|
||||
} = this.state;
|
||||
const {
|
||||
baseUrl, user, theme, Accounts_CustomFields
|
||||
baseUrl,
|
||||
user,
|
||||
theme,
|
||||
Accounts_AllowEmailChange,
|
||||
Accounts_AllowPasswordChange,
|
||||
Accounts_AllowRealNameChange,
|
||||
Accounts_AllowUserAvatarChange,
|
||||
Accounts_AllowUsernameChange,
|
||||
Accounts_CustomFields
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
|
@ -408,6 +457,10 @@ class ProfileView extends React.Component {
|
|||
/>
|
||||
</View>
|
||||
<RCTextInput
|
||||
editable={Accounts_AllowRealNameChange}
|
||||
inputStyle={[
|
||||
!Accounts_AllowRealNameChange && styles.disabled
|
||||
]}
|
||||
inputRef={(e) => { this.name = e; }}
|
||||
label={I18n.t('Name')}
|
||||
placeholder={I18n.t('Name')}
|
||||
|
@ -418,6 +471,10 @@ class ProfileView extends React.Component {
|
|||
theme={theme}
|
||||
/>
|
||||
<RCTextInput
|
||||
editable={Accounts_AllowUsernameChange}
|
||||
inputStyle={[
|
||||
!Accounts_AllowUsernameChange && styles.disabled
|
||||
]}
|
||||
inputRef={(e) => { this.username = e; }}
|
||||
label={I18n.t('Username')}
|
||||
placeholder={I18n.t('Username')}
|
||||
|
@ -428,6 +485,10 @@ class ProfileView extends React.Component {
|
|||
theme={theme}
|
||||
/>
|
||||
<RCTextInput
|
||||
editable={Accounts_AllowEmailChange}
|
||||
inputStyle={[
|
||||
!Accounts_AllowEmailChange && styles.disabled
|
||||
]}
|
||||
inputRef={(e) => { this.email = e; }}
|
||||
label={I18n.t('Email')}
|
||||
placeholder={I18n.t('Email')}
|
||||
|
@ -438,6 +499,10 @@ class ProfileView extends React.Component {
|
|||
theme={theme}
|
||||
/>
|
||||
<RCTextInput
|
||||
editable={Accounts_AllowPasswordChange}
|
||||
inputStyle={[
|
||||
!Accounts_AllowPasswordChange && styles.disabled
|
||||
]}
|
||||
inputRef={(e) => { this.newPassword = e; }}
|
||||
label={I18n.t('New_Password')}
|
||||
placeholder={I18n.t('New_Password')}
|
||||
|
@ -455,6 +520,10 @@ class ProfileView extends React.Component {
|
|||
/>
|
||||
{this.renderCustomFields()}
|
||||
<RCTextInput
|
||||
editable={Accounts_AllowUserAvatarChange}
|
||||
inputStyle={[
|
||||
!Accounts_AllowUserAvatarChange && styles.disabled
|
||||
]}
|
||||
inputRef={(e) => { this.avatarUrl = e; }}
|
||||
label={I18n.t('Avatar_Url')}
|
||||
placeholder={I18n.t('Avatar_Url')}
|
||||
|
@ -474,22 +543,6 @@ class ProfileView extends React.Component {
|
|||
loading={saving}
|
||||
theme={theme}
|
||||
/>
|
||||
<Dialog.Container visible={showPasswordAlert}>
|
||||
<Dialog.Title>
|
||||
{I18n.t('Please_enter_your_password')}
|
||||
</Dialog.Title>
|
||||
<Dialog.Description>
|
||||
{I18n.t('For_your_security_you_must_enter_your_current_password_to_continue')}
|
||||
</Dialog.Description>
|
||||
<Dialog.Input
|
||||
onChangeText={value => this.setState({ currentPassword: value })}
|
||||
secureTextEntry
|
||||
testID='profile-view-typed-password'
|
||||
style={styles.dialogInput}
|
||||
/>
|
||||
<Dialog.Button label={I18n.t('Cancel')} onPress={this.closePasswordAlert} />
|
||||
<Dialog.Button label={I18n.t('Save')} onPress={this.submit} />
|
||||
</Dialog.Container>
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
</KeyboardView>
|
||||
|
@ -499,6 +552,11 @@ class ProfileView extends React.Component {
|
|||
|
||||
const mapStateToProps = state => ({
|
||||
user: getUserSelector(state),
|
||||
Accounts_AllowEmailChange: state.settings.Accounts_AllowEmailChange,
|
||||
Accounts_AllowPasswordChange: state.settings.Accounts_AllowPasswordChange,
|
||||
Accounts_AllowRealNameChange: state.settings.Accounts_AllowRealNameChange,
|
||||
Accounts_AllowUserAvatarChange: state.settings.Accounts_AllowUserAvatarChange,
|
||||
Accounts_AllowUsernameChange: state.settings.Accounts_AllowUsernameChange,
|
||||
Accounts_CustomFields: state.settings.Accounts_CustomFields,
|
||||
baseUrl: state.server.server
|
||||
});
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
import { StyleSheet, Platform } from 'react-native';
|
||||
import { StyleSheet } from 'react-native';
|
||||
|
||||
export default StyleSheet.create({
|
||||
disabled: {
|
||||
opacity: 0.3
|
||||
},
|
||||
avatarContainer: {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
|
@ -20,14 +23,5 @@ export default StyleSheet.create({
|
|||
marginRight: 15,
|
||||
marginBottom: 15,
|
||||
borderRadius: 2
|
||||
},
|
||||
dialogInput: Platform.select({
|
||||
ios: {},
|
||||
android: {
|
||||
borderRadius: 4,
|
||||
borderColor: 'rgba(0,0,0,.15)',
|
||||
borderWidth: 2,
|
||||
paddingHorizontal: 10
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Keyboard, Text, ScrollView, Alert
|
||||
} from 'react-native';
|
||||
import { Keyboard, Text, ScrollView } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import { SafeAreaView } from 'react-navigation';
|
||||
import RNPickerSelect from 'react-native-picker-select';
|
||||
|
@ -24,6 +22,7 @@ import { withTheme } from '../theme';
|
|||
import { themes } from '../constants/colors';
|
||||
import { themedHeader } from '../utils/navigation';
|
||||
import { isTablet } from '../utils/deviceInfo';
|
||||
import { showErrorAlert } from '../utils/info';
|
||||
|
||||
const shouldUpdateState = ['name', 'email', 'password', 'username', 'saving'];
|
||||
|
||||
|
@ -129,12 +128,15 @@ class RegisterView extends React.Component {
|
|||
|
||||
if (Accounts_EmailVerification) {
|
||||
await navigation.goBack();
|
||||
Alert.alert(I18n.t('Verify_email_title'), I18n.t('Verify_email_desc'));
|
||||
showErrorAlert(I18n.t('Verify_email_desc'), I18n.t('Verify_email_title'));
|
||||
} else {
|
||||
await loginRequest({ user: email, password });
|
||||
}
|
||||
} catch (e) {
|
||||
Alert.alert(I18n.t('Oops'), e.data.error);
|
||||
if (e.data && e.data.errorType === 'username-invalid') {
|
||||
return loginRequest({ user: email, password });
|
||||
}
|
||||
showErrorAlert(e.data.error, I18n.t('Oops'));
|
||||
}
|
||||
this.setState({ saving: false });
|
||||
}
|
||||
|
|
|
@ -5,45 +5,50 @@ import PropTypes from 'prop-types';
|
|||
import styles from './styles';
|
||||
import { SWITCH_TRACK_COLOR, themes } from '../../constants/colors';
|
||||
|
||||
export default class SwitchContainer extends React.PureComponent {
|
||||
static propTypes = {
|
||||
value: PropTypes.bool,
|
||||
disabled: PropTypes.bool,
|
||||
leftLabelPrimary: PropTypes.string,
|
||||
leftLabelSecondary: PropTypes.string,
|
||||
rightLabelPrimary: PropTypes.string,
|
||||
rightLabelSecondary: PropTypes.string,
|
||||
onValueChange: PropTypes.func,
|
||||
theme: PropTypes.string,
|
||||
testID: PropTypes.string
|
||||
}
|
||||
const SwitchContainer = React.memo(({
|
||||
children, value, disabled, onValueChange, leftLabelPrimary, leftLabelSecondary, rightLabelPrimary, rightLabelSecondary, theme, testID, labelContainerStyle, leftLabelStyle
|
||||
}) => (
|
||||
<>
|
||||
<View key='switch-container' style={[styles.switchContainer, children && styles.switchMargin]}>
|
||||
{leftLabelPrimary && (
|
||||
<View style={[styles.switchLabelContainer, labelContainerStyle]}>
|
||||
<Text style={[styles.switchLabelPrimary, { color: themes[theme].titleText }, leftLabelStyle]}>{leftLabelPrimary}</Text>
|
||||
<Text style={[styles.switchLabelSecondary, { color: themes[theme].titleText }, leftLabelStyle]}>{leftLabelSecondary}</Text>
|
||||
</View>
|
||||
)}
|
||||
<Switch
|
||||
style={styles.switch}
|
||||
onValueChange={onValueChange}
|
||||
value={value}
|
||||
disabled={disabled}
|
||||
trackColor={SWITCH_TRACK_COLOR}
|
||||
testID={testID}
|
||||
/>
|
||||
{rightLabelPrimary && (
|
||||
<View style={[styles.switchLabelContainer, labelContainerStyle]}>
|
||||
<Text style={[styles.switchLabelPrimary, { color: themes[theme].titleText }, leftLabelStyle]}>{rightLabelPrimary}</Text>
|
||||
<Text style={[styles.switchLabelSecondary, { color: themes[theme].titleText }, leftLabelStyle]}>{rightLabelSecondary}</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
{children}
|
||||
<View key='switch-divider' style={[styles.divider, { borderColor: themes[theme].separatorColor }]} />
|
||||
</>
|
||||
));
|
||||
|
||||
render() {
|
||||
const {
|
||||
value, disabled, onValueChange, leftLabelPrimary, leftLabelSecondary, rightLabelPrimary, rightLabelSecondary, theme, testID
|
||||
} = this.props;
|
||||
return (
|
||||
[
|
||||
<View key='switch-container' style={styles.switchContainer}>
|
||||
<View style={styles.switchLabelContainer}>
|
||||
<Text style={[styles.switchLabelPrimary, { color: themes[theme].titleText }]}>{leftLabelPrimary}</Text>
|
||||
<Text style={[styles.switchLabelSecondary, { color: themes[theme].titleText }]}>{leftLabelSecondary}</Text>
|
||||
</View>
|
||||
<Switch
|
||||
style={styles.switch}
|
||||
onValueChange={onValueChange}
|
||||
value={value}
|
||||
disabled={disabled}
|
||||
trackColor={SWITCH_TRACK_COLOR}
|
||||
testID={testID}
|
||||
/>
|
||||
<View style={styles.switchLabelContainer}>
|
||||
<Text style={[styles.switchLabelPrimary, { color: themes[theme].titleText }]}>{rightLabelPrimary}</Text>
|
||||
<Text style={[styles.switchLabelSecondary, { color: themes[theme].titleText }]}>{rightLabelSecondary}</Text>
|
||||
</View>
|
||||
</View>,
|
||||
<View key='switch-divider' style={[styles.divider, { borderColor: themes[theme].separatorColor }]} />
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
SwitchContainer.propTypes = {
|
||||
value: PropTypes.bool,
|
||||
disabled: PropTypes.bool,
|
||||
leftLabelPrimary: PropTypes.string,
|
||||
leftLabelSecondary: PropTypes.string,
|
||||
rightLabelPrimary: PropTypes.string,
|
||||
rightLabelSecondary: PropTypes.string,
|
||||
onValueChange: PropTypes.func,
|
||||
theme: PropTypes.string,
|
||||
testID: PropTypes.string,
|
||||
labelContainerStyle: PropTypes.object,
|
||||
leftLabelStyle: PropTypes.object,
|
||||
children: PropTypes.any
|
||||
};
|
||||
|
||||
export default SwitchContainer;
|
||||
|
|
|
@ -6,9 +6,12 @@ import {
|
|||
import { connect } from 'react-redux';
|
||||
import { SafeAreaView } from 'react-navigation';
|
||||
import equal from 'deep-equal';
|
||||
import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
import semver from 'semver';
|
||||
|
||||
import database from '../../lib/database';
|
||||
import { eraseRoom as eraseRoomAction } from '../../actions/room';
|
||||
import { deleteRoomInit as deleteRoomInitAction } from '../../actions/room';
|
||||
import KeyboardView from '../../presentation/KeyboardView';
|
||||
import sharedStyles from '../Styles';
|
||||
import styles from './styles';
|
||||
|
@ -27,6 +30,8 @@ import StatusBar from '../../containers/StatusBar';
|
|||
import { themedHeader } from '../../utils/navigation';
|
||||
import { themes } from '../../constants/colors';
|
||||
import { withTheme } from '../../theme';
|
||||
import { MultiSelect } from '../../containers/UIKit/MultiSelect';
|
||||
import { MessageTypeValues } from '../../utils/messageTypes';
|
||||
|
||||
const PERMISSION_SET_READONLY = 'set-readonly';
|
||||
const PERMISSION_SET_REACT_WHEN_READONLY = 'set-react-when-readonly';
|
||||
|
@ -51,7 +56,8 @@ class RoomInfoEditView extends React.Component {
|
|||
|
||||
static propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
eraseRoom: PropTypes.func,
|
||||
deleteRoomInit: PropTypes.func,
|
||||
serverVersion: PropTypes.string,
|
||||
theme: PropTypes.string
|
||||
};
|
||||
|
||||
|
@ -70,7 +76,9 @@ class RoomInfoEditView extends React.Component {
|
|||
t: false,
|
||||
ro: false,
|
||||
reactWhenReadOnly: false,
|
||||
archived: false
|
||||
archived: false,
|
||||
systemMessages: [],
|
||||
enableSysMes: false
|
||||
};
|
||||
this.loadRoom();
|
||||
}
|
||||
|
@ -117,7 +125,7 @@ class RoomInfoEditView extends React.Component {
|
|||
|
||||
init = (room) => {
|
||||
const {
|
||||
name, description, topic, announcement, t, ro, reactWhenReadOnly, joinCodeRequired
|
||||
name, description, topic, announcement, t, ro, reactWhenReadOnly, joinCodeRequired, sysMes
|
||||
} = room;
|
||||
// fake password just to user knows about it
|
||||
this.randomValue = random(15);
|
||||
|
@ -131,7 +139,9 @@ class RoomInfoEditView extends React.Component {
|
|||
ro,
|
||||
reactWhenReadOnly,
|
||||
joinCode: joinCodeRequired ? this.randomValue : '',
|
||||
archived: room.archived
|
||||
archived: room.archived,
|
||||
systemMessages: sysMes,
|
||||
enableSysMes: sysMes && sysMes.length > 0
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -148,7 +158,7 @@ class RoomInfoEditView extends React.Component {
|
|||
|
||||
formIsChanged = () => {
|
||||
const {
|
||||
room, name, description, topic, announcement, t, ro, reactWhenReadOnly, joinCode
|
||||
room, name, description, topic, announcement, t, ro, reactWhenReadOnly, joinCode, systemMessages, enableSysMes
|
||||
} = this.state;
|
||||
const { joinCodeRequired } = room;
|
||||
return !(room.name === name
|
||||
|
@ -159,13 +169,15 @@ class RoomInfoEditView extends React.Component {
|
|||
&& room.t === 'p' === t
|
||||
&& room.ro === ro
|
||||
&& room.reactWhenReadOnly === reactWhenReadOnly
|
||||
&& isEqual(room.sysMes, systemMessages)
|
||||
&& enableSysMes === (room.sysMes && room.sysMes.length > 0)
|
||||
);
|
||||
}
|
||||
|
||||
submit = async() => {
|
||||
Keyboard.dismiss();
|
||||
const {
|
||||
room, name, description, topic, announcement, t, ro, reactWhenReadOnly, joinCode
|
||||
room, name, description, topic, announcement, t, ro, reactWhenReadOnly, joinCode, systemMessages
|
||||
} = this.state;
|
||||
|
||||
this.setState({ saving: true });
|
||||
|
@ -210,6 +222,10 @@ class RoomInfoEditView extends React.Component {
|
|||
params.reactWhenReadOnly = reactWhenReadOnly;
|
||||
}
|
||||
|
||||
if (!isEqual(room.sysMes, systemMessages)) {
|
||||
params.systemMessages = systemMessages;
|
||||
}
|
||||
|
||||
// Join Code
|
||||
if (this.randomValue !== joinCode) {
|
||||
params.joinCode = joinCode;
|
||||
|
@ -237,7 +253,7 @@ class RoomInfoEditView extends React.Component {
|
|||
|
||||
delete = () => {
|
||||
const { room } = this.state;
|
||||
const { eraseRoom } = this.props;
|
||||
const { deleteRoomInit } = this.props;
|
||||
|
||||
Alert.alert(
|
||||
I18n.t('Are_you_sure_question_mark'),
|
||||
|
@ -250,7 +266,7 @@ class RoomInfoEditView extends React.Component {
|
|||
{
|
||||
text: I18n.t('Yes_action_it', { action: I18n.t('delete') }),
|
||||
style: 'destructive',
|
||||
onPress: () => eraseRoom(room.rid, room.t)
|
||||
onPress: () => deleteRoomInit(room.rid, room.t)
|
||||
}
|
||||
],
|
||||
{ cancelable: false }
|
||||
|
@ -298,12 +314,34 @@ class RoomInfoEditView extends React.Component {
|
|||
return (permissions[PERMISSION_ARCHIVE] || permissions[PERMISSION_UNARCHIVE]);
|
||||
};
|
||||
|
||||
renderSystemMessages = () => {
|
||||
const { systemMessages, enableSysMes } = this.state;
|
||||
const { theme } = this.props;
|
||||
|
||||
if (!enableSysMes) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<MultiSelect
|
||||
options={MessageTypeValues.map(m => ({ value: m.value, text: { text: I18n.t('Hide_type_messages', { type: I18n.t(m.text) }) } }))}
|
||||
onChange={({ value }) => this.setState({ systemMessages: value })}
|
||||
placeholder={{ text: I18n.t('Hide_System_Messages') }}
|
||||
value={systemMessages}
|
||||
context={BLOCK_CONTEXT.FORM}
|
||||
multiselect
|
||||
theme={theme}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
name, nameError, description, topic, announcement, t, ro, reactWhenReadOnly, room, joinCode, saving, permissions, archived
|
||||
name, nameError, description, topic, announcement, t, ro, reactWhenReadOnly, room, joinCode, saving, permissions, archived, enableSysMes
|
||||
} = this.state;
|
||||
const { theme } = this.props;
|
||||
const { serverVersion, theme } = this.props;
|
||||
const { dangerColor } = themes[theme];
|
||||
|
||||
return (
|
||||
<KeyboardView
|
||||
style={{ backgroundColor: themes[theme].backgroundColor }}
|
||||
|
@ -408,6 +446,20 @@ class RoomInfoEditView extends React.Component {
|
|||
]
|
||||
: null
|
||||
}
|
||||
{serverVersion && !semver.lt(serverVersion, '3.0.0') ? (
|
||||
<SwitchContainer
|
||||
value={enableSysMes}
|
||||
leftLabelPrimary={I18n.t('Hide_System_Messages')}
|
||||
leftLabelSecondary={enableSysMes ? I18n.t('Overwrites_the_server_configuration_and_use_room_config') : I18n.t('Uses_server_configuration')}
|
||||
theme={theme}
|
||||
testID='room-info-edit-switch-system-messages'
|
||||
onValueChange={value => this.setState(({ systemMessages }) => ({ enableSysMes: value, systemMessages: value ? systemMessages : [] }))}
|
||||
labelContainerStyle={styles.hideSystemMessages}
|
||||
leftLabelStyle={styles.systemMessagesLabel}
|
||||
>
|
||||
{this.renderSystemMessages()}
|
||||
</SwitchContainer>
|
||||
) : null}
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.buttonContainer,
|
||||
|
@ -452,7 +504,7 @@ class RoomInfoEditView extends React.Component {
|
|||
]}
|
||||
onPress={this.toggleArchive}
|
||||
disabled={!this.hasArchivePermission()}
|
||||
testID='room-info-edit-view-archive'
|
||||
testID={archived ? 'room-info-edit-view-unarchive' : 'room-info-edit-view-archive'}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
|
@ -460,7 +512,6 @@ class RoomInfoEditView extends React.Component {
|
|||
styles.button_inverted,
|
||||
{ color: dangerColor }
|
||||
]}
|
||||
accessibilityTraits='button'
|
||||
>
|
||||
{ archived ? I18n.t('UNARCHIVE') : I18n.t('ARCHIVE') }
|
||||
</Text>
|
||||
|
@ -498,8 +549,12 @@ class RoomInfoEditView extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
eraseRoom: (rid, t) => dispatch(eraseRoomAction(rid, t))
|
||||
const mapStateToProps = state => ({
|
||||
serverVersion: state.server.version
|
||||
});
|
||||
|
||||
export default connect(null, mapDispatchToProps)(withTheme(RoomInfoEditView));
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
deleteRoomInit: (rid, t) => dispatch(deleteRoomInitAction(rid, t))
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(RoomInfoEditView));
|
||||
|
|
|
@ -63,5 +63,14 @@ export default StyleSheet.create({
|
|||
broadcast: {
|
||||
...sharedStyles.textAlignCenter,
|
||||
...sharedStyles.textSemibold
|
||||
},
|
||||
hideSystemMessages: {
|
||||
alignItems: 'flex-start'
|
||||
},
|
||||
systemMessagesLabel: {
|
||||
textAlign: 'left'
|
||||
},
|
||||
switchMargin: {
|
||||
marginBottom: 16
|
||||
}
|
||||
});
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue